📎

These docs are public. Live client_id / client_secret credentials are issued when your realm is provisioned — during closed beta, on approval. Request access →

Reference

KeyTrace API

KeyTrace is a token and identity service for distributed systems. It issues short-lived OIDC/OAuth 2.1 access tokens, validates them at the edge against published JWKS, and revokes them globally in under a second. This reference covers the token endpoint, validation, the error envelope, and revocation.

Base URL
https://auth.keytrace.net
All routes are scoped to a realm. Your realm ID appears in every path as {realm_id}. Examples below use the realm k9x1p3.

Concepts

ConceptDescription
RealmAn isolated tenant with its own signing keys, policies, clients, and audit stream. Compromise stops at the realm boundary.
ClientA service or application that authenticates with a client_id / client_secret (confidential) or PKCE (public).
Access tokenA short-lived (default 300s) RS256 JWT scoped to a realm. Verified locally against JWKS.
Refresh tokenA longer-lived credential that rotates on every use to mint fresh access tokens.
JWKSThe realm's public signing keys, published at /.well-known/jwks.json and rotated on schedule via kid.

Authentication

KeyTrace supports two grant types. Confidential clients (backend services) use client_credentials with a client_id and client_secret. Public clients (SPAs, mobile, CLIs) use the authorization-code grant with PKCE, which is enforced — a request without a valid code_verifier is rejected.

Credentials are scoped to a single realm and are issued when your realm is provisioned. During the closed beta, provisioning happens after your batch is approved.

# Recommended: read credentials from the environment
export KEYTRACE_CLIENT_ID="cid_k9x1p3_…"
export KEYTRACE_CLIENT_SECRET="csk_live_…"

The token endpoint

POST /v2/realm/{realm_id}/token/

Exchanges client credentials (or an authorization code + PKCE verifier) for a short-lived access token.

Request body

FieldTypeDescription
grant_typestringOne of client_credentials, authorization_code, refresh_token.
client_idstringThe client identifier for your realm.
client_secretstringRequired for confidential clients. Never ship this to a browser.
scopestringSpace-delimited scopes, e.g. token:issue.
code_verifierstringRequired for public clients using PKCE.

Example

$ curl -X POST https://auth.keytrace.net/v2/realm/k9x1p3/token/ \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "$KEYTRACE_CLIENT_ID",
    "client_secret": "$KEYTRACE_CLIENT_SECRET",
    "scope": "token:issue"
  }'
200 OK
{
  "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ims5eDFwMy0yMDI2LTAzIn0…",
  "token_type": "Bearer",
  "expires_in": 300,
  "realm": "k9x1p3"
}

expires_in defaults to 300 seconds (5 minutes). Configure per-realm TTL in the dashboard, or per-client with a rotation policy. token_type is always Bearer.

Validating tokens

Access tokens are RS256 JWTs. Verify them locally — no network round-trip on the happy path:

  1. Fetch and cache the realm's public keys from GET /v2/realm/{realm_id}/.well-known/jwks.json.
  2. Match the token header's kid to a key and verify the RS256 signature.
  3. Check iss = https://auth.keytrace.net, realm, exp, and required scope.
  4. Optionally consult the revocation signal (see below) to reject tokens revoked before exp.
GET /v2/realm/k9x1p3/.well-known/jwks.json
{ "keys": [{
  "kty": "RSA", "use": "sig", "alg": "RS256",
  "kid": "k9x1p3-2026-03",
  "n": "0vx7agoebGcQSuu…", "e": "AQAB"
}]}

Keys rotate on schedule. Old kids stay published until every token they signed has expired, so rotation is invisible to validators that cache JWKS with a short TTL.

Error envelope

Every error is a JSON envelope with a stable shape — never an HTML page. An unauthenticated request to any route returns:

401 Unauthorized
{
  "error": "UNAUTHENTICATED",
  "code": 401,
  "realm": null,
  "hint": "A valid client_id and client_secret are required. See https://www.keytrace.net/docs",
  "request_id": "req_0000000000000",
  "ts": "2026-03-26T19:35:51.004Z"
}

Envelope fields

FieldDescription
errorMachine-readable code in UPPER_SNAKE_CASE. Switch on this, not on the message.
codeThe HTTP status, mirrored in the body for clients that only read JSON.
realmThe realm the request resolved to, or null if none could be determined.
hintA human-readable next step. For display and logs — do not parse.
request_idAn opaque correlation ID. Quote it verbatim when contacting support.
tsRFC 3339 timestamp (UTC) the error was generated.

Error codes

errorHTTPMeaning
UNAUTHENTICATED401No valid credentials on the request.
INVALID_CLIENT401The client_id / client_secret pair is wrong or disabled.
INSUFFICIENT_SCOPE403Authenticated, but the token lacks a required scope.
REALM_NOT_FOUND404No realm matches {realm_id}.
TOKEN_REVOKED401The token was revoked before its exp.
RATE_LIMITED429Too many requests; retry after the Retry-After header.

Revocation

POST /v2/realm/{realm_id}/revoke

Revoke a single token, everything issued to a subject, or an entire realm. Compatible with RFC 7009. Propagation to every edge validator completes in under 800 ms p99.

$ curl -X POST https://auth.keytrace.net/v2/realm/k9x1p3/revoke \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{ "sub": "svc_ledger" }'
200 OK
{
  "revoked": "svc_ledger",
  "scope": "subject",
  "receipt": "rev_8f2c…",
  "propagated_in_ms": 742,
  "ts": "2026-03-26T19:41:02.318Z"
}

The signed receipt is written to the realm's append-only audit stream, so you can prove a revocation happened and when.

SDKs

Official SDKs read KEYTRACE_CLIENT_ID and KEYTRACE_CLIENT_SECRET from the environment and handle issuance, JWKS caching, and validation for you.

Node
npm i @keytrace/node
Go
go get keytrace.dev/go
Python
pip install keytrace
node — issue + verify
import { KeyTrace } from "@keytrace/node";

const kt = new KeyTrace({ realm: "k9x1p3" });

// issue a 5-minute access token
const { access_token } = await kt.token({ scope: "token:issue" });

// verify locally against JWKS (no round-trip)
const claims = await kt.verify(access_token);

Can't authenticate yet?

KeyTrace is in closed beta. Credentials are issued when your batch is approved.

Request access