Authentication
Keel has separate credentials for runtime calls, dashboard management, and approval. Do not substitute one class for another.
Runtime API keys
Send a project API key as a Bearer token:
Authorization: Bearer keel_sk_...Project API keys are scoped to one project. If a request body also contains
project_id, it must match the authenticated key’s project. X-API-Key is
accepted on documented compatibility paths, but Bearer authentication is the
public contract for new integrations.
Project keys have three scopes:
| Scope | Intended use |
|---|---|
client | Runtime decision and managed-execution requests that accept a project API key. |
admin | Project-scoped administrative runtime operations, including routes explicitly labeled admin-scope. Do not put this key in client-side code. |
approval | Service-principal approval of a review requirement. It is not a general execution credential. |
The generated public route table is the source for each route’s exact authentication class. A route labeled Project API key with admin scope must not be called with a client key. Routes labeled Project API key or approval key still authorize the specific operation; possessing either credential is not blanket approval authority.
Managed MCP credentials
Managed MCP :prepare and :call require a client-scope execution key bound by
Keel to a live agent identity in the authenticated project. A generic client key
without that binding is refused with 403 execution_principal_required.
Admin keys are refused on these execution routes; an approval key is not an
execution credential. Request-freshness headers remain required for execution.
MCP server management and the advisory POST /v1/mcp/decide route use an admin-scope key.
These credentials serve different boundaries: registering a server or obtaining
an advisory decision does not authorize a later tool dispatch. The published
OpenAPI subset omits these MCP routes, so its generated route table does not
supply their credential requirements.
Create a managed MCP execution key
Use the project’s admin key for this setup, and the legitimate owner’s user
subject identifier for owner_subject_id. Keep both in server-side configuration:
KEEL_ADMIN_KEY and KEEL_OWNER_SUBJECT_ID. KEEL_BASE_URL is optional and
defaults to the hosted API. This Python 3 example uses only the standard library.
First, POST /v1/principals registers the agent in the authenticated project.
Then POST /v1/api-keys creates a client key with agent_principal_id set to
that response’s id. Omitting this field creates an unbound key that cannot
execute managed MCP calls. Key creation requires the admin key; the returned
raw_key is the runtime credential.
import json
import os
import secrets
import time
from urllib.request import Request, urlopen
base_url = os.environ.get("KEEL_BASE_URL", "https://api.keelapi.com").rstrip("/")
admin_key = os.environ["KEEL_ADMIN_KEY"]
owner_subject_id = os.environ["KEEL_OWNER_SUBJECT_ID"]
def fresh_headers(key):
return {
"Authorization": "Bearer " + key,
"Content-Type": "application/json",
"X-Keel-Timestamp": str(int(time.time())),
"X-Keel-Nonce": secrets.token_hex(16),
}
def post_setup(path, body):
request = Request(
base_url + path,
data=json.dumps(body).encode("utf-8"),
headers=fresh_headers(admin_key),
method="POST",
)
with urlopen(request, timeout=30) as response:
return json.load(response)
principal = post_setup("/v1/principals", {
"display_name": "Managed MCP agent",
"owner_principal_type": "user",
"owner_subject_id": owner_subject_id,
})
if principal["status"] != "active" or principal.get("requires_revalidation", False):
raise RuntimeError("Agent identity is not eligible for execution")
credential = post_setup("/v1/api-keys", {
"scope": "client",
"name": "Managed MCP runtime",
"agent_principal_id": principal["id"],
})
runtime_key = credential["raw_key"]Store runtime_key securely and use it for :prepare and :call, generating a
fresh timestamp and nonce for every attempt with fresh_headers(runtime_key).
Keep the admin credential out of the agent’s runtime. Do not recreate the
principal or key on each tool call or ordinary retry.
This setup binds the credential to the agent; it does not grant tool authority.
Server enrollment, eligible tool contracts, policy, plan, review and final
execution checks still apply. An admin key used for :call is refused with
403 insufficient_scope; a client key without a live binding is refused with
403 execution_principal_required.
Dashboard sessions
Management routes use an authenticated user session or verified user JWT. They back the dashboard and include project, policy, provider-key, billing, and API-key management. Do not automate dashboard cookies as if they were runtime API keys.
Public and special-purpose routes
Some status and verification-key routes are unauthenticated. Stripe webhooks use signatures.
The Permit guest review, review invitation, passkey registration, and co-signature routes are contained on hosted Keel: they refuse requests with 403, and guest approval and multi-party co-signature are not available.
The presence of these routes does not make neighboring routes public.
Key prefixes and expiry
New keys start with a prefix that makes them easy to recognize:
| Prefix | Key |
|---|---|
keel_sk_admin_ | Admin scope |
keel_sk_live_ | Client key for a Production project |
keel_sk_test_ | Client key for a Sandbox project |
The prefix is a label, not a permission. Keel reads a key’s scope, project,
environment, agent binding, expiry, and revocation from its stored record. Older
keys that start with keel_sk_, keel_live_, or keel_test_ keep working
unchanged; you do not need to rotate them.
When you create a key in the dashboard, choose how long it lasts: 24 hours,
7 days, 30 days, 90 days, a custom date, or no expiration. The defaults are
30 days for Admin keys, 90 days for Live keys, and 7 days for Test keys. After
a key expires, every request made with it is refused with 401. Rotating a key
creates a replacement and leaves the old key active until you revoke it.
Secret handling
- Create and revoke credentials in the dashboard or documented management flow.
- Store runtime keys in a secrets manager or server-side environment variable.
- Never commit keys, paste them into agent prompts, or expose them in browser bundles, client logs, screenshots, or support transcripts.
- Use the narrowest scope that satisfies the route.
- Treat a copied key as compromised and revoke it.
- Provider credentials used by Keel-managed dispatch remain server-side. A permit-first integration instead owns and protects its provider credential.
Authentication failures
401means the credential is missing, invalid, expired, or otherwise not authenticated. Fix authentication before retrying.403can mean the credential is authenticated but lacks the required scope, targets the wrong project, or is not authorized for the operation. Do not retry unchanged.- Execution routes can additionally require request-freshness headers. Passing authentication does not bypass freshness validation.