Docs

Clients

The client_id / client_secret pair a partner backend authenticates with.

What a Client Is

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.

Api Keys

The core record the client binds to.

Certificates

Per-tenant signing certificates (PEM).

Contracts

Per-tenant consumer policy & spaces.

Webhooks

The partner's Origin + notification URL.

Credentials at a Glance

reveal-once secretstored irreversibly hashedbound from the API key side20 org-wide
client_id
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.
client_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.
Storage
KeenAgents keeps only an irreversibly hashed copy of the secret plus the display prefix — the platform itself cannot re-display or recover it. A secret nobody can read back is a secret a data disclosure cannot leak.
Binding
A client is bound to an API key from the API key side — the "Pick a client" dropdown on the API key form. A client can be bound to at most one API key at a time, so every partner request always resolves to exactly one identity.

Rules & Limits

  • Name: required, 1–100 characters, unique.
  • Org-wide cap: 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.
  • Secret display prefix: ksec_ + the first 8 characters of the random part — the only fragment lists and detail pages ever show after the reveal.
  • When editing an API key, picking a different client re-binds it, clearing the selection unassigns it, and leaving the field untouched keeps the current binding.
  • Deleting a bound client does not delete the API key — the binding is simply cleared. The key silently stops being fully bound and its partner loses access until a client is re-bound.
  • Rotating a client secret retires the old secret at once for new application-token requests — a server still sending the old secret is refused. It does not revoke application tokens already issued: those are tied to the key ↔ client binding, not to the secret, so they keep verifying until the key is deleted, re-bound or unbound. If a leaked secret worries you, rotate it and, to cut existing tokens too, re-bind or delete the key.
  • Changes take effect immediately everywhere on the platform.

The Application Token Exchange

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.

Method & path
POST /v1/auth/application_token?client_id=kcid_…
Headers
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.
Query & body
Query: 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

no response
The 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.
500 — Invalid request
Uniform and opaque on every credential problem — wrong 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.

In the Admin UI

  • Clients are their own entry in the sidebar's Api group, with list, create and detail pages, gated by the Clients scope — read to view clients, write to create, rename, rotate or delete them. Binding a client to a key happens on the API key form and needs API Keys write.
  • The client_secret appears in a one-time reveal panel on create and on rotate; afterwards only the ksec_ display prefix is visible anywhere.
  • Rotate Secret lives in its own card below the form and confirms (Confirm rotate / Cancel) — it mints a new secret in place: same client, new secret, revealed once.
  • Binding happens on the API key form: the "Pick a client" dropdown offers unassigned clients, plus the currently bound one when editing.
  • A client rename or secret rotation automatically sends the bound key's notification URL a refresh notice (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.
  • A Rebuild Resource Cache button in the footer rebuilds the served copy of all clients (the Cache page has no card for clients — this button is the only place to do it). It appears only for roles with Cache write, independently of the Clients scope. You rarely need it — a save refreshes the copy on its own; to refresh everything one partner reads, use Rebuild Resource Cache on its API key (the cascade) and then Wake.
  • Also available to an AI agent through the Keen MCP Server: list_clients, get_client, create_client, update_client, rotate_client, delete_client — same scope, same rules.
Client detail page

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.

Good to Know

Reveal-once is absolute
Only the 8-character display prefix survives creation — the secret itself is never stored in a readable form. If the partner loses its client_secret, rotate the client and hand over the new secret — there is no recovery path.
Unbinding is a silent kill switch
Deleting a client (or unassigning it from its API key) means the key is no longer fully bound: its Origin drops off the partner-facing allow-list, and new token mints fail with the same uniform 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.

Previous

Api Keys

Next

Certificates

Keen Agents 2026

Documentation

Release 15