BETA
Skip to content

Authentication

Every Krafter API request — REST and MCP — authenticates with a Bearer API key.

http
Authorization: Bearer kr_live_abc123def456ghi789

Keys are issued per team from Settings → API Keys in the dashboard. The plaintext value is shown only once at creation time.

Key format

PrefixBodyExample
kr_live_43 url-safe base64 characters (32 random bytes)kr_live_abc123def456ghi789…

There is a single namespace for live and sandbox keys — sandbox keys are flagged on the record itself, not by a separate prefix. Sandbox keys are scoped to safe defaults (mail:send only, @test.krafter.dev sender, hourly + daily caps) and the dashboard surfaces those constraints when the key is created.

How keys are stored

API keys are hashed with SHA-256 before being persisted — Krafter never stores plaintext keys. The hashed lookup happens on every request via Krafter.ApiKeys.get_by_hash/1: the bearer token from the Authorization header is hashed with the same algorithm and matched against the stored hash, so the platform never holds the key in cleartext at rest.

If you lose a key you must rotate it; the platform cannot retrieve the original value. The dashboard displays the first 12 characters (key_prefix) so you can identify which key is which without exposing the secret.

Storage hygiene

Treat the plaintext value like any other secret — store it in your runtime's environment or a secrets manager. Server logs, error trackers, and frontend bundles must never receive it.

Per-request lifecycle

Each authenticated REST or MCP request goes through the same pipeline:

  1. Hash the bearer token and look it up in api_keys.
  2. Check expires_at — expired keys behave the same as missing keys (401).
  3. Touch last_used_at so the dashboard can show key activity.
  4. Apply the per-team rate limit for the bucket the route belongs to.
  5. Audit-log the request (method, path, status, duration) under the team for the API requests page.
  6. Check the route's required scope before dispatching to the controller.

Authentication errors

json
// 401 — missing, malformed, expired, or revoked key
{ "error": "Invalid or missing API key" }
json
// 403 — valid key, but does not hold the route's required scope
{ "error": "Insufficient scope" }

The Audit API uses a richer envelope ({ "error": { "code": ..., "message": ... } }) — every other service uses the flat shape above.

Scopes

Each route declares a required scope (e.g. mail:send, flags:read, cron:write). A key holds a list of scopes; the route passes if any of the key's scopes equals the required one or if the key holds the wildcard *.

Scope catalogues live with the service docs:

  • Mail authentication
  • Other services: see each service's API reference for the scope required per endpoint.

Wildcard *

The * scope grants every permission across every service. The dashboard's Create API Key form treats * as the implicit default when no scope checkboxes are selected, so any key created without explicitly choosing scopes ends up as a wildcard key.

Avoid wildcard keys in production

Always tick the specific scopes your integration needs. A leaked wildcard key compromises every service for the team that owns it. If you spot a * key in your team's key list and don't know why it is wildcard, rotate it.

Rotation and revocation

  • Rotate from the dashboard to mint a new key with the same name, scopes, expiry, and resource permissions, then atomically delete the old key. The new plaintext is shown once.
  • Revoke by deleting the key. Subsequent requests using the deleted key receive 401 immediately — there is no grace period.

Best practices

  • Use the narrowest scopes that satisfy your integration. Avoid wildcard keys.
  • One key per environment (and per logical service). Rotating a single shared key forces a fleet-wide cutover.
  • Set expires_at for keys handed to short-lived integrations, contractors, or CI runners.
  • Watch the API requests page for unexpected paths or 401/403 spikes — both are early signs of a leaked or misconfigured key.

Built by Krafter Studio