Conventions
Requests
Section titled “Requests”- HTTPS and JSON. A request with a body must send
Content-Type: application/jsonand exactly one JSON object. - Unknown fields are rejected (
400 COMMON.INVALID_INPUT), so a misspelled field is never silently ignored. - Request bodies are limited to 64 KiB.
- Services generates
X-Request-IDfor every response and ignores a client-supplied one. Quote it when reporting a problem. - Every
/v1response setsCache-Control: no-store.
Envelopes
Section titled “Envelopes”Success:
{ "code": 0, "message": "success", "data": { "execution": { }, "result": { } } }Error:
{ "code": 409, "error_code": "EXECUTION.DUPLICATE_INVOCATION", "message": "This invocation was already executed.", "request_id": "req_5f0c...", "execution": { }}Branch on error_code. message is static, caller-safe text and never contains provider responses, URLs or internal errors. See Errors for the full list.
Invocation ids and execution records
Section titled “Invocation ids and execution records”Every capability call carries your invocation_id (1–128 characters of A-Z a-z 0-9 . _ : -). Services records one execution per (caller, invocation_id, operation), written before the provider is called:
- if the record cannot be written, the provider is not called and the response is
503 SERVICES.RECORD_UNAVAILABLE; - if you repeat an
invocation_idfor the same operation, the provider is not called again and the response is409 EXECUTION.DUPLICATE_INVOCATIONwith the existingexecution. Services does not store results and cannot replay them.
Requests rejected before an execution exists (authentication, scope, validation, unknown or disabled provider, local concurrency limit, body size) carry no execution and started no provider work.
The execution block
Section titled “The execution block”{ "id": "exe_0192f6...", "invocation_id": "order-10086-1", "capability": "public_holiday", "operation": "public_holiday.query", "provider": "holiday-cn", "outcome": "succeeded", "error_category": null, "error_code": null, "usage": { "provider_requests": 0, "units": [{ "name": "days", "value": 8, "source": "measured" }] }, "request_id": "req_5f0c...", "started_at": "2026-10-11T09:00:00Z", "finished_at": "2026-10-11T09:00:01Z"}Outcomes
Section titled “Outcomes”| outcome | Meaning | Caller handling |
|---|---|---|
succeeded |
Result delivered | Bill by your product rules |
failed |
Definitely no usable result | Apply failure rules |
unknown |
The provider received the request; its effect is undetermined | Never retry automatically; reconcile via the call log |
accepted |
Asynchronous task created, not terminal | Look up later |
expired |
The provider reported the asynchronous result expired | Apply failure rules |
in_progress |
Reconciliation only: still inside its deadline | Query again later |
An execution still unfinished after its deadline (for example after a crash) is reported as unknown.
provider_requests: requests actually sent upstream; 0 for local data such asholiday-cn.units[]: the capability’s own units, such asdaysfor holidays orworksfor scholarly search.sourceisprovider(reported upstream) ormeasured(counted by Services). Unavailable quantities are omitted, never estimated.- Usage never contains money; pricing is yours.
Retries
Section titled “Retries”| Situation | Retry? | Which invocation_id |
|---|---|---|
Errors without execution: 429 COMMON.RATE_LIMITED, 503 SERVICES.RECORD_UNAVAILABLE, 503 SERVICES.PROVIDER_DISABLED |
Yes, later | The same id |
Request errors: 400, 413, 415, 422 COMMON.VALIDATION_FAILED |
After fixing the request | The same id |
outcome failed, for example PROVIDER.UNAVAILABLE, PROVIDER.RATE_LIMITED, COMMON.TIMEOUT |
Yes, honoring Retry-After |
A new id (the old one returns 409) |
outcome unknown (502 EXECUTION.OUTCOME_UNKNOWN) |
Not automatically | Reconcile in the call log first, then decide |
409 EXECUTION.DUPLICATE_INVOCATION |
No need | Read the attached execution for the first result |
Timeouts
Section titled “Timeouts”Providers time out after 30 seconds by default; some take longer (world-bank allows 60 seconds; see timeout_seconds in the reference). Use a client read timeout of at least 90 seconds so you never disconnect before the execution is recorded.
Health
Section titled “Health”| Endpoint | Auth | Meaning |
|---|---|---|
GET /health/live |
None | The process is alive |
GET /health/ready |
None | Configuration and database are available; never calls a paid provider |