Eazip
Eazip CloudHTTP API

Jobs API

API reference for creating and managing ZIP jobs.

A job represents a request to fetch multiple remote files and package them into one or more ZIP files.

A single job produces one ZIP by default, but you can opt into auto-split by setting max_zip_size_bytes — Eazip then bin-packs the input across N ZIP files, each capped to your size limit. Both shapes are exposed through the same job lifecycle and the same zips[] array in the response.

:::tip[New integrations: read zips[]] zips[] is the canonical shape for the ZIP outputs of a job, and it is always present — length 1 for non-split jobs and length N for auto-split jobs. New clients should iterate zips[] for filenames, sizes, and download URLs instead of reading the top-level zip_size / zip_filename / download_url fields.

The top-level fields remain populated for non-split jobs as a backward-compatibility convenience for existing integrations, and are null for split jobs. Building against zips[] from the start means your code works identically whether or not auto-split is later enabled on a job. :::

Create Job

POST /jobs

Request Body

ParameterTypeRequiredDescription
files{ url: string; filename?: string }[]YesArray of file entries to download. Each entry requires url and can optionally include filename. Min 1; max depends on your plan and mode.
mode"stored" | "stream"Nostored (default) builds the ZIP once and serves it from R2. stream builds the ZIP on the fly at download time.
zip_filenamestringNoFilename for the ZIP. Supports the filename template syntax for split jobs. Max 255 characters. Defaults to archive-{id}.zip.
expires_innumberNoSeconds until the ZIP is deleted. Min 300 (5 min), max depends on your plan. Defaults to 24 hours.
fail_on_url_errorbooleanNoIf true (default), the job fails when any URL cannot be fetched. If false, failed URLs are skipped and recorded in errors.
metadataobjectNoUp to 10 key/value pairs (key ≤ 128 chars, value ≤ 1,024 chars) stored with the job.
max_zip_size_bytesnumberNoEnable auto-split: each output ZIP is capped at this many bytes. Min 104857600 (100 MB), max 536870912000 (500 GB). Omit (or pass null) to produce a single ZIP.
split_strategy"preserve-order" | "ffd"NoBin-packing strategy when max_zip_size_bytes is set. preserve-order (default) keeps the input order across bins. ffd (First-Fit-Decreasing) minimises the number of bins by sorting by size.
allow_oversize_zipsbooleanNoIf true (default), a single file larger than max_zip_size_bytes is placed in its own ZIP that exceeds the cap. Pass false to fail the job instead.
fail_on_zip_errorbooleanNoIf true (default), the job is marked failed when any individual ZIP fails to build. If false, completed ZIPs remain downloadable and only the failing ones are reported in errors.

Plan Limits

FreeStarterProScale
Stored files per job1001,0005,00020,000
Stream files per job1001,0005,00020,000
Max total output per job5 GB25 GB100 GB500 GB
Max size per output archive50 GB50 GB50 GB50 GB
Stored quota100 GB-days/month3,000 GB-days/month20,000 GB-days/month75,000 GB-days/month
Stream quota20 GB/month500 GB/month3 TB/month20 TB/month
Max retention (expires_in)86,400 s (24 h)604,800 s (7 d)5,184,000 s (60 d)7,776,000 s (90 d)

max_zip_size_bytes is the per-archive cap used when auto-splitting. If omitted, Eazip creates one ZIP archive and enforces both the uniform 50 GB archive guardrail and the plan's total-output limit. If provided, it must be at most 50 GB. Prefer stored mode for larger or repeated downloads.

Example Request

curl -X POST https://api.eazip.io/jobs \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "files": [
      {
        "url": "https://example.com/file1.pdf",
        "filename": "invoice-001.pdf"
      },
      {
        "url": "https://example.com/file2.pdf"
      },
      {
        "url": "https://example.com/image.png",
        "filename": "preview.png"
      }
    ],
    "zip_filename": "my-archive",
    "expires_in": 172800,
    "fail_on_url_error": false,
    "metadata": {
      "order_id": "ord_123"
    }
  }'

Example: Auto-Split

Cap each output ZIP at 2 GB. The job is split into N ZIP files, each at or below the cap, and the output filenames are auto-numbered (e.g. photos_1.zip, photos_2.zip, ...).

curl -X POST https://api.eazip.io/jobs \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "files": [
      { "url": "https://example.com/photo-001.jpg" },
      { "url": "https://example.com/photo-002.jpg" }
    ],
    "zip_filename": "photos.zip",
    "max_zip_size_bytes": 2147483648,
    "split_strategy": "preserve-order",
    "fail_on_zip_error": true
  }'

Response

HTTP 201 Created

{
  "success": true,
  "job_id": "550e8400-e29b-41d4-a716-446655440000"
}

For auto-split jobs, the job briefly enters status: "preparing" while Eazip probes each URL with HEAD and runs the bin-packing algorithm. Once preparation completes the job moves to pending / processing and zips[] is populated.


List Jobs

GET /jobs

Uses cursor-based pagination for efficient querying.

Query Parameters

ParameterTypeDescription
limitnumberResults per page. Default 20, max 100.
cursorstringOpaque cursor from meta.next_cursor in the previous response. Omit for the first page.
statusstringFilter by status: pending, processing, completed, failed.

Example Request

curl "https://api.eazip.io/jobs?status=completed&limit=10" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{
  "success": true,
  "jobs": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "status": "completed",
      "url_count": 3,
      "file_count": 3,
      "zip_filename": "my-archive.zip",
      "zip_size": 1048576,
      "multi_zip": false,
      "zip_count": 1,
      "total_size": 1048576,
      "max_zip_size_bytes": null,
      "fail_on_url_error": true,
      "created_at": "2025-01-21T10:00:00.000Z",
      "completed_at": "2025-01-21T10:00:45.000Z",
      "expires_at": "2025-01-23T10:00:00.000Z"
    }
  ],
  "meta": {
    "limit": 10,
    "total": 42,
    "next_cursor": "2025-01-20T09:00:00.000Z",
    "has_more": true
  }
}

For split jobs the legacy zip_size / zip_filename fields surface the first ZIP bin only, while zip_count, total_size, and multi_zip describe the job as a whole. Call GET /jobs/:id to fetch the full zips[] array.

To fetch the next page, pass meta.next_cursor as the cursor query parameter. When meta.has_more is false, you have reached the end.


Get Job

GET /jobs/:id

Example Request

curl https://api.eazip.io/jobs/550e8400-e29b-41d4-a716-446655440000 \
  -H "X-API-Key: YOUR_API_KEY"

Response (single ZIP)

For a non-split job (no max_zip_size_bytes), zips[] contains a single entry — read zips[0] for the filename, size, and download URL. The legacy top-level fields (zip_size, zip_filename, download_url) mirror that single entry for backward compatibility, but new clients should prefer zips[] so the same code path works once auto-split is enabled.

{
  "success": true,
  "job": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "files": [
      { "url": "https://example.com/file1.pdf", "filename": "invoice-001.pdf" },
      { "url": "https://example.com/file2.pdf", "filename": null },
      { "url": "https://example.com/image.png", "filename": "preview.png" }
    ],
    "url_count": 3,
    "file_count": 3,
    "zip_filename": "my-archive.zip",
    "zip_size": 1048576,
    "fail_on_url_error": true,
    "download_url": "https://api.eazip.io/download/eyJ...",
    "metadata": { "order_id": "ord_123" },
    "created_at": "2025-01-21T10:00:00.000Z",
    "completed_at": "2025-01-21T10:00:45.000Z",
    "expires_at": "2025-01-23T10:00:00.000Z",
    "multi_zip": false,
    "max_zip_size_bytes": null,
    "split_strategy": "preserve-order",
    "fail_on_zip_error": true,
    "zip_count": 1,
    "total_size": 1048576,
    "zips": [
      {
        "id": "zip_a1b2c3d4e5f6",
        "sequence": 1,
        "status": "completed",
        "filename": "my-archive.zip",
        "file_count": 3,
        "size": 1048576,
        "download_url": "https://api.eazip.io/download/eyJ..."
      }
    ]
  }
}

Response (auto-split)

When max_zip_size_bytes is set, multi_zip is true, the legacy top-level download_url / zip_size are null, and clients should iterate zips[]. Each entry has its own signed download_url.

{
  "success": true,
  "job": {
    "id": "9c4d2f8e-1234-5678-90ab-cdef01234567",
    "status": "completed",
    "files": [ /* 1000 entries */ ],
    "url_count": 1000,
    "file_count": 1000,
    "zip_filename": "photos_01.zip",
    "zip_size": null,
    "fail_on_url_error": true,
    "download_url": null,
    "metadata": null,
    "created_at": "2026-04-22T10:00:00.000Z",
    "completed_at": "2026-04-22T10:08:12.000Z",
    "expires_at": "2026-04-23T10:00:00.000Z",
    "multi_zip": true,
    "max_zip_size_bytes": 2147483648,
    "split_strategy": "preserve-order",
    "fail_on_zip_error": true,
    "zip_count": 2,
    "total_size": 3221225472,
    "zips": [
      {
        "id": "zip_aaa111",
        "sequence": 1,
        "status": "completed",
        "filename": "photos_01.zip",
        "file_count": 612,
        "size": 2147483648,
        "download_url": "https://api.eazip.io/download/eyJ...AAA"
      },
      {
        "id": "zip_bbb222",
        "sequence": 2,
        "status": "completed",
        "filename": "photos_02.zip",
        "file_count": 388,
        "size": 1073741824,
        "download_url": "https://api.eazip.io/download/eyJ...BBB"
      }
    ]
  }
}

download_url (top-level and per-zip) is only present when status is completed and the job has not yet expired.

zips[] entry fields

FieldTypeDescription
idstringZIP file identifier (zip_...).
sequencenumber1-indexed bin number.
statusstringpending, processing, completed, or failed. Each ZIP has an independent lifecycle.
filenamestringFilename for this bin (see filename templates).
file_countnumberNumber of files packaged into this bin.
sizenumber | nullFinal ZIP size in bytes. null until packaging completes.
download_urlstring | nullSigned download URL. null for non-completed bins.
errorsarrayPer-bin URL errors (only present when applicable).

When fail_on_url_error is false and some file fetches fail, the response includes an errors array at the job level — and per-bin in zips[*].errors:

{
  "success": true,
  "job": {
    "id": "...",
    "status": "completed",
    "errors": [
      { "url": "https://example.com/missing.pdf", "error": "HTTP 404" }
    ]
  }
}

Job Status Values

StatusDescription
preparingAuto-split jobs only: probing URLs and computing the bin layout.
pendingJob (or one of its ZIP bins) waiting to start.
processingFetching files and building one or more ZIPs.
completedAll ZIPs are ready for download.
failedJob failed — check errors (job-level and per-zip) for details.

status is the aggregate across all ZIP bins. With fail_on_zip_error: false, the job may finish as completed even if some bins failed; the surviving bins remain downloadable from zips[].


ZIP Filename Templates

zip_filename accepts either a plain string or a template containing {...} placeholders. The same field powers both single-ZIP and auto-split jobs.

Plain mode (no {...})

CaseOutput
Single ZIP (max_zip_size_bytes not set)zip_filename used as-is.
Single ZIP, zip_filename omittedarchive-{job_id}.zip.
Split into N ≥ 2 ZIPs_{n} is inserted before the last extension, zero-padded to the digit count of N.
Split, no extension.zip is appended automatically.
zip_filename: "abc.zip"
  N=1   → abc.zip
  N=5   → abc_1.zip ... abc_5.zip          (1 digit)
  N=15  → abc_01.zip ... abc_15.zip        (2 digits)
  N=150 → abc_001.zip ... abc_150.zip      (3 digits)

zip_filename: "photos.tar.gz"
  N=5   → photos.tar_1.gz ... photos.tar_5.gz   (insert before last dot)

zip_filename: "abc"  (no extension)
  N=5   → abc_1.zip ... abc_5.zip

Template mode (contains {...})

When zip_filename contains a placeholder, Eazip treats the value as a literal template and expands it.

PlaceholderMeaningExample (n=2, N=5)
{n}1-indexed bin number, no padding2
{n2}Bin number, zero-padded to 2 digits02
{n3}Bin number, zero-padded to 3 digits002
{total}Total number of ZIPs5
{date}Job creation date in UTC (YYYY-MM-DD)2026-04-23

Use {{ and }} to render literal braces.

"abc_{n}.zip"                → abc_1.zip, abc_2.zip, ..., abc_5.zip
"abc_{n2}.zip"               → abc_01.zip, abc_02.zip, ..., abc_05.zip
"photos-{n}-of-{total}.zip"  → photos-1-of-5.zip, ..., photos-5-of-5.zip
"{date}-backup-{n2}.zip"     → 2026-04-23-backup-01.zip, ...
"export.{n3}.zip"            → export.001.zip, export.002.zip, ...

If a template has no index placeholder ({n} / {n2} / {n3}) but the job is split into multiple ZIPs, Eazip first renders the template once and then applies the plain-mode _{n} rule to guarantee unique filenames.

Validation:

  • Unknown placeholders (e.g. {banana}) return 400.
  • Unbalanced braces return 400.
  • The expanded filename per bin must be 1–255 characters and must not contain / or \.

Retry Job

Retry a failed job. By default the job resumes from the last checkpoint.

POST /jobs/:id/retry

Request Body

ParameterTypeRequiredDescription
from_checkpointbooleanNoResume from the last checkpoint (default: true). Pass false to restart from scratch.

Example Request

curl -X POST https://api.eazip.io/jobs/550e8400-e29b-41d4-a716-446655440000/retry \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "from_checkpoint": true }'

Response

{
  "success": true,
  "job_id": "550e8400-e29b-41d4-a716-446655440000"
}

Only jobs with status: "failed" can be retried.


Download ZIP

Download a completed ZIP. This endpoint does not require an API key — it is protected by the signed URL embedded in download_url.

GET /download/:token

Supports HTTP range requests (Range / If-Range headers) for partial downloads and resumable transfers.

For auto-split jobs, every entry in zips[] has its own download_url token. Each token is independent — clients are expected to download each ZIP file individually (in parallel or sequentially).


Error Codes

CodeHTTPDescription
INVALID_REQUEST400Missing or invalid parameters
PLAN_LIMIT_EXCEEDED403Exceeded file count or retention limit for your plan
QUOTA_EXCEEDED403Monthly stream or stored quota exceeded while overage is disabled
NOT_FOUND404Job doesn't exist or belongs to another user
INVALID_REQUEST400Only failed jobs can be retried
INVALID_TOKEN403Download token is invalid
EXPIRED_TOKEN403Download token has expired