Appearance
Files & storage
Every file a utility produces is copied to Cloudflare R2 before the job is marked succeeded, and result.files lists them. You never receive links into the utilities' internal storage.
File objects
json
{
"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",
"size": 48213,
"contentType": "application/pdf",
"path": "docs/invoice.pdf"
}| Field | Type | Description |
|---|---|---|
url | string | Where to download it. Public, or presigned (see below). |
key | string | The object key in the bucket — stable, useful for logging and support. |
name | string | File name without directories. |
size | integer | null | Bytes. |
contentType | string | null | MIME type (application/pdf, application/zip, …). |
urlExpiresAt | ISO 8601 | Only on presigned links: when url stops working. |
pageNumber | integer | Split PDF only: the 1-based page this file holds. |
path | string | Unzip only: the entry's path inside the archive. |
Order is meaningful: split-pdf lists pages in order, unzip lists entries in archive order.
Key layout
<utility>/<YYYY-MM-DD>/<job_id>/<file>YYYY-MM-DDis the job's UTC creation date — handy for lifecycle rules and browsing.- Everything a job produced sits under its own
job_idfolder, so concurrent jobs never collide. - Unzip keeps each entry's relative path (
…/job_…/docs/invoice.pdf); leading slashes and..segments are stripped so no entry can escape its folder.
Files are served with Content-Disposition: inline; filename="<name>", so browsers display PDFs and save other types under their real name.
Public vs presigned links
The deployment decides which kind of url you get:
| Mode | url looks like | Expires? |
|---|---|---|
| Public (default here) | https://files.servicelabs.dev/<key> | No — valid until the file is deleted by retention. |
| Presigned | https://….r2.cloudflarestorage.com/<bucket>/<key>?X-Amz-… | Yes, after 7 days; urlExpiresAt says when. |
Presigned links are re-signed whenever you read the job (GET /v1/jobs/{id}, callbacks) if they are within a day of expiring, so fetching the job again always gives you a working link. Don't store presigned URLs long-term — store the job id or the key.
Retention
Output files are deleted automatically by an R2 lifecycle rule — 30 days after creation unless your deployment says otherwise. Job records (and their links) are kept 90 days, so an old job can point at a file that no longer exists.
Download what you need to keep
ServiceLabs is a processing service, not an archive. Copy outputs you need beyond the retention window into your own storage as soon as the job succeeds.
Inputs
The gateway never downloads your inputs — it passes the URL to the utility, which fetches it itself from AWS (ap-south-1). So an input URL must be reachable from there:
| Scheme | Accepted by | Notes |
|---|---|---|
https:// / http:// | every utility | Must be publicly reachable. Presigned S3/R2 URLs work well. |
s3://bucket/key | html-pdf, split-pdf, unzip-files | Read with the utility's own AWS permissions — in practice the finnoto-data bucket. |
Zip files and XLS → XLSX accept only http(s):// sources. Internal hostnames (VPN-only, localhost) won't resolve from the utility; the job fails with the download error in error.message (usually upstream_rejected).