Docs

Connecting a Site

Wire a partner website to KeenAgents end to end — from credentials to live chat and staying in sync, in order.

What The Admin Sets Up First

The wiring is done in the admin before you write a line of integration code.

Before a site can connect, a KeenAgents admin builds the access chain in this order: the space and the agent your site will run; a Certificate (the signing material, optionally with a shared secret key); a Client (your server's identity — its secret is shown once); a Consumer Contract (your policy, the spaces it offers and the default spaces new consumers get); a Webhook entry (your site's exact browser Origin plus the notification URL on your server); and finally the Api Key, binding all four. When those four are bound the key is fully bound and your Origin is on the allow-list. The admin then hands you three things: your Client id and secret, your Api Key id and secret (both secrets are shown once), and confirmation of the Origin and notification URL registered for you — and presses Rebuild Resource Cache and Wake on the key so your notification URL receives the certificate material and a first refresh notice.

Your site must be served from the registered Origin
Every request carries an Origin header, matched exactly on scheme://host[:port] against the one the admin registered (only the default ports 80/443 are omitted; no wildcards). A request from an unregistered Origin is silently dropped — you see a connection reset or a generic proxy error on your side, never a descriptive one — so an unknown caller learns nothing. If connections fail with no message, check the Origin first.

Automating the Setup with the Keen MCP Server

Everything the admin does above, an AI agent can do through the MCP server — under the same rules.

Everything on this page that an admin does in the console, an AI agent can do through the Keen MCP Server at https://<your KeenAgents host>/mcp. The agent signs in with an MCP account — an id plus a secret shown once — using mcp_login, and then operates as that account, under its role's scopes: create_certificate, create_client, create_consumer_contract / update_consumer_contract_policy / assign_contract_spaces / assign_contract_default_outside_spaces, create_webhook, create_api_key / update_api_key (bind the four), rebuild_api_key_cache, wake_api_key, and the matching list_*, get_*, rotate_* and delete_* tools.

The same rules apply as in the console — the four bindings, reveal-once secrets, a half-wired key refused by rebuild and Wake with a message naming what is missing — and every action is recorded in the audit log under the MCP account. An MCP account can never manage human accounts, roles or other MCP accounts, whatever its role.

Keen MCP Server

Connect an AI agent and drive the platform by tool calls.

MCP Accounts

The headless identity an agent signs in with.

Integration, Step by Step

Server-side first, then the browser — in this order.

  1. Keep your credentials server-side. Your Client secret and API key live on your server, never in the browser. The browser only ever holds a short-lived access token minted for an end user.
  2. Get an application token. Your server exchanges the client credentials — client id, client secret, api key id, api key secret — for an application token, the standing credential it presents on the end user's behalf. This is a two-token, OAuth-style model: the application token stays on the server; the browser gets its own access token later. It has no expiry and no refresh: it keeps working until the admin deletes the key or changes one of its bindings — then simply request a new one.
  3. Complete the certificate handshake. Ask for a one-time code, then claim it. On a valid claim, KeenAgents delivers the certificate material to your notification URL — the public key your site uses to verify KeenAgents-issued tokens, plus the shared secret. It arrives on that URL (not in the claim response), so store it when it lands. If you miss the delivery, or the certificate is later rotated, run the handshake again — or ask the admin to press Wake on your key, which delivers it afresh.
  4. Fetch your resources. With the application token you read your consumer contract, your consumer policy and each agent's front settings — all three scoped to your own key. The space list is different: it returns the organization's whole space collection (every space, with its agents); keep only the spaces named in your contract's spaces — that set is the boundary of what your consumers may use, and a chat run outside it is refused.
  5. Sign in your end users. Register and authenticate your consumers (the people using your site). Where the contract requires it, a one-time passcode step is part of login. Each consumer session is its own two-token pair; self-service covers profile edits, password reset and account deletion.
  6. Open the live chat. Connect over a secure WebSocket to run an agent — name the agent by its slug, send a prompt, and stream the response. Each run is verified against the signed-in consumer's access token.
  7. Stay in sync. Keep the notification URL the admin registered for you reachable. Contract, key, client and certificate changes send a notice there on their own; space, agent and front-settings changes reach you when the admin presses Wake. On receipt, re-fetch the affected resources — the notice carries no data (except the certificate delivery). This keeps your site current without polling.

The Requests, In Shape

Interface-level shapes — every path below is relative to https://<your KeenAgents host>/keen-api (the host is provided at onboarding); the /v1 prefix in the examples is replaced by /keen-api.

1 · Application token (server-side, once — no expiry and no refresh; request a new one if it stops verifying)

POST /v1/auth/application_token?client_id=kcid_…
Origin: https://your-site.example
Authorization: <base64 of "client_secret|api_key_id|api_key_secret">
// client_id rides the query string; the base64 triple rides the
// Authorization header — bare, no "Bearer " / "Basic " prefix. No body.

→ 200 {
  "success": true,
  "status": 200,
  "message": "Success",
  "result": { "type": "Bearer", "access_token": "…" }   // keep this on the server
}

2 · Certificate handshake (issue a code, then claim it)

GET  /v1/resources/certificate?client_id=kcid_…
     Authorization: Bearer <application_token>
     Origin: https://your-site.example
→ 200 { "success": true, "status": 200, "message": "Success", "result": { "code": "…" } }
     // the code lives ~120s and is single-use; a new GET reissues + resets it

POST /v1/resources/certificate?client_id=kcid_…
     Authorization: Bearer <application_token>
     Origin: https://your-site.example
     Content-Type: application/json
     { "code": "…" }
→ 200 { "success": true }
     // on this valid claim the certificate material is POSTed to your
     // notification URL — the public key + shared secret, not in this reply

3 · Fetch your resources (all Bearer + Origin; contract, policy and front settings are scoped to your key — the space list is the whole organization, filter it by your contract)

// every read carries the two headers, and returns the same envelope:
//   Authorization: Bearer <application_token>
//   Origin: https://your-site.example
//   → { "success": true, "status": 200, "message": "Success", "result": … }

GET /v1/space/list                              // every space with its nested agents[] — keep the ones in your contract
GET /v1/resources/consumer-contract             // your contract: identity + the ids of the spaces you may use
GET /v1/resources/consumer-policy               // your live registration / login policy
GET /v1/resources/agent-front-settings?agentId=<slug>   // one agent's partner-facing config

/v1/space/list — the result shape (spaces, agents nested by slug)

{
  "success": true,
  "status": 200,
  "message": "Success",
  "result": [
    {
      "id": "…",
      "createdAt": "…",
      "updatedAt": "…",
      "title": "Support",
      "description": "…",
      "deploymentToken": "",           // present but always empty — deployment tokens are never served here
      "agents": [
        {
          "id": "…",
          "agentId": "billing-help",     // the SLUG — what you name to open a chat
          "displayName": "Billing Help",
          "startFlow": "…",
          "startNode": "…",
          "active": true
        }
      ]
    }
  ]
}

4 · Sign in an end user (consumer login; a passcode step may follow)

POST /v1/auth/access_token
Origin: https://your-site.example
Content-Type: application/json
{ "email": "user@example.com", "password": "…" }

// contract does NOT require a passcode → tokens straight back:
→ 200 {
  "success": true, "status": 200, "statusCode": 200,
  "result": { "access_token": "…", "refresh_token": "…" }
}

// contract DOES require a passcode → a challenge instead of tokens:
→ 200 {
  "success": true, "status": 200, "statusCode": 201, "message": "Verify",
  "result": { "hash": "…" }        // passcode emailed to the user; hold this hash
}

// then confirm the emailed passcode to receive the token pair:
POST /v1/auth/otp/confirm
Origin: https://your-site.example
{ "pin": "123456", "token": "<hash from the Verify response>" }
→ 200 {
  "success": true, "status": 200, "statusCode": 200,
  "result": { "access_token": "…", "refresh_token": "…" }
}
Notify-then-fetch
A notice to your URL is a hint, not data — it tells you something changed, re-read it. It is delivered once, without retries, so treat it as a nudge to re-fetch over your authenticated connection, never as the source of the change itself. The one exception is the certificate delivery (after a code claim or a Wake), which carries the material. This is the same model behind the admin's Wake action.

The Resources You Fetch

Spaces & agents
Every space in the organization with the agents inside it — each agent's slug, display name, run entry and active state. Your consumer contract's spaces tells you which of them you may use; the slug is what you name when opening a chat.
Consumer contract
Your tenant identity and the ids of the master set of spaces the contract grants. It is the boundary of what your consumers can reach.
Consumer policy
The live registration and login rules for your contract — whether outside registration is allowed, whether a one-time passcode is required, which logins are blocked — plus the default spaces new consumers receive.
Agent front settings
The partner-facing configuration an admin defines per agent — the fields your site reads to render or drive that agent. Fetched per agent, by slug.
Certificate material
Delivered to your notification URL by the handshake, and again on every admin Wake — the public key you verify KeenAgents-issued tokens with, plus the shared secret. It never rides a GET response, so store it when it lands; if you miss the delivery or the certificate is rotated, run the handshake again or ask for a Wake.

Kept fresh for you:

spacesagentscontractpolicyfront settings

When A Step Fails

The observable failure at each step, and how to recover.

Any request · wrong Origin
Connection reset / no reply — a request from an unregistered Origin is dropped silently at every step (on your side it looks like a generic proxy error). No status, no message. Serve your site from the exact Origin registered on your webhook entry, then retry.
1 · Application token
500 { "message": "Invalid request" } — a uniform reject for every credential or binding failure (bad client id / secret, bad api key, or a key that is not yet fully bound with a certificate, contract and webhook). No hint about which. Re-check the credentials the admin handed you and that the key is fully bound, then retry.
2 · Certificate handshake
The GET (issue) drops the connection silently on a bad token or a client_id that does not match the token. The POST (claim) returns 401 { "success": false } if the token is invalid, the client_id mismatches, or the code is wrong, missing, expired (~120s) or already spent. The claim consumes the code either way — GET a fresh one and claim again.
3 · Fetch resources
401 Unauthorized when the application token is missing or malformed, its signature is invalid, its key was deleted or is no longer fully bound, or the key was re-bound to another client — the message names which (Missing or malformed Authorization header / Invalid token / Token api_key no longer valid / Token client_id no longer matches binding). Mint a fresh token. A contract or policy read for a key with no contract bound is a success with "result": null, not an error; the space list is never empty on that account — it lists the organization's spaces regardless. agent-front-settings returns 404 Agent not found for a slug that is not in one of your contract's spaces — the same message whether the agent is elsewhere or does not exist.
4 · Consumer sign-in
200 with { "success": false, "status": 406 } — invalid credentials, an account that is not registered for this tenant, or a login your contract blocks, all returned the same way with no leak of which. On the passcode branch, a wrong or expired pin comes back 200 { "success": false, "message": "Expired" } — start the login again for a fresh challenge.
6 · Stay in sync
A notice is best-effort and sent once — a miss is not an error, only stale data your next authenticated read recovers. Keep the endpoint idempotent so a duplicate or missed notice always converges. See Webhooks for the exact notice payloads.

Previous

Webhooks

Next

Overview

Keen Agents 2026

Documentation

Release 15