Capabilities

How an App declares its public API — the capability key, the route served under /v1, and the difference between defining a contract and implementing someone else's.

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.

manifest.capabilities.json
{
  "type": "define",
  "capability": "inventories.stock.list",
  "version": "1.0.0",
  "route": { "method": "GET", "path": "/inventories/stock" }
}
Request
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 doesThe path does
Identifies the contract for all timeIdentifies the resource to a caller
Becomes the operation id, so it becomes the generated SDK method nameBecomes the URL under /v1
Is what another app names in a capability grant or an implement entryIs what a curl hits
Is dotted namespace.nameIs 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.

manifest.capabilities.json
[
  {
    "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

text
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

Was this page helpful?