Capability reference
The field-by-field reference for one entry in manifest.capabilities.json. The model behind it — the key, the route, define versus implement — is Capabilities.
Field summary
| Field | Type | Applies to | Description |
|---|---|---|---|
type | define | implement | both | Required. Which of the two this entry is. |
capability | string | both | Required. Dotted namespace.name key, e.g. serials.list. The operation id. Not part of the URL. |
version | string | define | Required for define. Contract semver. |
compatible | string | implement | Required for implement. Semver range the implementation satisfies, e.g. ^1.0. |
route | object | define | Required for define. { method, path }. Path is relative to /v1. |
parameters | array | define | Path, query and header parameters. |
request | object | define | JSON Schema for the request body. |
response | object | define | Required for define. JSON Schema for the response body. |
responses | object | define | The HTTP statuses this operation answers, keyed by status code. |
summary | string | define | One-line summary. Becomes the OpenAPI operation summary. |
description | string | define | Longer explanation. Becomes the OpenAPI operation description. |
tag | string | define | Which group the operation is published under. Must name a tag declared in the manifest's top-level tags. |
binding | object | both | { function }. Only needed when the app exposes more than one function. Usually omitted. |
config_schema | object | both | JSON Schema for per-tenant configuration passed to the implementation. |
cache | object | define | Gateway response-cache policy. Absent means never cached. |
version
Semver for the contract, not for the app. It is what another app's compatible range is matched against, and what makes a published SDK safe to pin.
Versioning is strict-forward: a contract may gain optional fields and new statuses; it may never change the meaning of what it already published. A breaking change is a new major, a new capability key and a new route. See Versioning.
route
"route": { "method": "POST", "path": "/serials/{id}/sold" }
method is one of GET, POST, PUT, PATCH, DELETE. path is the resource path without the /v1 prefix and without a namespace prefix of its own. Path parameters use {braces}, matching the router's own template syntax.
A path may not use a reserved core segment: search, storage, auth, messaging, functions, imports, exports.
parameters
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "string", "format": "uuid" },
"description": "The serial."
},
{
"name": "limit",
"in": "query",
"required": false,
"schema": { "type": "integer", "minimum": 1, "maximum": 200, "example": 50 },
"description": "Page size (default 50, max 200). A larger value is clamped rather than refused."
}
]
in is path, query or header. schema is plain JSON Schema — everything you put in it, including example, reaches the published document and the API Explorer.
Declare every parameter your handler reads. An undeclared query parameter is not part of the contract, which is how a paged list ends up stranding every caller on page one — see Pagination and filtering.
Declare pagination parameters on paged lists only. A get, create, update or delete has no use for them and declaring them anyway publishes a lie.
request and response
Both are plain JSON Schema for the body. Give each a title: the gateway hoists a titled schema into components.schemas, which means the generated SDKs get a named type instead of an inline anonymous object.
"request": {
"title": "SerialCreateRequest",
"type": "object",
"required": ["serial", "product_id"],
"properties": {
"serial": { "type": "string", "description": "Manufacturer serial, unique per tenant." },
"product_id": { "type": "string", "format": "uuid", "description": "The product this device is." }
}
}
Per-field descriptions are worth the keystrokes — they are the only documentation a partner reading the API Explorer will ever see for that field.
The gateway validates a live request body against request before your function is invoked. So the declared schema must never be stricter than what your handler actually requires: a required field your code treats as optional turns a working request into a 400 your code never sees. Align required, enum and anyOf with the guards your handler really enforces.
responses
Optional, and the one field most worth adding. Without it, the published contract claims a bare 200: no create declares its 201, no unique index declares its 409, no unknown id declares its 404, and a generated SDK has no case for any of them.
"responses": {
"201": { "description": "Created. The body is the stored serial." },
"404": { "description": "No serial with that id, or it belongs to another tenant." },
"409": { "description": "That serial is already registered for this tenant." }
}
Each entry currently carries only a description, and that description should be written in the caller's terms — "the serial is already registered", not "Conflict".
Two rules govern how these merge into the published document:
- The gateway already declares the statuses every capability can answer —
401,403,429,502, and400wherever there is a request body. You do not repeat those; a status you declare here keeps your wording and the standard ones fill in the rest. - Declaring any
2xxreplaces the assumed200and movesresponseonto the status you declared. A create that answers201stops advertising a200it never returns. So declare every success status the operation can answer, not just the unusual one.
See Errors for the statuses your handler produces.
summary and description
summary is the one-line label. It becomes the OpenAPI operation summary — the line in the API Explorer's list and the doc comment on the generated SDK method. Keep it under 255 characters; a longer one is rejected at publish time.
description is the longer prose, for anything a caller needs to know that the schemas do not say: an ordering guarantee, an idempotency property, a side effect.
Write both as if a partner is reading them without you in the room, because that is exactly what happens.
tag
Which group the operation is published under in the OpenAPI document. It must name a tag declared in the manifest's top-level tags; naming an undeclared tag is rejected at publish time. Declaring none is the normal case. See Tags.
config_schema
JSON Schema for per-tenant configuration handed to the implementation of this capability. It is the mechanism for a contract that needs a knob per tenant without that knob being a request parameter — an external endpoint, a rounding mode, a feature switch.
"config_schema": {
"type": "object",
"properties": {
"rounding": { "type": "string", "enum": ["up", "down", "nearest"], "default": "nearest" }
}
}
It applies to both define and implement, which is what lets an override declare the configuration it needs without changing the contract every caller sees.
For anything a merchant should be able to change in the Cockpit, use settings.json instead — that is a form, with validation and per-market resolution, and config_schema is not.
binding
"binding": { "function": "reports" }
Names which of the app's functions serves this capability. Only needed when the app exposes more than one; omit it and the app's primary function is used. Most apps never write it.
cache
An optional gateway response-cache policy. No block means the capability is never cached. Set it only on read-safe capabilities; writes must omit it. Full field reference in Caching.
A complete entry
{
"type": "define",
"capability": "serials.create",
"version": "1.0.0",
"summary": "Register a device serial",
"description": "Adds a serial to the registry. Idempotent on the serial: registering an existing serial answers 409 rather than creating a duplicate.",
"route": { "method": "POST", "path": "/serials" },
"request": {
"title": "SerialCreateRequest",
"type": "object",
"required": ["serial", "product_id"],
"properties": {
"serial": { "type": "string", "minLength": 1, "description": "Manufacturer serial, unique per tenant." },
"product_id": { "type": "string", "format": "uuid", "description": "The product this device is." },
"status": { "type": "string", "enum": ["in_stock", "sold", "in_service"], "description": "Defaults to in_stock." }
}
},
"response": {
"title": "Serial",
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"serial": { "type": "string" },
"product_id": { "type": "string", "format": "uuid" },
"status": { "type": "string" },
"created_at": { "type": "string", "format": "date-time" }
}
},
"responses": {
"201": { "description": "Created. The body is the stored serial." },
"409": { "description": "That serial is already registered for this tenant." }
}
}
Next steps
- Capabilities — the model.
- Errors — the statuses to declare.
- Caching — the
cacheblock. - The published OpenAPI document — where all of this ends up.