5 utilities live · sync or async · one key

Every developer utility.
One API.

HTML → PDF, split PDF, zip, unzip and XLS → XLSX today — with more on the way — behind a single API key. Call any of them sync or as a job; every file, request and millisecond is tracked.

⇢ Sync — wait for it ⇢ Async — get a job id ✓ Files + usage
1 key for every utility 90 s sync, then a job id 15 min per job p50 · p95 · p99 per key
How a ServiceLabs call flows Your service or the console calls ServiceLabs synchronously or as an async job. ServiceLabs checks the API key, creates a job and tracks it, runs the utility — HTML to PDF, split PDF, zip and unzip, or XLS to XLSX — and returns the resulting files. Sync call 200 · result Async job 202 · job_… 🧪 Console playground ServiceLabs key · job · usage 🧾 HTML → PDF html-pdf ✂️ Split PDF split-pdf 🗜️ Zip · Unzip zip-files · … 📊 XLS → XLSX xls-to-xlsx + more on the way calls → → runs & files CALL AUTH & TRACK RUN → FILES
One call. A finished file back.

Create a key in the console, then POST api.servicelabs.dev/v1/utilities/html-pdf — the answer is a job with a link to your PDF.

Get an API key →
Why it exists

Five utilities used to mean
five of everything.

Every useful little service — make a PDF, split one, zip a folder — shipped with its own URL, its own secret and its own quirks. Nobody could say who called what, how often, or how slowly.

A URL and secret per utility

Every caller hard-coded five endpoints and five signing keys — rotating one meant hunting down every service that used it.

Gateway timeouts, no job ids

A big PDF hit a 29-second gateway ceiling, and there was no job to come back to — just a timeout and a guess.

No usage, no latency

No way to answer “who uses split-pdf, and how slow is it?” — the data lived in five places, or nowhere.

ServiceLabs puts every utility behind one front door — one key, one job model, one place to see usage and latency.

How it works

A key, a call, a file

Same shape for every utility: send { "input": … }, get back a job.

1

Create a key

In the console → API keys. Scope it to the utilities a service needs and give it a rate limit. It's shown once; only a hash is stored.

2

Call it sync — or as a job

POST /v1/utilities/{slug} waits for the result. POST /v1/utilities/{slug}/jobs answers 202 with a job id straight away.

3

Get the result your way

The finished job carries result.files[].url. For async jobs, poll GET /v1/jobs/{id}?wait=30 or receive a signed callback.

# sync — waits for the finished PDF
curl https://api.servicelabs.dev/v1/utilities/html-pdf \
  -H "Authorization: Bearer $SERVICELABS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input": {"url": "https://acme.test/invoice.html",
                 "pdfOptions": {"format": "A4"}},
       "metadata": {"invoiceId": "INV-1042"}}'
Every call is a job

Sync when it's quick.
A job id when it isn't.

A sync call waits up to 90 seconds. If the utility needs longer, you get 202 and a job id — and the work keeps going, up to 15 minutes. Idempotency keys make retries safe; transient failures are retried for you; the job's timeline shows every stage down to the millisecond.

job_01K7… — timeline
# POST /v1/utilities/split-pdf  (sync)
 accepted          key checked, input validated
 running           invoking split-pdf
 90 s pass         still working
 202 + job id      you're free to go
 utility answered  412 pages in 6m 12s
 files stored      412 links
 succeeded         callback sent, signed
1
key for every utility
0
sync budget, then a job id
0
maximum per job
0
signed callback attempts
Batteries included

Everything around the utility, done once

Auth, jobs, callbacks, files and metrics are the platform's job — so every new utility gets them on day one.

One auth layer

Bearer API keys, scoped to utilities, with their own rate limit and optional expiry. Only a hash is stored; revoking takes effect on the next request. Every call is attributed to the key that made it.

Idempotent by design

Send Idempotency-Key and a retry returns the original job — never a second run.

Signed callbacks

Standard Webhooks signatures with each key's own secret, retried 8× over ~21 minutes.

Usage & latency, per key

Requests, success rate and p50 / p95 / p99 — end to end and inside the utility — per utility and per API key, for the last hour or the last 30 days. Rollups outlive the raw jobs.

Files handled

Outputs come back as links with name, size and type, laid out per job.

Guarded egress

Callback URLs must be public HTTPS; private and loopback addresses are refused.

A console for the whole team

A playground to try any utility, a job timeline down to the millisecond, usage by key, members and invites. Google sign-in for staff, passwords and 2FA for everyone, and partner accounts that only ever see their own keys and jobs.

Docs that can't drift

The API reference is generated from the same schemas that validate every request — plus llms.txt so your AI tools can read the docs too.

Developer-first

An API you can read,
generate and hand to an agent

One OpenAPI document describes every utility. Errors are stable codes, not strings to parse.

API reference

Every operation with request and response schemas, real examples, and a try-it panel.

OpenAPI 3.1

Generate a typed client in any language. Generated from the validators, so it always matches the API.

llms.txt for agents

The whole documentation as plain text — point Claude, Cursor or your own agent at /docs/llms-full.txt.

Stable error codes

code HTTP meaning
invalid_request 400 input failed validation — no job created
unauthorized 401 missing, revoked or expired key
rate_limited 429 per-key limit hit — honour Retry-After
upstream_rejected 422 the utility couldn't use your input
upstream_timeout 504 the utility ran past its limit
// callbacks verify with the open Standard Webhooks lib
import { Webhook } from "standardwebhooks";

// your key's whsec_… secret, shown when the key is created
const wh = new Webhook(process.env.SERVICELABS_WHSEC);

app.post("/hooks/servicelabs", rawJson, (req, res) => {
  const evt = wh.verify(req.body, req.headers); // throws if forged
  if (evt.type === "job.succeeded") {
    save(evt.data.result.files);  // dedupe on webhook-id
  }
  res.sendStatus(200);
});

Standard Webhooks, on the wire

Every callback is signed per the open standardwebhooks spec — webhook-id, webhook-timestamp, webhook-signature — so any off-the-shelf library verifies it. No proprietary SDK required.

Read the spec →
FAQ

Questions, answered

What is ServiceLabs?
One API in front of a growing set of developer utilities — HTML → PDF, split PDF, zip, unzip and XLS → XLSX today. Every call is a job with an id: wait for the result, or take the job id and fetch it later or receive a signed callback. Usage and latency are tracked for every utility and every key.
Who can use it?
ServiceLabs is run by Finnoto for its teams and invited partners. Finnoto staff sign in with Google; partners are invited to a guest account that only ever sees its own API keys, jobs and usage.
What does it cost?
There's no public price list — ServiceLabs is an internal platform. If your team or company wants access, talk to us and we'll set you up with an invite and a key.
How long can a utility run?
A sync call waits up to 90 seconds; if the utility is still working, it answers 202 with a job id and keeps going. An async job can run up to 15 minutes (5 for XLS → XLSX). Poll GET /v1/jobs/{id}?wait=30 or pass a callbackUrl.
Where do the output files live, and for how long?
Each file comes back as a link in result.files[] with its name, size and content type, stored under a per-job folder. Outputs are kept on a retention schedule, so download what you need to keep. Job records are kept for 90 days; usage history is kept for good.
How is it secured?
API keys are stored only as hashes and can be scoped, rate-limited, expired and revoked. The console uses httpOnly sessions, argon2id passwords, optional TOTP 2FA and Google sign-in. Callbacks are signed per key and only go to public HTTPS addresses. Utilities run in isolated cloud functions that only the gateway can invoke. More in Security.
Can we add our own utility?
Yes — a utility is one definition file: an input schema, how to call the function, and how to read its output. It then appears in the API, the console, the docs and the dashboards automatically. See Adding a utility.

One key. Every utility.

Sign in to the console, create a key, and make your first call in a couple of minutes.