Appearance
Introduction
ServiceLabs is Finnoto's single front door for document and file utilities. Before it, every utility was its own AWS Lambda behind its own API Gateway URL, each with its own JWT secret, its own request shape and no shared view of who was calling what. Now there is one base URL, one API key, one request envelope and one result shape — plus a console that shows every call.
- One auth layer. Services authenticate with a scoped, revocable API key (
du_live_…). People sign in to the console with Google (finnoto.com) or a password, with optional two-factor. - Sync or async. Every call becomes a job with a stable id. Wait for the result in the same request, or get the job id back immediately and fetch the result later — by polling or through a signed callback.
- Tracked. Requests, success rate and latency (end-to-end and inside the utility) per utility and per API key, kept after the raw jobs age out.
- Files on R2. Every file a utility produces is copied to Cloudflare R2; you always get R2 links back.
The utilities
| Utility | Slug | Input | Output | Time limit |
|---|---|---|---|---|
| HTML → PDF | html-pdf | HTML document URL (http(s)://, s3://) + page options | 1 PDF | 15 min |
| Split PDF | split-pdf | PDF URL (≤ 150 MB, ≤ 500 pages) | 1 PDF per page | 15 min |
| Zip files | zip-files | Up to 500 { url, fileName } | 1 .zip | 15 min |
| Unzip archive | unzip-files | .zip URL (http(s)://, s3://) | 1 file per archive entry | 15 min |
| XLS → XLSX | xls-to-xlsx | Legacy .xls URL | 1 .xlsx | 5 min |
GET /v1/utilities returns the same list for your key, each with its full input JSON Schema and an example — see List utilities.
How a call flows
your service
│ POST /v1/utilities/split-pdf Authorization: Bearer du_live_…
▼
┌──────────────────────────┐ validate input · check key scope + rate limit
│ API gateway │ create job job_01K…
└──────────┬───────────────┘
sync │ async (POST …/jobs, or Prefer: respond-async)
run it │ ──────────▶ queue ──▶ worker picks it up
in the │ │
request ▼ ▼
┌──────────────────────────────────────────────────────┐
│ invoke the utility (AWS Lambda, up to 15 min) │ retries only transient failures
│ → it reads your input URL and writes files to S3 │
└──────────┬───────────────────────────────────────────┘
▼
copy every output file S3 → Cloudflare R2
▼
job succeeded / failed ──▶ response (sync) · GET /v1/jobs/:id · signed callback (async)
▼
usage + latency rollups ──▶ console dashboardWhat a job is
A job is one call to one utility. Sync and async calls produce exactly the same object — the only difference is whether the request waited for it to finish.
json
{
"id": "job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3",
"object": "job",
"utility": "split-pdf",
"mode": "async",
"status": "succeeded",
"timings": { "queueMs": 41, "upstreamMs": 2140, "storageMs": 180, "totalMs": 2372 },
"result": {
"files": [
{
"url": "https://files.servicelabs.dev/split-pdf/2026-09-10/job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3/page-1.pdf",
"key": "split-pdf/2026-09-10/job_01K7Z3Q4N5P6R7S8T9V0W1X2Y3/page-1.pdf",
"name": "page-1.pdf",
"size": 48213,
"contentType": "application/pdf",
"pageNumber": 1
}
],
"pageCount": 1
},
"error": null
}The id is the handle for everything: fetch it with GET /v1/jobs/{id}, find it in the console, quote it to support. Details in Jobs, sync & async.
Where to next
- Quickstart — your first sync and async call in a few minutes.
- Jobs, sync & async — lifecycle, idempotency, polling, retries.
- Callbacks — get async results pushed to you, signed.
- Migrating from the Lambda URLs — moving an existing caller over.
- API reference and API operations — every endpoint and schema.