Eazip
Eazip.jsPricingSign inStart free

Jobs API

API reference for creating and managing ZIP jobs.

A job fetches remote files and packages them into one or more ZIPs. It produces one ZIP by default; set max_zip_size_bytes to auto-split the input across N ZIP files, each capped at that size.

Both shapes share the same job lifecycle and the same zips[] array in the response.

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, and must be at most 50 GB. Omit it and Eazip creates one archive under both the uniform 50 GB archive guardrail and the plan's total-output limit.

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 ZIPs at or below the cap, with auto-numbered filenames (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"
}

An auto-split job briefly enters status: "preparing" while Eazip probes each URL with HEAD and runs the bin-packing algorithm. It then moves to pending / processing and zips[] is populated.


List Jobs

GET /jobs

Uses cursor-based pagination.

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, zip_size / zip_filename surface the first ZIP bin only, while zip_count, total_size, and multi_zip describe the job as a whole. Call GET /jobs/:id for the full zips[] array.

Pass meta.next_cursor as the cursor query parameter for the next page. meta.has_more: false means 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[] holds a single entry — read zips[0] for the filename, size, and download URL.

{
  "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 and the top-level download_url / zip_size are null. Each zips[] 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 present only 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 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 completed even if some bins failed, and the surviving bins stay downloadable from zips[].


ZIP Filename Templates

zip_filename accepts either a plain string or a template containing {...} placeholders, for 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 splits into multiple ZIPs, Eazip 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 job with status: "failed" — no other status can be retried. By default it 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"
}

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 zips[] entry has its own independent download_url token. Download each ZIP 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