Every API response shares the same envelope. What varies between endpoints is the content of data; the surrounding structure is identical across all 456 operations, error responses included.
The envelope follows the JSON:API convention, with two mutually exclusive top-level keys —data on successful responses, errors on failed ones— and a meta key present in both.
Successful response document
{
"data": {
"type": "webhook_endpoint",
"id": "wh_9f3c2a1b",
"attributes": {
"url": "https://receiver.example/hooks/spei",
"status": "active"
}
},
"meta": {
"version": "1.57.0",
"api_version": "v1",
"request_id": "c4d5e6f7a8b9",
"datetime": { "timezone": "UTC", "format": "ISO 8601" }
}
}Each resource is identified by type and id, and its fields live under attributes. The shape of data depends on the operation:
shape of data | operations returning it |
|---|---|
Object {type, id, attributes} | Single-resource reads and resource creation |
Array of {type, id, attributes} objects | Listings |
null | Operations with no associated resource, such as deletions answering 200 |
| No body | 204 responses, which carry no document |
Operations that accompany the primary resource with related ones add an included key next to data, holding objects of the same shape.
Error document
{
"errors": [
{
"status": "422",
"code": "webhook_url_not_https",
"detail": "The URL must use HTTPS.",
"source": { "pointer": "/data/attributes/url" }
}
],
"meta": {
"version": "1.57.0",
"api_version": "v1",
"request_id": "d5e6f7a8b9c0",
"datetime": { "timezone": "UTC", "format": "ISO 8601" }
}
}errors is always an array, even when it holds a single element. A validation rejection covering three fields returns three entries, each with its own source.pointer naming the offending field.
The code is the stable snake_case identifier of the error, and the only field an integration should branch on. The detail is human-readable text and varies with the language of the request: comparing it as a string breaks the integration as soon as a client requests another language.
Rejections whose reason carries a concrete value expose it in meta rather than burying it in the sentence: meta.limit when a cap is reached, meta.event when an unrecognised value is received. The full inventory of codes lives in the error taxonomy.
The meta block
Present on every response, successful or failed, with these base fields:
| field | content |
|---|---|
version | API version that served the request |
api_version | Route family that resolved it (v1) |
request_id | Unique request identifier, required for any support enquiry |
datetime.timezone | Always UTC |
datetime.format | Always ISO 8601 |
The datetime pair is a runtime-verifiable guarantee: every *_at, *_start, *_end and *_date field of any response is emitted as ISO 8601 with a Z suffix, with no need to consult the specification on each release.
An operation may add its own keys to meta — a listing's pagination, for instance — without altering the ones above.
Consumption contract
- The HTTP status code determines handling:
204carries no document. - The presence of
errorsindicates failure; branching is done oncode. - The presence of
dataindicates success; resource fields live underattributes. - The
request_idmust be recorded alongside each response: it is the value that locates a specific request during an incident.