Appearance
Unzip archive
Extracts every file in a .zip and returns one link per entry, keeping the entry's path. The archive can be a public http(s):// URL or an s3:// object.
| Slug | unzip-files |
| Sync | POST /v1/utilities/unzip-files → runUnzipFiles |
| Async | POST /v1/utilities/unzip-files/jobs → createUnzipFilesJob |
| Max duration | 900 s (15 min) |
| Output | one file per archive entry, plus fileCount |
Quick example
bash
curl -X POST https://api.servicelabs.dev/v1/utilities/unzip-files/jobs \
-H "Authorization: Bearer $DU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: bundle-7" \
-d '{ "input": { "url": "s3://finnoto-data/uploads/bundle.zip" } }'
# → 202 { "id": "job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3", "status": "queued", … }
curl "https://api.servicelabs.dev/v1/jobs/job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3?wait=30" \
-H "Authorization: Bearer $DU_API_KEY"js
const job = await fetch('https://api.servicelabs.dev/v1/utilities/unzip-files', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.DU_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ input: { url: 'https://example.com/bundle.zip' } }),
}).then((r) => r.json())
for (const f of job.result?.files ?? []) console.log(f.path, f.size, f.url)json
// → 200 OK (abridged)
{
"id": "job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3",
"utility": "unzip-files",
"status": "succeeded",
"result": {
"fileCount": 3,
"files": [
{
"url": "https://files.servicelabs.dev/unzip-files/2026-09-10/job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3/readme.txt",
"key": "unzip-files/2026-09-10/job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3/readme.txt",
"name": "readme.txt",
"path": "readme.txt",
"size": 1204,
"contentType": "text/plain"
},
{
"url": "https://files.servicelabs.dev/unzip-files/2026-09-10/job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3/docs/invoice.pdf",
"key": "unzip-files/2026-09-10/job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3/docs/invoice.pdf",
"name": "invoice.pdf",
"path": "docs/invoice.pdf",
"size": 88410,
"contentType": "application/pdf"
},
{ "name": "summary.csv", "path": "docs/summary.csv", "…": "…" }
]
}
}Input
| Field | Type | Required | Description |
|---|---|---|---|
url | string | yes | The .zip to extract. http://, https:// or s3://, ≤ 4096 chars. |
Result
| Field | Description |
|---|---|
result.fileCount | Number of files extracted (= files.length). |
files[].path | The entry's path inside the archive, e.g. docs/invoice.pdf. |
files[].name | The last path segment, e.g. invoice.pdf. |
files[].key | Where it lives: unzip-files/<date>/<job id>/<path>. The archive's folder structure is preserved under the job's folder. |
files[].contentType | Set from the entry's file extension (PDF, images, Office documents, CSV, JSON, text, …); application/octet-stream when the extension is unknown. |
files[].size | The uncompressed size in bytes. |
Limits & behaviour
- Directory entries are skipped — only files are returned.
- Paths can't escape. Entry names containing
.., leading slashes or backslashes are normalised when stored, so every file stays inside the job's folder even if the archive itself was crafted maliciously. - No file-count limit, and entries are extracted one at a time. A zip with thousands of small files is slow — use the async route for anything big (the sync route answers
202with the job id after 90 s anyway). - The whole archive is read into memory, so extremely large archives can exceed the function's memory (
502 upstream_error). - An empty archive succeeds with
fileCount: 0andfiles: [].
Errors specific to this utility
| Situation | Outcome |
|---|---|
url missing or not http(s):// / s3:// | 400 invalid_request — no job |
| Archive can't be downloaded, or isn't a valid zip | failed job, 502 upstream_error (message from the extractor) |
| Out of memory on a huge archive | failed job, 502 upstream_error |
| Exceeded 900 s | failed job, 504 upstream_timeout |