Skip to content

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.

Slugzip-files
SyncPOST /v1/utilities/zip-filesrunZipFiles
AsyncPOST /v1/utilities/zip-files/jobscreateZipFilesJob
Max duration900 s (15 min)
Outputone 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 callback
json
// → 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

FieldTypeRequiredDescription
filesarrayyes1 – 500 entries.
files[].urlstringyesWhere to download the entry from. Must be http:// or https:// (no s3://).
files[].fileNamestringyesPath of the entry inside the zip, e.g. invoices/jan.pdf. 1 – 255 chars.

Validation at the gateway (→ 400, no job):

  • fileName must 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.)
  • fileName values must be unique within the request.
  • Every url must be a valid http(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 202 with the job id after 90 s anyway.

Errors specific to this utility

SituationOutcome
Empty list, > 500 files, duplicate or unsafe fileName, non-http(s) url400 invalid_request — no job
One of the downloads failedfailed job, 502 upstream_error with the download error in error.message
Out of memory on large inputsfailed job, 502 upstream_error
Exceeded 900 sfailed job, 504 upstream_timeout

ServiceLabs · a Finnoto company