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.
  • 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.

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
// 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

no response / 502
The 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.
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 live as a sub-page of the API Keys sidebar section, with list, create, and detail pages. A single permission flag gates every chain surface: api-keys for read, api-keys:w for 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 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.
  • After a client change or rotation, use the API key's Wake action to send the partner a refresh notice at its webhook entry's notification URL. The notice carries no data — the partner re-fetches what changed, so a missed notice is recovered on the next fetch.
  • A Rebuild Resource Cache button on the detail page rebuilds this client's served configuration on demand — a shortcut to the same action on the Cache page. It is gated by the cache permission (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.
Client detail page

A client's detail screen — its client_id, the masked secret display prefix, and the Rotate action.

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