Docs

Certificates

Per-tenant signing certificates — your partner verifies consumer tokens with the public PEM; the private half never leaves the platform.

Overview

What a certificate is and why every partner chain needs one.

A certificate is the per-tenant signing identity of the API access chain. KeenAgents generates the keypair for you: the platform signs your partner's consumer tokens with the private half, and the public certificate (PEM) is what your partner application uses to verify them. A certificate is bound 1:1 to at most one API key, and it is one of the four bindings a key needs to be fully bound: until a certificate is bound, the key's Origin never joins the partner-facing allow-list, and an application-token mint is refused with api_key has no certificate bound.

Visibility is split on purpose: the public PEM is always visible in the admin UI, while the private key is never displayed and can never be read back through any API or UI surface — that guarantee is what makes the public PEM safe to share. Certificates are self-issued for your tenant with a one-year validity, so partners should trust the PEM they receive directly (pin it) rather than expect a public certificate-authority chain, and you should plan a rotation before expiry.

private key never leaves the platformpublic PEM always visiblerotation keeps the binding

Anatomy of a Certificate

Key material
A signing keypair generated entirely by KeenAgents — you never upload key material. Expect the create and rotate actions to pause for a few seconds while the new keypair is generated.
Public certificate
The PEM is always shown on the detail page for copy/paste. Partners do not download it from an API — they receive it through the certificate-code handshake on their webhook (see below), so delivery always lands on the URL your organization registered.
Private key
Held by the platform and used only to sign your partner's consumer tokens. It is never displayed, never delivered to anyone, and can never be read back through any API or UI surface — if it must change, rotate; there is no export.
Secret key
An optional admin-supplied shared secret (16–32 characters) used to encrypt consumer material that both KeenAgents and your partner must be able to read. Because both ends need the same value, it stays visible on the detail page — recoverable by design, unlike reveal-once secrets. It is an independent credential: rotating the keypair does not change it, and vice versa.
API key binding
Set from the API key side via the "Pick a certificate" dropdown; a certificate can be bound to at most one API key. Deleting a certificate does not delete the key — the key simply loses its certificate binding and silently stops being fully bound until a new certificate is bound.

Rules & Limits

  • Name: required, 1–100 characters, unique.
  • Org-wide cap: 20 certificates, checked on create only. Past the cap the create fails with 400 and a "Reached the maximum number of …" message — delete one first.
  • Secret key: 16–32 characters after trimming. Submitting '' (empty) is valid and clears the value; omitting the field on update leaves it untouched.
  • Rotation replaces only the key material and the public PEM. The certificate keeps its identity, its API key binding, and its shared secret key — so nothing needs re-wiring after a rotate.
  • Delete permanently destroys the certificate and its private key, and unbinds it from the API key. There is no undo and no recovery of the key material.
  • Permissions: the single api-keys flag covers certificates — api-keys for read, api-keys:w for write, shared with API keys, Clients, Consumer Contracts and Webhooks.
  • Propagation: changes take effect immediately — a create, rotate, or delete is live across the platform right away.

Rotation & Deletion

Rotate swaps the keypair in place; delete tears the whole thing down.

  1. Admin triggers Rotate on the certificate detail page.
  2. KeenAgents generates a fresh keypair (expect a pause of a few seconds). The certificate's identity, its API key binding, and the shared secret key are all unchanged — only the key material and the public PEM are new.
  3. The old private key is destroyed the moment the new one exists — there is no two-key overlap window. Tokens signed with the old key remain verifiable only by parties still holding the old public cert.
  4. The admin sends the partner a refresh notice — the Wake action on the bound API key — and the partner re-runs the code handshake to fetch the new PEM.
Rotation is destructive — coordinate with the partner
The old private key is wiped immediately, with no overlap window. The partner keeps verifying with the old public cert until it re-fetches, so treat the refresh notice as the start of the handover, not the end.

Delete (as opposed to rotate) permanently destroys the certificate and its private key, and unbinds it from the API key — the key stops being fully bound, so its Origin drops off the partner-facing allow-list and the partner loses access until a new certificate is bound.

Delivering the PEM & Shared Key

Partners receive the certificate out-of-band, via the code handshake.

  1. The partner requests a certificate code from the public API with its client_id and receives a one-time code — valid for 120 s, single-use.
  2. The partner POSTs the code back; it is consumed on claim — a second claim with the same code fails.
  3. KeenAgents re-verifies that the API key belongs to that client before releasing anything (defense-in-depth).
  4. A webhook POST delivers the public PEM plus the shared secret key to the key's webhook entry notification URL.
  5. The partner stores the PEM (to verify consumer tokens) and the shared key (to decrypt sealed material). After a rotation, the refresh notice prompts the partner to run the handshake again.

the redeem webhook

POST <the key's webhook entry notification URL>
{
  "synch-data": "r_cert",
  "result": { "cert": "<public PEM>", "key": "<shared secret or ''>" },
  "type": "resource"
}
The only channel for the shared secret
This redeem webhook is the only path that delivers the certificate's shared secret key to a partner — it always lands on the webhook URL your organization registered, never on a caller-chosen address. Note the delivery is one-shot and not retried: if the partner's endpoint is down it misses the POST, and the partner recovers by running the handshake again.

Certificate-Code API — Request, Response & Failures

The two HTTP calls behind the handshake: issue a one-time code, then claim it. Both carry the partner's application token.

Every call is gated on an allow-listed Origin and the partner's Authorization: Bearer <application_token>. The client_id query must equal the client the token was minted for.

GET /v1/resources/certificate — issue a code

Method & path
GET /v1/resources/certificate?client_id=kcid_…
Headers & body
Origin (allow-listed) and Authorization: Bearer <application_token>. Query client_id required; no request body. A re-issue against the same key overwrites the previous code and restarts the 120 s window.

200 — code issued

200 OK
{
  "success": true,
  "status": 200,
  "message": "Success",
  "result": {
    "code": "<one-time code — URL-safe, ~43 chars, single-use, valid 120 s>"
  }
}

Failure modes — GET

  • No response / 502. This route is a silent surface: an unlisted Origin, a missing or unknown client_id, a missing/invalid/expired token, a revoked key, or a client_id that doesn't match the token — every failure drops the connection with no status and no body, surfacing as a 502 through a gateway. A probing scanner never even confirms the route exists. Recover by fixing the credential and retrying; the specific cause is never disclosed.

POST /v1/resources/certificate — claim the code

Method & path
POST /v1/resources/certificate?client_id=kcid_…
Headers
Origin (allow-listed), Authorization: Bearer <application_token>, and Content-Type: application/json.
Body
code — string, required: the code returned by the GET issuance. Extra fields are rejected.

POST /v1/resources/certificate

POST /v1/resources/certificate?client_id=kcid_…
Origin: https://app.yourcompany.com            // must be allow-listed
Authorization: Bearer <application_token>
Content-Type: application/json
{
  "code": "<the code returned by the GET issuance>"
}

200 OK
{ "success": true }              // the redeem webhook fires server-side, in parallel

On 200 { "success": true } the code is consumed and the redeem webhook is dispatched to your registered URL in parallel — the success reply means the delivery went out.

Failure modes — POST

no response / 502
The Origin is not allow-listed — the connection is dropped before authentication and surfaces as a 502 through a gateway.
401
Missing or malformed Authorization header, or an invalid / revoked token — Invalid token. Also returned (as { "success": false }) when the client_id is missing or doesn't match the token, when the code is wrong, expired, or already claimed, or when the API key ↔ client binding no longer holds. Codes are single-use, so a replay always fails — recover by requesting a fresh code with a GET and claiming that.
400
The body is missing code, sends it empty, or includes unknown fields — rejected with the validation reasons. Send exactly { "code": "…" }.

In the Admin UI

  • Certificates live as a sub-page of the API Keys section, with list, create, and detail pages — gated by the shared api-keys / api-keys:w permission.
  • Unlike API keys and client secrets, nothing here is reveal-once: the public PEM is always visible on the detail page (textarea + copy button), and Rotate reveals the new PEM.
  • The create and detail forms include the optional shared secret-key field (16–32 characters) with a Generate button; the value is displayed on the detail page — it is recoverable by design.
  • Delete is a confirmed dialog; the private key is destroyed along with the certificate.
  • Binding happens on the API key form via the "Pick a certificate" dropdown, which offers unassigned certificates plus the currently bound one on Edit.
  • A Rebuild Resource Cache button on the detail page rebuilds this certificate'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.
Certificate detail page

A certificate's detail screen — the always-visible public PEM, the optional shared secret key, and the Rotate and Delete actions.

Previous

Clients

Next

Contracts

Keen Agents 2026

Documentation

Release 15