ServiceLabs

Zip files

Send a list of URLs and the names you want them to have inside the archive. Get back one .zip.

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

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

What it’s for

  • Download-all linksBundle a month of invoices, or a ticket’s attachments, into one link for whoever asked.
  • Hand-over packsGive an auditor or a partner one archive, with folders like invoices/jan.pdf.
  • Gathering from many placesFiles from different hosts and buckets end up side by side in one download.
  • Your folder structureEach fileName is the entry’s path inside the zip, so you decide the layout.

How it runs

  1. Check1–500 entries, unique relative names, http(s) URLs only. Anything else is a 400 before a job exists.
  2. DownloadFetches each URL one after another and holds it in memory. One failed download fails the job.
  3. PackCompresses everything into a single .zip at zlib level 9.
  4. StoreOne application/zip file on the job.

Very large inputs, hundreds of MB in total, can run out of memory and fail with 502. Split big exports across several jobs, and use the async route for more than a handful of files.

Input

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

Field Type Required or optional Accepts
files array Required 1–500 entries. fileName values must be unique.
files[].url string Required A valid http:// or https:// URL. No s3:// here. Where to download the entry from.
files[].fileName string Required Path of the entry inside the zip, e.g. "invoices/jan.pdf". 1–255 characters, relative: no leading / and no .. segment.

The fileName rules close the zip-slip hole: no entry can escape its folder when the archive is extracted.

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.files Exactly one file.
files[].name A generated yyyymmddhhmmss_<10 hex>.zip name.
files[].contentType application/zip

Example

curl 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/a.pdf",
          "fileName": "a.pdf"
        },
        {
          "url": "https://example.com/b.pdf",
          "fileName": "docs/b.pdf"
        }
      ]
    }
  }'
Response · 200 OK
{
  "id": "job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3",
  "object": "job",
  "utility": "zip-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://…/20260910173009_8f3a1c2b4d.zip",
        "name": "20260910173009_8f3a1c2b4d.zip",
        "size": 2048733,
        "contentType": "application/zip"
      }
    ]
  },
  "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 zip-files",
    "details": [
      {
        "path": "input.files.0.url",
        "message": "must be an http(s) 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)
Entries 1–500 per job
Entry names 1–255 characters, relative, unique
Sources http:// or https://, reachable from the internet
Downloads One after another, held in memory
Compression zlib level 9
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
An empty list, more than 500 files, a duplicate or unsafe fileName, a non-http(s) url 400 invalid_request No job
One of the downloads failed. The download error is in error.message. 502 upstream_error Failed job
Out of memory on large inputs 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 Zip files on your own file.

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