Capabilities
A capability is one typed HTTP operation your app contributes to the platform. Capabilities are how an app has a public API at all: nothing your function answers is reachable from outside until a capability declares it.
They live in manifest.capabilities.json, a sibling of manifest.json that the platform splices into manifest.capabilities before validation. An app ships either the inline array in the manifest or the sibling file, never both. Every shipped app uses the sibling file, because the capability array is by far the largest thing in a manifest.
The authoritative schema is at https://schemas.revenexx.com/manifest.capabilities.schema.json; each item reuses the capability definition from https://schemas.revenexx.com/manifest.schema.json.
The file is generated, not hand-written. revenexx apps capabilities --write regenerates it from schema.json and your routes, and the scaffolded npm run capabilities emits the same output. Hand-edits to the array are lost on the next run — change the generator script, not the file.
The route is the public surface
A capability's declared route is served verbatim under the gateway's /v1 prefix.
{
"type": "define",
"capability": "inventories.stock.list",
"version": "1.0.0",
"route": { "method": "GET", "path": "/inventories/stock" }
}
curl "https://api.revenexx.com/v1/inventories/stock?limit=50" \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..."
There is no per-app URL prefix, no function id in the path, and no separate host. path plus /v1 is the URL.
The key is not the URL
The capability key — inventories.stock.list — is the stable identifier and the OpenAPI operation id. It is not part of the URL. Path and key are allowed to diverge, and often do.
| The key does | The path does |
|---|---|
| Identifies the contract for all time | Identifies the resource to a caller |
| Becomes the operation id, so it becomes the generated SDK method name | Becomes the URL under /v1 |
Is what another app names in a capability grant or an implement entry | Is what a curl hits |
Is dotted namespace.name | Is a slash path with {braces} for parameters |
Two consequences:
- A caller cannot construct the key from the URL, or the URL from the key. Both are published in the OpenAPI document; nothing else should be inferring either.
- Renaming the path is a breaking change and so is renaming the key. Neither is an edit you make to a live capability — see Versioning.
Keep the key hierarchical and the path RESTful, and let them agree where they naturally can. locations.stock.list at GET /locations/{location_id}/stock reads well from both sides.
define and implement
Every entry has a type.
define introduces a new contract: this capability did not exist, and this app is its author. A define must carry version, route and response.
implement fulfils a contract another app already defined. It must carry compatible — the semver range of the contract it satisfies — and it does not redeclare the route, because the route belongs to the contract.
[
{
"type": "define",
"capability": "serials.list",
"version": "1.0.0",
"route": { "method": "GET", "path": "/serials" },
"response": { "title": "SerialList", "type": "object" }
},
{
"type": "implement",
"capability": "inventories.availability",
"compatible": "^1.0"
}
]
Almost everything you write is a define. implement is the override mechanism, and it has its own page — it is the reason the app model exists, but it is not how you publish your own operations.
Everything you declare is published
manifest.capabilities.json ──► /v1/openapi.json ──► SDKs · API Explorer · MCP server
There is no second place to fix a wrong description, a missing status or a bad parameter name. The merged contract at /v1/openapi.json is generated from the capabilities of the apps a tenant has installed, and everything downstream reads it. See The published OpenAPI document.
That is worth taking literally when you write a summary or a field description. They are not comments. They are the only documentation a partner reading the API Explorer will ever see for that operation.
Path rules
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.
Keep every route under a prefix derived from your app, so the gateway's routing and a human's reading of the URL agree. Routes are lowercase.
Two runtime rules that bite
Always send a body on a POST whose capability declares a request schema. Send {} if you have nothing to say. A POST with no body at all fails the gateway's contract validation before your function is invoked.
Call your /defaults route explicitly after install. Apps commonly seed their defaults on the app.installed event, but that event does not reliably fire on a Marketplace install. Expose a /defaults capability, make it idempotent, and call it once. Both rules, with the calls to make, are in Verifying live.
Next steps
- Capability reference — every field of an entry.
- The router — the handler behind the route.
- Overriding a capability —
implementin detail. - Calling another app's capability — the outbound direction.
- API usage — headers, errors, pagination, rate limits.
- API Explorer — the merged contract, live.