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: a Webhook entry (your browser Origin plus a notification URL), a Client (your server's identity), a Certificate (the signing material), and a Consumer Contract (your policy and the spaces you may use), all bound to one Api Key. When those four are bound, the key is fully bound and your Origin is on the allow-list. The admin then hands you two things: your Client id and secret, and confirmation of the Origin and notification URL registered for you.

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 gateway error, never a descriptive one — so an unknown caller learns nothing. If connections fail with no message, check the Origin first.

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 for an application token — the standing credential your server 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.
  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, just run the handshake again to have it re-delivered.
  4. Fetch your resources. With the application token you read everything bound to your key — your spaces and their agents, your consumer contract, your consumer policy, and each agent's front settings. Every read is scoped to your own key: you only ever see your own tenant's data.
  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. Register your notification URL to receive a notice whenever your configuration changes (or when an admin presses Wake). On receipt, re-fetch the affected resources — the notice carries no data. This keeps your site current without polling.

The Requests, In Shape

Interface-level shapes — your KeenAgents base URL is provided at onboarding.

1 · Application token (server-side, once, then refresh as needed)

POST /v1/auth/application_token?client_id=kcid_…
Origin: https://your-site.example
Authorization: <base64 of "client_secret|api_key|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.example200 { "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, all scoped to your key)

// 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                              // your spaces, each with a nested agents[] array
GET /v1/resources/consumer-contract             // your contract: identity + 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": "…",
      "title": "Support",
      "description": "…",
      "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. This is the same model behind the admin's Wake action.

The Resources You Fetch

Spaces & agents
The spaces your key may use and the agents inside them — each agent's slug, display name, run entry and active state. The slug is what you name when opening a chat.
Consumer contract
Your tenant identity and 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 — 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 to have it re-sent.

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 (through a proxy it looks like a generic gateway 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, expired, or its api key / client binding changed — mint a fresh token. A read for a key with nothing bound is a success with "result": null (contract / policy) or an empty [] (spaces), not an error. 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.

Where To Go Next

Each piece, in depth.

Api Keys

The hub credential and its four bindings; the Wake action.

Authentication

The two-token model, one-time passcodes and refresh.

Live Chat

Running an agent over the streaming connection.

Self-Service

Consumer profile, password reset and account deletion.

Webhooks

The Origin + notification URL your key is bound to.

Resource Cache

Why your fetched data is always current.

Logs & Monitoring

Live-stream an agent's runtime logs to your site.

Previous

Webhooks

Next

Overview

Keen Agents 2026

Documentation

Release 15