Conventions
The rules every call follows: money on the wire, idempotency, lists and the feed's cursor, versions, webhook signatures, request ids and casing.
Every brand's API follows the same rules on every route, so an integrator learns them once. Each one below is shown as a captured call.
You write prices in dollars: price: 49, max_usd: 49. A buyer reads dollars where a person reads money: an order's max_usd, a job's payment.amount_usd, a receipt's fee_usd, an event's amount_usd. A machine computes in integer cents where the manifest and the quote carry a price, beside the sentence a person reads:
curl http://localhost:8787/translation.json{
"type": "OK",
"id": "translation",
"name": "Translation",
"price": {
"per": "job",
"cents": 4900,
"within": "48h",
"ai_cap": "20%",
"final_after": "7d",
"charged": "on delivery",
"sentence": "$49 per job, within 48 hours, charged on delivery."
},
"surfaces": {
"route": "POST /jobs {\"service\":\"translation\"}",
"tool": "order_translation"
}
}Every write takes an Idempotency-Key header; an order's key field is the same key, for the CLI and MCP. The first answer is kept for 24 hours. Sending the same key again replays it byte for byte, with its original status and idempotent-replayed: true, and emits no event twice. The same key with a different body is 409 key_reused, and param names the first field that differs.
curl -i -X POST http://localhost:8787/jobs -H 'content-type: application/json' -H 'Idempotency-Key: lease-fr-CA' -d '{"service":"translation","document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","max_usd":49,"key":"lease"}'HTTP/1.1 202
request-id: req_WppKI6
services-version: 2026-10-25
← job_7Hq2 · working · $49.00 heldcurl -i -X POST http://localhost:8787/jobs -H 'content-type: application/json' -H 'Idempotency-Key: lease-fr-CA' -d '{"service":"translation","document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"de","max_usd":49,"key":"lease"}'HTTP/1.1 409
request-id: req_d8ix4b
services-version: 2026-10-25
← key_reused · param to · This Idempotency-Key was first sent with a different body: to differs. Send a fresh key for a different call.A list is { object: 'list', data, has_more, url }. limit takes up to 100, and starting_after takes the last id you saw. The events feed pages by seq instead, with after and next_seq: the feed is an ordered log, each event's seq never changes, and a reader that stopped at seq 41 resumes after 41, however many events arrived meanwhile.
curl 'http://localhost:8787/events?after=0&limit=2'{
"type": "OK",
"object": "list",
"data": [
{
"id": "evt_5gX1X2B5swzY61",
"object": "event",
"seq": 1,
"type": "job.ordered",
"created": "2026-10-25T14:02:11.000Z",
"job": "job_7Hq2",
"service": "translation",
"data": {
"by": "buyer",
"amount_usd": 49
},
"request": {
"id": "req_WppKI6",
"idempotency_key": null
},
Every answer carries services-version, such as 2026-10-25. A request may pin one with the Services-Version header, and a version the brand doesn't serve is 400 version_unknown. npx services.dev diff marks every change to a Service breaking or not: a removed field, a newly required field, a changed type or a removed enum value breaks. A key can pin a version, so its answers never change shape under it, and check --against live fails a breaking change before you deploy it.
Each delivery carries services-signature: t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of <t>.<raw body>, keyed with the watch's whsec_ secret. Verify the raw bytes as they arrived, before parsing them, and refuse a t more than 300 seconds from now. Your events has a verifier in six lines and the retry schedule.
Every answer carries a request-id header, and a refusal repeats it as request_id. Each event names the request that caused it, { id, idempotency_key }, or null when the work or a clock did.
Fields, query parameters, codes, events and paths are snake_case: send_back, max_usd, next_seq, approved_ledger. Every id starts with its object's prefix, such as job_, ask_ or rcp_: Objects lists them.