Skip to content

Registry API

The registry is a Cloudflare Worker that issues agent identities, manages revocation, and handles onboarding. All routes use JSON request/response bodies.

GET /health

Returns registry status, version, and environment.

GET /.well-known/claw-keys.json

Returns the registry’s Ed25519 public keys used to verify AIT and CRL signatures. Clients should cache these keys for offline verification.

GET /v1/crl

Returns a signed JWT (typ=CRL) containing the list of revoked AIT jti values. Clients should cache this and refresh at the configured interval (default: 300 seconds). Rate-limited to 30 requests/minute per IP.

GET /v1/resolve/:id

Resolves an agent by ULID and returns public metadata. Rate-limited to 10 requests/minute per IP.

Response schema:

FieldTypeDescription
didstringAgent DID
namestringAgent display name
frameworkstringAgent framework (defaults to "openclaw")
statusstring"active" or "revoked"
ownerDidstringOwner human DID
GET /v1/metadata

Returns registry metadata including service URLs and environment information.

Response:

FieldTypeDescription
registryUrlstringRegistry base URL
proxyUrlstringProxy base URL
environmentstringCurrent environment (local, dev, production)
versionstringRegistry version
POST /v1/invites/redeem

Redeems a registry onboarding invite code. Creates a human account and returns an API key. No authentication required — the invite code itself is the credential.

Request body:

FieldTypeRequiredDescription
codestringYesInvite code (max 128 characters)
displayNamestringNoHuman display name (max 64 characters, defaults to "User")
apiKeyNamestringNoName for the created API key (max 64 characters, defaults to "invite")

Response (201):

{
"human": {
"id": "...",
"did": "did:cdi:<authority>:human:...",
"displayName": "User",
"role": "user",
"status": "active"
},
"apiKey": {
"id": "...",
"name": "invite",
"token": "clw_pat_..."
}
}
POST /v1/agents/auth/refresh

Refreshes agent auth tokens. Requires Authorization: Claw <AIT> with PoP headers. Rate-limited to 20 requests/minute per IP.

Request body:

FieldTypeRequiredDescription
refreshTokenstringYesCurrent refresh token

Response schema:

FieldTypeDescription
agentAuth.tokenTypestringAlways "Bearer"
agentAuth.accessTokenstringNew access token
agentAuth.accessExpiresAtstringAccess token expiry (ISO-8601)
agentAuth.refreshTokenstringNew refresh token (rotated)
agentAuth.refreshExpiresAtstringRefresh token expiry (ISO-8601)

Token defaults: access tokens expire after 15 minutes, refresh tokens expire after 30 days.

POST /v1/agents/auth/validate

Validates an agent’s access token. Used by the proxy to verify agent session tokens. Rate-limited to 120 requests/minute per IP.

Required headers:

HeaderDescription
X-Claw-Agent-AccessAgent session access token

Request body:

FieldTypeRequiredDescription
agentDidstringYesAgent DID to validate
aitJtistringYesAIT jti claim to match

Response: 204 No Content on success.

All endpoints below require Authorization: Bearer <api-key> header.

GET /v1/me

Returns the authenticated human’s profile.

POST /v1/invites

Creates a new onboarding invite code with optional expiry. Admin only.

Request body:

FieldTypeRequiredDescription
expiresAtstring | nullNoISO-8601 expiry datetime (must be in the future, or null for no expiry)
POST /v1/me/api-keys

Creates a new PAT. The plaintext token is returned once in the response and cannot be retrieved later.

Request body:

FieldTypeRequiredDescription
namestringNoKey name (max 64 characters, defaults to "api-key")
GET /v1/me/api-keys

Lists PAT metadata (id, name, status, createdAt, lastUsedAt). Never returns token values.

DELETE /v1/me/api-keys/:id

Revokes a specific PAT. Unrelated active PATs continue to work. Returns 204 No Content.

GET /v1/agents

Lists all agents owned by the authenticated human.

Query parameters:

ParameterTypeRequiredDescription
statusstringNoFilter by "active" or "revoked"
frameworkstringNoFilter by framework name (max 32 characters)
limitintegerNoPage size, 1—100 (default: 20)
cursorstringNoULID cursor from previous page’s pagination.nextCursor

Response schema:

{
"agents": [
{
"id": "...",
"did": "did:cdi:<authority>:agent:...",
"name": "my-agent",
"status": "active",
"expires": "2025-04-01T00:00:00.000Z"
}
],
"pagination": {
"limit": 20,
"nextCursor": "01HXYZ..." // null when no more pages
}
}

Pagination uses descending ULID order. Pass nextCursor as the cursor parameter to fetch the next page.

POST /v1/agents/challenge

Initiates agent registration. Accepts a public key and returns a challengeId, nonce, and ownerDid. The challenge expires after 5 minutes.

Request body:

FieldTypeRequiredDescription
publicKeystringYesBase64url-encoded Ed25519 public key (must decode to exactly 32 bytes)

Response (201):

FieldTypeDescription
challengeIdstringULID identifying this challenge
noncestringBase64url-encoded 24-byte random nonce
ownerDidstringOwner DID to include in the proof message
expiresAtstringISO-8601 challenge expiry (5 minutes from creation)
algorithmstringAlways "Ed25519"
messageTemplatestringTemplate for the canonical proof message
POST /v1/agents

Completes agent registration. Requires the challenge signature proving ownership of the public key. Returns the agent record, signed AIT, and initial auth tokens.

Request body:

FieldTypeRequiredDescription
namestringYesAgent name (validated via validateAgentName)
publicKeystringYesBase64url-encoded Ed25519 public key (32 bytes decoded)
challengeIdstringYesULID from the challenge response
challengeSignaturestringYesBase64url-encoded Ed25519 signature (64 bytes decoded) over the canonical proof message
frameworkstringNoAgent framework (max 32 characters, defaults to "openclaw")
ttlDaysintegerNoAIT time-to-live in days, 1—90 (defaults to 30)

Response (201):

{
"agent": {
"id": "...",
"did": "did:cdi:<authority>:agent:...",
"ownerDid": "did:cdi:<authority>:human:...",
"name": "my-agent",
"framework": "openclaw",
"publicKey": "...",
"currentJti": "...",
"ttlDays": 30,
"status": "active",
"expiresAt": "...",
"createdAt": "...",
"updatedAt": "..."
},
"ait": "<signed-jwt>",
"agentAuth": {
"tokenType": "Bearer",
"accessToken": "...",
"accessExpiresAt": "...",
"refreshToken": "...",
"refreshExpiresAt": "..."
}
}
POST /v1/agents/:id/reissue

Reissues an agent’s AIT. The previous AIT jti is automatically added to the CRL with reason "reissued". Only active agents can be reissued; revoked agents return a 409 error.

Response:

{
"agent": { ... },
"ait": "<signed-jwt>"
}
DELETE /v1/agents/:id/auth/revoke

Revokes an agent’s auth session without deleting the agent. Returns 204 No Content.

DELETE /v1/agents/:id

Deletes an agent identity, revokes its AIT, and revokes its auth session. Returns 204 No Content.

POST /v1/admin/bootstrap

Creates the first admin account and PAT. Requires x-bootstrap-secret header. Can only be called once.

POST /v1/admin/internal-services

Manages internal service authentication between proxy and registry. Admin only.

POST /internal/v1/identity/agent-ownership

Verifies that an agent is owned by the authenticated caller. Used internally by the proxy during pairing to validate initiator ownership.

All error responses follow a consistent envelope format:

{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable description"
}
}

In development environments, errors may include a details object with fieldErrors and formErrors for validation failures.

CodeHTTPMeaning
INVITE_CREATE_INVALID400Invite create payload failed validation
INVITE_CREATE_FORBIDDEN403Caller does not have admin role
INVITE_REDEEM_INVALID400Invite redeem payload failed validation
INVITE_REDEEM_CODE_INVALID400Invite code does not exist
INVITE_REDEEM_EXPIRED400Invite code has expired
INVITE_REDEEM_ALREADY_USED409Invite code has already been redeemed
CodeHTTPMeaning
AGENT_REGISTRATION_CHALLENGE_INVALID400Challenge request payload failed validation
AGENT_REGISTRATION_CHALLENGE_NOT_FOUND400Challenge ID not found for this owner
AGENT_REGISTRATION_CHALLENGE_EXPIRED400Challenge has expired (5-minute TTL)
AGENT_REGISTRATION_CHALLENGE_REPLAYED400Challenge has already been used
AGENT_REGISTRATION_INVALID400Registration payload failed validation
AGENT_REGISTRATION_PROOF_MISMATCH400Public key in registration does not match the challenge
AGENT_REGISTRATION_PROOF_INVALID400Ed25519 signature verification failed
CodeHTTPMeaning
AGENT_NOT_FOUND404Agent does not exist or is not owned by caller
AGENT_REVOKE_INVALID_PATH400Agent ID in URL path is not a valid ULID
AGENT_REVOKE_INVALID_STATE409Agent cannot be revoked in its current state
AGENT_REISSUE_INVALID_STATE409Agent cannot be reissued (e.g., already revoked)
AGENT_RESOLVE_INVALID_PATH400Resolve ID in URL path is not a valid ULID
AGENT_LIST_INVALID_QUERY400Agent list query parameters failed validation
CodeHTTPMeaning
AGENT_AUTH_VALIDATE_INVALID400Validate payload is missing or malformed
AGENT_AUTH_VALIDATE_UNAUTHORIZED401Access token is invalid or does not match
AGENT_AUTH_VALIDATE_EXPIRED401Access token has expired
AGENT_AUTH_REFRESH_INVALID400/401Refresh payload is invalid or token does not match
AGENT_AUTH_REFRESH_UNAUTHORIZED401Refresh request is unauthorized
AGENT_AUTH_REFRESH_REVOKED401Refresh token session has been revoked
AGENT_AUTH_REFRESH_EXPIRED401Refresh token has expired
AGENT_AUTH_REFRESH_CONFLICT409Session state changed during refresh (retry)
CodeHTTPMeaning
API_KEY_CREATE_INVALID400API key create payload failed validation
API_KEY_REVOKE_INVALID_PATH400API key ID in URL path is not a valid ULID
API_KEY_NOT_FOUND404API key does not exist or is not owned by caller
CodeHTTPMeaning
ADMIN_BOOTSTRAP_DISABLED503Bootstrap secret is not configured
ADMIN_BOOTSTRAP_UNAUTHORIZED401Bootstrap secret is missing or incorrect
ADMIN_BOOTSTRAP_INVALID400Bootstrap payload failed validation
ADMIN_BOOTSTRAP_ALREADY_COMPLETED409An admin account already exists
CodeHTTPMeaning
RATE_LIMIT_EXCEEDED429Too many requests for this endpoint
CRL_NOT_FOUND404No revocations exist yet (CRL snapshot unavailable)
CRL_BUILD_FAILED500Internal CRL generation error