Appearance
Zip files
Downloads a list of public URLs and packs them into a single .zip, with the entry names you choose. Returns one link to the archive.
| Slug | zip-files |
| Sync | POST /v1/utilities/zip-files → runZipFiles |
| Async | POST /v1/utilities/zip-files/jobs → createZipFilesJob |
| Max duration | 900 s (15 min) |
| Output | one application/zip file |
Quick example
bash
curl -X POST https://api.servicelabs.dev/v1/utilities/zip-files \
-H "Authorization: Bearer $DU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": {
"files": [
{ "url": "https://example.com/invoices/jan.pdf", "fileName": "invoices/jan.pdf" },
{ "url": "https://example.com/invoices/feb.pdf", "fileName": "invoices/feb.pdf" },
{ "url": "https://example.com/summary.xlsx", "fileName": "summary.xlsx" }
]
}
}'js
const res = await fetch('https://api.servicelabs.dev/v1/utilities/zip-files/jobs', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.DU_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'export-2026-09-customer-812',
},
body: JSON.stringify({
input: {
files: documents.map((d) => ({ url: d.url, fileName: `${d.folder}/${d.name}` })),
},
metadata: { exportId: 'export-2026-09-customer-812' },
callbackUrl: 'https://reports.finnoto.com/hooks/du',
}),
})
const { id } = await res.json() // 202 — the zip link arrives on your callbackjson
// → 200 OK (abridged)
{
"id": "job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3",
"utility": "zip-files",
"status": "succeeded",
"result": {
"files": [
{
"url": "https://files.servicelabs.dev/zip-files/2026-09-10/job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3/20260910173009_8f3a1c2b4d.zip",
"key": "zip-files/2026-09-10/job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3/20260910173009_8f3a1c2b4d.zip",
"name": "20260910173009_8f3a1c2b4d.zip",
"size": 1843220,
"contentType": "application/zip"
}
]
}
}Input
| Field | Type | Required | Description |
|---|---|---|---|
files | array | yes | 1 – 500 entries. |
files[].url | string | yes | Where to download the entry from. Must be http:// or https:// (no s3://). |
files[].fileName | string | yes | Path of the entry inside the zip, e.g. invoices/jan.pdf. 1 – 255 chars. |
Validation at the gateway (→ 400, no job):
fileNamemust be a relative path: no leading/and no..segment. (This closes the "zip slip" hole — an archive can never contain an entry that escapes its folder when extracted.)fileNamevalues must be unique within the request.- Every
urlmust be a validhttp(s)URL.
Result
result.files holds exactly one file — the archive — named yyyymmddhhmmss_<10 hex>.zip. No extras.
Limits & behaviour
- Files are downloaded one after another and buffered in memory before being compressed (zlib level 9). A long list, or large files, makes a job slow; very large inputs (hundreds of MB in total) can exceed the function's memory and fail with
502 upstream_error. Split big exports into several jobs. - Sources must be reachable from the public internet. A single failed download fails the whole job — nothing partial is returned.
- Prefer the async route for anything more than a handful of files; the sync route answers
202with the job id after 90 s anyway.
Errors specific to this utility
| Situation | Outcome |
|---|---|
Empty list, > 500 files, duplicate or unsafe fileName, non-http(s) url | 400 invalid_request — no job |
| One of the downloads failed | failed job, 502 upstream_error with the download error in error.message |
| Out of memory on large inputs | failed job, 502 upstream_error |
| Exceeded 900 s | failed job, 504 upstream_timeout |