The client_id / client_secret pair a partner backend authenticates with.
The server-side identity in the API access chain.
A Client is an OAuth-style identity pair — client_id plus client_secret — that a partner's server presents when it exchanges credentials for an application token. It is one link in the API access chain: the request's Origin resolves exactly one API key through its bound Webhook entry, and that key binds (each 1:1, each optional until wired) the Client that authenticates the partner's backend, the Certificate that signs its consumer tokens, and the Consumer Contract that governs its consumers. A client does nothing on its own — it earns its keep the moment an API key binds it.
Fully bound is the rule to keep in mind: an API key works for its partner only when a client, a certificate, a contract and a webhook entry are all wired. A key without a client is fully visible in the admin UI so you can finish wiring it — but its partner cannot reach the platform through it.
kcid_ followed by a random identifier. Public-ish: it identifies the client in the token exchange and in the certificate-code handshake. It is safe to log and reference — only the secret is secret.ksec_ followed by a long random value. Shown exactly once at creation and once per rotation; afterwards only an 8-character display prefix remains visible. There is no recovery path — a lost secret means rotate.1–100 characters, unique.20 clients, checked on create only. Creating past the cap returns 400 with a "Reached the maximum number of …" message — delete one first. The cap keeps the credential surface small enough to audit at a glance.ksec_ + the first 8 characters of the random part — the only fragment lists and detail pages ever show after the reveal.Where the client_id / client_secret pair actually gets used.
The partner's server exchanges its client credentials for an application token — a long-lived signed token that fronts the public partner API. The three secrets are packed into one base64 string sent in the Authorization header; client_id rides the query string. There is no request body.
POST /v1/auth/application_token?client_id=kcid_…Origin — an allow-listed browser Origin, checked before anything else. Authorization — the bare base64 of client_secret | api_key_id | api_key_secret, with no Bearer / Basic prefix.client_id — the kcid_… Client id in the clear, which selects the secret to verify against (required). No request body.Request
POST /v1/auth/application_token
POST /v1/auth/application_token?client_id=kcid_… Origin: https://app.yourcompany.com // must be allow-listed — checked first Authorization: <base64( client_secret | api_key_id | api_key_secret )> // bare base64 — no "Bearer" / "Basic" prefix // no request body // client_secret the raw ksec_… plaintext // api_key_id the API key's id // api_key_secret the raw kak_… plaintext
Response
200 — token minted
200 OK { "success": true, "status": 200, "message": "Success", "result": { "type": "Bearer", "access_token": "<signed token — send as Bearer on the partner API>" } }
On success the token is returned at result.access_token as a Bearer credential valid only for the partner API — it can never be used as an admin credential. It has no expiry: revocation is by state (delete the API key, or break the chain), not by clock, so cutting a partner off is immediate and does not wait for anything to time out.
Failure modes
Origin is not on the allow-list. The connection is dropped before any authentication — no status, no body — so through a gateway it surfaces as a 502. An unknown caller never even learns the endpoint exists. Recover by calling from an allow-listed Origin.client_secret, wrong api_key_secret, unknown client_id, the API key not bound to that client, a malformed triple, or a half-wired key (certificate / contract / webhook not all bound). It is also returned when client_id or Authorization is missing. The caller is never told which part failed — a failed guess reveals nothing. Recover by re-checking all three credential parts and confirming the key is fully wired (an operator task, not a partner-side bug).The client_id also drives the certificate-code handshake: the partner requests a one-time code with its client_id, and KeenAgents re-verifies the API key ↔ client binding before delivering the certificate PEM to the key's webhook URL.
api-keys for read, api-keys:w for write.ksec_ display prefix is visible anywhere.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.
A client's detail screen — its client_id, the masked secret display prefix, and the Rotate action.
500 Invalid request every other credential problem returns. The admin list still shows the key as a partial row — the admin view is the only place to see and finish partial wiring.Keen Agents 2026
Documentation
Release 15