The partner credential at the center of the access chain — issued once, shown once, rotatable.
One record links everything a partner needs.
An API key is the core record of the API access chain. The raw key — it starts with kak_ — is itself the Bearer credential for direct API calls, with no token-exchange step in between. That is what makes revocation immediate: deleting a key cuts access right away. KeenAgents keeps only an irreversible hash of the key; lists and detail pages show a display prefix (kak_ plus the first 8 characters), never the full key.
Beyond being a credential, the key is the chain's hub: it binds four sibling resources, each 1:1 and each set through a picker on the key form — a Client (the partner server's identity pair), a Certificate (the tenant's signing material), a Consumer Contract (the per-tenant policy), and a Webhook entry (the named pairing of the partner's browser Origin and its notification URL). Because webhook origins are unique, an inbound request's Origin header always resolves to exactly one key. The other chain members have their own pages; this one covers the key itself.
Fully bound =
client boundcertificate boundcontract boundwebhook boundExact formats and caps you will hit in the forms.
kak_ followed by a long random secret. After creation only the display prefix (kak_ + first 8 characters) is ever shown again.1–100 characters, unique.null unassigns, and omitting the field leaves the current binding unchanged. The partner's Origin and notification URL live on the bound Webhook entry, not on the key.20 API keys, checked on create only — past the cap the create fails with a Reached the maximum number of … message; delete a key first. Keys have no owner account; there is no per-account allocation.Rotate replaces the credential in place; delete revokes everything.
kak_) usage. Application tokens do not expire on a clock, so the only per-key lever that revokes them is DELETE — or unbinding the key's certificate or consumer contract so the chain check fails. Plan revocation around state, not time.Wake is notify-then-fetch: the notice carries no data. These are the two authenticated reads the partner makes to pick up what changed.
Both reads carry the partner's Authorization: Bearer <application_token> and are scoped to the caller's own API key — the token's subject — so a partner only ever reads its own tenant's configuration. There are no query parameters and no request body; the request must still arrive from an allow-listed Origin.
GET /v1/resources/consumer-contract — the tenant's master spaces
Returns the API key's bound consumer contract: its name, its partner-shared key, and the master spaces the tenant may offer. result is null when the key has no contract bound.
200 — consumer contract
GET /v1/resources/consumer-contract Authorization: Bearer <application_token> 200 OK { "success": true, "status": 200, "message": "Success", "result": { "id": "<consumer contract id>", "apiKeyId": "<this api key's id>", "name": "Acme production", "key": "<19-char partner-shared tenant key>", "createdAt": "2026-01-04T10:22:31.000Z", "spaces": [{ "id": "<space id>", "title": "Support" }], "outsideRegistration": false, "otpInsideRegistration": false, "otpOutsideRegistration": false, "blockOutsideRegistrationLogins": false, "blockSystemRegistrationLogins": false } } // result: null when the key has no contract bound. // The policy booleans here are mapped DEFAULTS, not the live values — // read /v1/resources/consumer-policy for the authoritative ones.
GET /v1/resources/consumer-policy below.GET /v1/resources/consumer-policy — the live policy
The fetch leg of a r_consumer_policy Wake. Returns the live five registration/login booleans plus the two default-space subsets a new consumer is seeded with. result is null when the key has no contract bound.
200 — consumer policy
GET /v1/resources/consumer-policy Authorization: Bearer <application_token> 200 OK { "success": true, "status": 200, "message": "Success", "result": { "id": "<consumer contract id>", "key": "<19-char partner-shared tenant key>", "outsideRegistration": true, "otpInsideRegistration": false, "otpOutsideRegistration": true, "blockOutsideRegistrationLogins": false, "blockSystemRegistrationLogins": false, "defaultSystemSpaces": ["<space id>"], "defaultOutsideSpaces": ["<space id>"] } } // result: null when the key has no contract bound. // These booleans are the LIVE policy values.
Failure modes (both reads)
Origin is not on the allow-list. The connection is dropped before authentication — no status, no body — surfacing as a 502 through a gateway. Call from an allow-listed Origin.Authorization header, or an invalid token — Invalid token. Also returned when the token's API key was deleted, rotated, or re-bound to a different client since the token was minted (the binding check fails). Recover by re-minting an application token against the current key.api-keys for read, api-keys:w for write — one role grant unlocks keys, Clients, Certificates, Consumer Contracts and Webhooks together.kak_ display prefix — never the full key.cache read, cache:w write), separate from the edit permission, so a cache manager can refresh it while an editor without cache rights never sees the button. You rarely need it — normal saves refresh the copy automatically. See Resource Cache.
An API key's detail screen — its name and display prefix, the four resource bindings, and the Rotate, Delete and Wake actions.
500 Invalid request every credential problem returns. The specific reason (for example api_key has no certificate bound) is recorded server-side only, never disclosed to the caller — it is an operator wiring task, not a partner-side outage.scheme://host[:port], taken from the key's bound Webhook entry: the port matters (browsers omit only the default ports 80/443) and wildcards are not supported. A rejected Origin produces no error at all — KeenAgents simply drops the connection, so the caller sees a connection reset or a generic gateway error; this is deliberate, so an unknown caller learns nothing from the refusal.Keen Agents 2026
Documentation
Release 15