Skip to content

Jobs API

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.

POST /jobs
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.
FreeStarterProScale
Stored files per job1001,0005,00020,000
Stream files per job1001,0005,00020,000
Stored ZIP size per archive2 GB10 GB50 GB100 GB
Stream ZIP size per archive2 GB10 GB50 GB100 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 the plan’s per-archive limit. If provided, it must be within your plan’s per-archive limit. Stream mode supports the same per-archive limits as stored mode, but 10 GB is recommended for best reliability; prefer stored mode for larger or repeated downloads.

Terminal window
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"
}
}'

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, …).

Terminal window
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
}'

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.


GET /jobs

Uses cursor-based pagination for efficient querying.

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.
Terminal window
curl "https://api.eazip.io/jobs?status=completed&limit=10" \
-H "X-API-Key: YOUR_API_KEY"
{
"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 /jobs/:id
Terminal window
curl https://api.eazip.io/jobs/550e8400-e29b-41d4-a716-446655440000 \
-H "X-API-Key: YOUR_API_KEY"

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..."
}
]
}
}

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.

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" }
]
}
}
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 accepts either a plain string or a template containing {...} placeholders. The same field powers both single-ZIP and auto-split jobs.

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

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 a failed job. By default the job resumes from the last checkpoint.

POST /jobs/:id/retry
ParameterTypeRequiredDescription
from_checkpointbooleanNoResume from the last checkpoint (default: true). Pass false to restart from scratch.
Terminal window
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 }'
{
"success": true,
"job_id": "550e8400-e29b-41d4-a716-446655440000"
}

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


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).


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