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.
Create Job
Section titled “Create Job”POST /jobsRequest Body
Section titled “Request Body”| Parameter | Type | Required | Description |
|---|---|---|---|
files | { url: string; filename?: string }[] | Yes | Array 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" | No | stored (default) builds the ZIP once and serves it from R2. stream builds the ZIP on the fly at download time. |
zip_filename | string | No | Filename for the ZIP. Supports the filename template syntax for split jobs. Max 255 characters. Defaults to archive-{id}.zip. |
expires_in | number | No | Seconds until the ZIP is deleted. Min 300 (5 min), max depends on your plan. Defaults to 24 hours. |
fail_on_url_error | boolean | No | If true (default), the job fails when any URL cannot be fetched. If false, failed URLs are skipped and recorded in errors. |
metadata | object | No | Up to 10 key/value pairs (key ≤ 128 chars, value ≤ 1,024 chars) stored with the job. |
max_zip_size_bytes | number | No | Enable 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" | No | Bin-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_zips | boolean | No | If 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_error | boolean | No | If 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
Section titled “Plan Limits”| Free | Starter | Pro | Scale | |
|---|---|---|---|---|
| Stored files per job | 100 | 1,000 | 5,000 | 20,000 |
| Stream files per job | 100 | 1,000 | 5,000 | 20,000 |
| Stored ZIP size per archive | 2 GB | 10 GB | 50 GB | 100 GB |
| Stream ZIP size per archive | 2 GB | 10 GB | 50 GB | 100 GB |
| Stored quota | 100 GB-days/month | 3,000 GB-days/month | 20,000 GB-days/month | 75,000 GB-days/month |
| Stream quota | 20 GB/month | 500 GB/month | 3 TB/month | 20 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.
Example Request
Section titled “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
Section titled “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
Section titled “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
Section titled “List Jobs”GET /jobsUses cursor-based pagination for efficient querying.
Query Parameters
Section titled “Query Parameters”| Parameter | Type | Description |
|---|---|---|
limit | number | Results per page. Default 20, max 100. |
cursor | string | Opaque cursor from meta.next_cursor in the previous response. Omit for the first page. |
status | string | Filter by status: pending, processing, completed, failed. |
Example Request
Section titled “Example Request”curl "https://api.eazip.io/jobs?status=completed&limit=10" \ -H "X-API-Key: YOUR_API_KEY"Response
Section titled “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
Section titled “Get Job”GET /jobs/:idExample Request
Section titled “Example Request”curl https://api.eazip.io/jobs/550e8400-e29b-41d4-a716-446655440000 \ -H "X-API-Key: YOUR_API_KEY"Response (single ZIP)
Section titled “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)
Section titled “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
Section titled “zips[] entry fields”| Field | Type | Description |
|---|---|---|
id | string | ZIP file identifier (zip_...). |
sequence | number | 1-indexed bin number. |
status | string | pending, processing, completed, or failed. Each ZIP has an independent lifecycle. |
filename | string | Filename for this bin (see filename templates). |
file_count | number | Number of files packaged into this bin. |
size | number | null | Final ZIP size in bytes. null until packaging completes. |
download_url | string | null | Signed download URL. null for non-completed bins. |
errors | array | Per-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
Section titled “Job Status Values”| Status | Description |
|---|---|
preparing | Auto-split jobs only: probing URLs and computing the bin layout. |
pending | Job (or one of its ZIP bins) waiting to start. |
processing | Fetching files and building one or more ZIPs. |
completed | All ZIPs are ready for download. |
failed | Job 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
Section titled “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 {...})
Section titled “Plain mode (no {...})”| Case | Output |
|---|---|
Single ZIP (max_zip_size_bytes not set) | zip_filename used as-is. |
Single ZIP, zip_filename omitted | archive-{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.zipTemplate mode (contains {...})
Section titled “Template mode (contains {...})”When zip_filename contains a placeholder, Eazip treats the value as a literal template and expands it.
| Placeholder | Meaning | Example (n=2, N=5) |
|---|---|---|
{n} | 1-indexed bin number, no padding | 2 |
{n2} | Bin number, zero-padded to 2 digits | 02 |
{n3} | Bin number, zero-padded to 3 digits | 002 |
{total} | Total number of ZIPs | 5 |
{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}) return400. - Unbalanced braces return
400. - The expanded filename per bin must be 1–255 characters and must not contain
/or\.
Retry Job
Section titled “Retry Job”Retry a failed job. By default the job resumes from the last checkpoint.
POST /jobs/:id/retryRequest Body
Section titled “Request Body”| Parameter | Type | Required | Description |
|---|---|---|---|
from_checkpoint | boolean | No | Resume from the last checkpoint (default: true). Pass false to restart from scratch. |
Example Request
Section titled “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
Section titled “Response”{ "success": true, "job_id": "550e8400-e29b-41d4-a716-446655440000"}Only jobs with status: "failed" can be retried.
Download ZIP
Section titled “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/:tokenSupports 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
Section titled “Error Codes”| Code | HTTP | Description |
|---|---|---|
INVALID_REQUEST | 400 | Missing or invalid parameters |
PLAN_LIMIT_EXCEEDED | 403 | Exceeded file count or retention limit for your plan |
QUOTA_EXCEEDED | 403 | Monthly stream or stored quota exceeded while overage is disabled |
NOT_FOUND | 404 | Job doesn’t exist or belongs to another user |
INVALID_REQUEST | 400 | Only failed jobs can be retried |
INVALID_TOKEN | 403 | Download token is invalid |
EXPIRED_TOKEN | 403 | Download token has expired |