ServiceLabs

Unzip archive

Send a .zip. Get back one link per file inside it, with the archive’s folder paths kept.

SyncPOST /v1/utilities/unzip-filesWaits up to 90 s for the finished job.

AsyncPOST /v1/utilities/unzip-files/jobsAnswers 202 with the job id at once.

What it’s for

  • Uploads that arrive zippedUnpack what a customer or partner sent, then handle each file on its own.
  • Bulk importsA folder of invoices sent as one archive becomes a list of files you can loop over.
  • Folders that mean somethingEvery file keeps its path, like docs/invoice.pdf, so structure survives.
  • Archives you didn’t makeEntry paths are normalised, so a crafted archive can’t write outside its job’s folder.

How it runs

  1. DownloadReads the .zip from your http(s):// or s3:// URL into memory.
  2. WalkGoes through the entries one at a time. Directory entries are skipped.
  3. NormalisePaths with .., leading slashes or backslashes are cleaned, so every file stays inside the job’s folder.
  4. StoreOne file per entry, each with its path, plus fileCount.

There is no file-count limit, but thousands of small files take a while. Use the async route for anything big; the sync route answers 202 with the job id after 90 s anyway.

Input

Send these fields as input in the request body. Anything not listed is refused with 400.

Field Type Required or optional Accepts
url string Required http://, https:// or s3:// URL, 1–4,096 characters. The .zip to extract.

The request body

Field Required or optional What it is
input Required The fields above.
metadata Optional Your own key–value pairs, returned on the job. At most 50 keys, 4 KB serialised.
callbackUrl Optional Async route only. The finished job is POSTed here, signed. Up to 2,048 characters.

What comes back

Field Value
files[].url A link to the file.
result.fileCount Number of files extracted, equal to 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[].contentType From the file extension; application/octet-stream when it isn’t known.
files[].size The uncompressed size in bytes.

Example

curl https://api.servicelabs.dev/v1/utilities/unzip-files \
  -H "Authorization: Bearer $DU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": {
      "url": "https://example.com/bundle.zip"
    }
  }'
Response · 200 OK
{
  "id": "job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3",
  "object": "job",
  "utility": "unzip-files",
  "mode": "sync",
  "status": "succeeded",
  "createdAt": "2026-09-10T17:30:06.650Z",
  "finishedAt": "2026-09-10T17:30:09.009Z",
  "timings": { "queueMs": 0, "upstreamMs": 2140, "storageMs": 196, "totalMs": 2359 },
  "result": {
    "files": [
      {
        "url": "https://…/invoices/jan.pdf",
        "name": "jan.pdf",
        "path": "invoices/jan.pdf",
        "size": 120334,
        "contentType": "application/pdf"
      },
      {
        "url": "https://…/invoices/feb.pdf",
        "name": "feb.pdf",
        "path": "invoices/feb.pdf",
        "size": 118902,
        "contentType": "application/pdf"
      },
      {
        "url": "https://…/summary.csv",
        "name": "summary.csv",
        "path": "summary.csv",
        "size": 2211,
        "contentType": "text/csv"
      }
    ],
    "fileCount": 3
  },
  "error": null,
  "links": {
    "self": "/v1/jobs/job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3"
  }
}

An example job. File links point at the stored output; download anything you need to keep.

Bad input · 400
{
  "error": {
    "code": "invalid_request",
    "message": "input failed validation for unzip-files",
    "details": [
      {
        "path": "input.url",
        "message": "must be an http://, https:// or s3:// URL"
      }
    ]
  }
}

Input is checked before anything runs. A 400 creates no job and costs nothing.

Limits

Limit Value
Runs up to 900 s (15 min)
Files per archive No limit
Archive size Read into memory; extremely large archives can run out
Directory entries Skipped
An empty archive Succeeds with fileCount: 0
Sync wait 90 s, then 202 with the job id
Long-poll Up to 60 s per GET /v1/jobs/{id}?wait=
Rate limit Per key; 600 requests a minute unless set otherwise
Output files Kept for a limited period; download what you need

When it fails

A 400 means nothing ran. Everything else is a failed job with status: "failed", an error code and the utility’s own message.

Situation Status Code Job
url missing, or not http(s):// or s3:// 400 invalid_request No job
The archive can’t be downloaded, or isn’t a valid zip 502 upstream_error Failed job
Out of memory on a huge archive 502 upstream_error Failed job
Ran past 900 s 504 upstream_timeout Failed job

Any call can also get 401 unauthorized, 403 forbidden (the key isn’t allowed this utility) or 429 rate_limited. The full list is in Conventions & errors.

Reference

Also in service

Try Unzip archive on your own file.

The console’s playground runs unzip-files against any URL you give it and shows the job it returns.