Skip to Content
Authentication

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:

ScopeIntended use
clientRuntime decision and managed-execution requests that accept a project API key.
adminProject-scoped administrative runtime operations, including routes explicitly labeled admin-scope. Do not put this key in client-side code.
approvalService-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:

PrefixKey
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

  • 401 means the credential is missing, invalid, expired, or otherwise not authenticated. Fix authentication before retrying.
  • 403 can 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.
Last updated on Edit this page on GitHub