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.
Paths shown as /v1/… are reached at https://<your KeenAgents host>/keen-api/… — the /v1 prefix is replaced by /keen-api.
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 (the Api Key ID on its detail page) // 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 and no refresh: 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. If a token stops verifying, simply request a new one.
Failure modes
Origin is not on the allow-list. The connection is dropped before any authentication — no status, no body — so on your side it surfaces as a connection reset or a generic proxy error. 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.
ksec_ display prefix is visible anywhere.r_api_keys). If the partner must also receive its certificate material again, press Rebuild Resource Cache and then Wake on the key. A missed notice is recovered on the partner's next fetch.list_clients, get_client, create_client, update_client, rotate_client, delete_client — same scope, same rules.
A client's detail screen — the key holding it, the masked secret prefix, its name and client id, the Save · Rebuild Resource Cache · Delete footer, and the Rotate Secret card below.
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