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 /jobsRequest 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
| 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 |
| Max total output per job | 5 GB | 25 GB | 100 GB | 500 GB |
| Max size per output archive | 50 GB | 50 GB | 50 GB | 50 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 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 /jobsUses cursor-based pagination for efficient querying.
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
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/:idExample 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
| 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
| 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
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 {...})
| 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 {...})
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
Retry a failed job. By default the job resumes from the last checkpoint.
POST /jobs/:id/retryRequest Body
| Parameter | Type | Required | Description |
|---|---|---|---|
from_checkpoint | boolean | No | Resume 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/: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
| 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 |