Skip to content

13. Conventions and wire formats

conventions holds the api-wide settings. Any layer may set them (section 2), usually a pack; a node never does, because a convention is one fact for the whole api. The table gives each key’s default and, where there is none, when the key becomes required; a missing required key is a compile error naming the key, the route that needs it and the fix.

# shared/base.yaml (excerpt)
conventions:
format: bare
pathCase: kebab
paging: { default: cursor, maxLimit: 100 }
idempotency: { header: Idempotency-Key, replayHeader: Idempotency-Replayed, lifetime: 24h, methods: [post] }
bodyLimit: 1MiB
batch: { maxIds: 200, bodyLimit: 2MiB, budget: 150ms }
cache:
families:
minute: 60s
five_minutes: { maxAge: 300s, sharedMaxAge: 600s, staleWhileRevalidate: 60s }
revalidated: { maxAge: 0s, noCache: true }
restrictedInput: reject
outputEnums: enum
rateLimit: { header: Retry-After, param: retryAfterMs, unit: s }
errors:
every: [api.internal]
derived: { unauthenticated: api.unauthenticated, forbidden: api.forbidden }
Key Meaning Default, or when required
format the wire format (section 13) bare (A30)
pathCase how node names become path segments: kebab, camel or snake kebab
paging.default the paging style of a list without paging (section 14) none: a list with neither is a compile error naming both fixes (A31)
paging.maxLimit the largest limit a request may send none: required when a list uses a style with a limit role and sets no maxLimit
paging.defaultLimit the limit of a request that sends none, and the page size of a style without limit maxLimit; required for a style without limit
paging.overLimit reject (400) or clamp (served as maxLimit): a limit above maxLimit (section 14) reject
idempotency { header, replayHeader, lifetime, methods, required, enforce }: enables the idempotency key on every non-safe route whose method methods lists and that is not exempt (FR-7, A34). methods lists post, put, patch and delete, all four by default, so a POST-only policy is methods: [post]. required is true by default: a request without the key answers 400 from the schemaInvalid role; with false it runs without replay protection. enforce is boundary (the default: the boundary fingerprints the request, stores the final response of every request that reached the handler, a success or an error the handler answered, and replays it; a refusal before the handler is not stored; a key reused with another request answers 422, one still running 409) or none (the key is declared, documented and passed to the handler, for an app whose gateway or library already handles replay) unset: no key is derived, and an idempotency.exempt on a route the key does not reach is a compile error naming the unused reason
bodyLimit the default request body limit 1MiB
batch { maxIds, bodyLimit, budget } for batch ops maxIds required when a batch op sets none; bodyLimit from bodyLimit; no budget
cache.families freshness families: a max-age (60s), or { maxAge, sharedMaxAge, staleWhileRevalidate, staleIfError, immutable, noCache, mustRevalidate }, each key a Cache-Control directive in camel case (noCache: true writes no-cache, for a response always revalidated with its ETag) empty: each family a cache.freshness names is required
cache.sensitiveElsewhere noStore or refuse: a response carrying a sensitive value outside a field of its success body, which is a whole scalar body or its items, a map key, a response header’s type or an error code’s params; noStore makes it no-store as a sensitive field is (section 10), every response of that error code included, refuse is a compile error naming the value and each route answering it, or the error code’s params noStore
restrictedInput reject or strip: a restricted field sent without its right (A21); overridable per field (ADR 0003) reject
outputEnums enum or open: how the emitter writes an output enum (FR-29) enum
pathVersion prefix or none: whether every surface’s path starts with /v{version} (section 6); a surface’s pathVersion overrides it prefix (A33)
query { lists, objects, unknownKeys }: how a query list and an object field travel, and whether an undeclared key is refused (section 7) { lists: repeat, objects: explode, unknownKeys: refuse }
input.unknownKeys refuse, { ignore: "<reason>" } or { keep: "<reason>" }: the api’s policy for unknown keys of body inputs; an unknownKeys on a schema use wins (ADR 0003 section 4) refuse
int64 string or number: the wire form of an int64 value (ADR 0003 section 8) string
deprecation { header, sunsetHeader, successorRel }: the names of the deprecation headers (section 10); successorRel is an RFC 8288 registered relation name (a lowercase letter, then lowercase letters, digits, . and -), and an extension relation URI is refused; the two header names differ Deprecation, Sunset, successor-version
ops per op name, { method, path, status }: the defaults of the ops table (section 6) the table’s values
operationIds { order: verbNoun | nounVerb, case: camel | snake }: how operation ids are derived (section 6) { order: verbNoun, case: camel }
rateLimit { header, param, unit }: the header written on a 429 from a bucket rule, carrying the denial’s integer millisecond param in unit (s, rounded up, or ms) unset: a 429 carries no retry header
degradedHeader the header listing degraded parts unset: required for a degradable route whose format has no degraded meta field
urls relative or absolute: how the URL slots and roles are written (section 13) relative
rights.pattern a pattern every right name must also match (section 2) unset: the core pattern only (A20)
service.allowPublic true: a service api (api.kind: service, section 2) may declare public or optionalAuth routes, which then run without the caller identity; false refuses them false
access.guestRule refuse or allow: a right required on a public or optional-auth route needs a guest reason (section 2); with allow it needs none refuse
errors.pattern a pattern every error code must also match, the dotted family.name form for example ('^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$') unset: the core pattern only (A36)
errors.every codes attached to every operation (section 11) empty
errors.reach subtree or resource: how far a code attached on a node reaches (section 11) subtree (A35)
errors.requireCode false: a format error body may write no $code (section 11); true refuses it true; the server targets and the fetch client refuse a codeless format by name until one models it
errors.derived role bindings (section 11) the default codes of ADR 0006 section 6 (A34)

The core imposes no wire format (A30). format: bare, the default, is itself written as format data below: every success body is the bare payload, and an error is an RFC 9457 application/problem+json body whose members are fixed in ADR 0006 section 7. bare has no paging style, because the core imposes no page shape either (A31, section 14). Every other format (a house envelope, JSON:API, HAL, JSON-LD and Hydra, Laravel resources and paginators, Spring Page and ProblemDetail) is declared in formats, usually by a convention pack, and selected with conventions.format; each target maps it onto its framework’s own mechanism (FR-42, SCN-048, SCN-100).

# packs/house.yaml (excerpt)
formats:
acmeEnvelope:
media: application/json
success:
item: { body: { data: $payload, meta: $meta } }
table: { body: { data: $payload, meta: $meta } }
list: { body: { data: $payload, meta: $meta } }
page: { body: { data: $items, meta: $meta } }
meta:
servedAt: { fill: now }
degraded: { fill: degraded }
error:
body: { error: { code: $code, nature: $nature, params: $params, traceId: $traceId } }
Key Meaning
media the media type of success bodies; application/json by default
absent what a slot with no value writes: omit (default) or null
success.item, success.table, success.list, success.page one per response shape: { body, headers }. body is a template, $payload by default ($items for page); headers maps a header name to a template. item is one record or any non-list body, table the id-keyed object of a batch read, list an unpaged list, page a paged list, whose paging style adds members to it or replaces its body (section 14)
meta the meta fields, each { fill, schema }: fill is now, traceId, degraded, a principal.<field> source, or handler; handler and principal.<field> then need schema
error { media, body, headers }; media defaults to the format’s. A format without error uses the bare error. The body must write $code, as a member or inside a text template ('https://errors.acme.com/${code}'), so a client can tell the code; a format whose error writes no $code is a compile error naming the format, unless conventions.errors.requireCode is false

A shape left out uses its default (body: $payload, or $items for a page, and no headers), so bare writes only its error.

A template describes a JSON value the boundary writes. The same language serves format shapes, paging styles (section 14) and error-role params (section 11).

Written Meaning
$name (the whole string) a slot: the value the boundary fills, with its own JSON type
$name.member a member of an object slot ($item.taskId, $issue.pointer)
${name} inside a longer string the slot’s value as text ('/contexts/${resource}', '${status}')
$$ a literal $ ($$ref writes $ref)
a mapping an object; each key is a member name, written literally
the key $merge: <slot> spreads an object slot’s members beside the others; a member defined twice is a compile error naming both
a key that is a name slot ($collection) the member name is the slot’s value; a literal member of the same name in that object is a compile error naming both
{ $each: <slot>, $as: <template> } an array: $as applied to each element of an array slot; inside it, $item is the element
a sequence an array of templates
any other scalar a literal

A member whose value is a slot with no value is omitted, or written null when absent: null; an object whose members are all omitted is omitted in turn, so next: { href: $next } disappears on the last page. $merge takes an object-typed slot only, and a payload that is not an object fails to compile under a format that merges it; $each takes an array-typed slot only. A table’s id-keyed payload is an object; its members are ids, so no member clash is checked. Any other $ in a string is a compile error; write $$ for a literal $. A key that starts with $ is a name slot or $$…; any other is a compile error. $collection is the only name slot; every other slot, text or number included ($status, $id, a URL or a cursor), names no member.

The slots are a closed set:

Slot Value Where
$payload what the handler returns: the item, the id-keyed table, or the array of an unpaged list success shapes
$meta the object of the format’s meta fields; in success.page, joined by the meta members of the paging style (section 14) success shapes, paging styles
$items the page’s records, as the handler returns them success.page, paging styles
$status the HTTP status everywhere
$self the URL of what the body describes: the member URL (built from $id) when the body is the collection’s item, which has the id property; the current page of a paged list, the request URL otherwise everywhere
$path the request path, without the query everywhere
$resource the item schema’s typeName (ADR 0003 section 6), or its name (Task) success shapes, paging styles
$collection the collection’s or single’s resourceType, or its node name (tasks); also a member name; in item, only on an operation that belongs to a collection or single item, table, list, paging styles
$id, $fields the record’s id, and the record without its id member item
$item, $itemId, $itemFields, $itemSelf inside $as: the element, its id, the element without its id member, its member URL $as
$code, $nature, $title, $detail, $instance, $traceId, $params the error’s code, nature, written title (a slot with no value when the code has none: a member holding only $title is then left out, or written null under absent: null), optional text, optional occurrence reference, optional trace id, and its params object error
$<role> a paging response role (section 14) or an error-role datum (section 11) paging styles, role params

$id, $fields, $itemId, $itemFields and $itemSelf read the item property named by the collection’s idProperty, or else named like its id parameter (taskId); a format that uses one on an operation whose item lacks that property fails to compile, naming the operation and the property. The batch table is keyed by the same property. The URL slots ($self, $itemSelf and the URL roles of section 14) are relative references by default; conventions.urls: absolute writes absolute URLs, each starting with one public origin (scheme, host, optional port) configured on the target, never the request’s Host or forwarded headers; a target without one refuses to generate or to start, naming the setting. bare (format) is reserved: a declared bare replaces the default. The names never (cache family) and none (paging style) are reserved, and so are the portable format names in scalars (ADR 0003 section 8).