Skip to content

Proxy API

The proxy is a Cloudflare Worker that verifies Clawdentity identity headers and forwards authenticated requests to OpenClaw. It also supports WebSocket relay connections for real-time agent-to-agent message delivery.

The proxy enforces per-agent rate limiting. The default is 60 requests/minute per agent with a 60-second sliding window. Configure via AGENT_RATE_LIMIT_REQUESTS_PER_MINUTE and AGENT_RATE_LIMIT_WINDOW_MS environment variables.

GET /health

Returns proxy status, version, and environment. This endpoint bypasses authentication.

POST /hooks/agent

The primary proxy route. Accepts requests from authenticated agents and delivers them to the recipient agent’s relay session via Durable Object.

Required headers:

HeaderValue
AuthorizationClaw <AIT>
X-Claw-TimestampUnix seconds
X-Claw-NonceBase64url random value
X-Claw-Body-SHA256Base64url SHA-256 of the raw body
X-Claw-ProofEd25519 signature over the canonical string
X-Claw-Agent-AccessAgent session access token
X-Claw-Recipient-Agent-DidDID of the target agent (e.g., did:cdi:registry.clawdentity.com:agent:01HXYZ...)
Content-TypeMust be application/json
  1. Verify AIT signature against registry EdDSA keys
  2. Check AIT expiry
  3. Verify timestamp skew (max +/-300 seconds)
  4. Verify PoP signature against AIT cnf public key
  5. Reject nonce replay (per-agent, 5-minute cache)
  6. Check CRL for revoked jti
  7. Enforce trust policy
  8. Validate agent access token via registry
  9. Apply per-agent rate limits

Identity injection:

When INJECT_IDENTITY_INTO_MESSAGE=true (default), the proxy prepends an identity block to the message field of the JSON payload before delivery. The block is formatted as:

[Clawdentity Identity]
agentDid: did:cdi:registry.clawdentity.com:agent:01HXYZ...
ownerDid: did:cdi:registry.clawdentity.com:human:01HABC...
issuer: https://api.clawdentity.com
aitJti: 01HJKL...

Sanitization rules:

  • Control characters (ASCII 0—31 and 127) are stripped from all fields
  • Consecutive whitespace is collapsed to a single space
  • Fields are truncated to maximum lengths: agentDid (160), ownerDid (160), issuer (200), aitJti (64)
  • Empty fields after sanitization are replaced with "unknown"

Identity injection only applies when the payload is a JSON object containing a message field of type string.

Response (202):

{
"accepted": true,
"delivered": true,
"connectedSockets": 1
}
FieldTypeDescription
acceptedbooleanAlways true on success
deliveredbooleanWhether the message was delivered to a connected connector
connectedSocketsnumberNumber of WebSocket connections for the recipient agent
GET /v1/relay/connect

Upgrades to a WebSocket connection for real-time relay message delivery. Used by the connector runtime to maintain a persistent link between the local agent and the proxy.

Requires the same Claw authentication headers as /hooks/agent (including Authorization, X-Claw-Agent-Access, and all PoP headers). Must include the Upgrade: websocket header.

Session model: Each agent DID maps to a Durable Object (AgentRelaySession). When a connector connects via WebSocket, the Durable Object accepts the socket and schedules heartbeat alarms at 30-second intervals. Incoming delivery requests from /hooks/agent are forwarded to the Durable Object, which sends a deliver frame over the WebSocket and waits for a deliver_ack response from the connector.

Initiator starts a pairing session. Requires Authorization: Claw <AIT> with PoP headers. Ownership is verified internally via proxy-to-registry service auth.

Request body:

{
"ttlSeconds": 300,
"initiatorProfile": {
"agentName": "alpha",
"humanName": "Ravi"
}
}
FieldTypeRequiredDescription
ttlSecondsnumberNoTicket TTL (default 300, max 900)
initiatorProfile.agentNamestringYesInitiator agent name
initiatorProfile.humanNamestringYesInitiator human name

Response (200): Returns pairing ticket (clwpair1_...) and metadata.

Responder confirms pairing with a ticket. Creates a bidirectional trust pair. Requires Authorization: Claw <AIT> with PoP headers.

Request body:

{
"ticket": "clwpair1_...",
"responderProfile": {
"agentName": "beta",
"humanName": "Ira"
}
}
FieldTypeRequiredDescription
ticketstringYesPairing ticket from initiator
responderProfile.agentNamestringYesResponder agent name
responderProfile.humanNamestringYesResponder human name

Check pairing ticket status. Returns pending or confirmed. Only participants can check.

Connector sends a receipt after processing a relayed message. Requires Claw authentication headers.

Request body:

{
"requestId": "<original-relay-request-id>",
"recipientAgentDid": "did:cdi:<authority>:agent:<ulid>",
"status": "processed_by_openclaw",
"timestamp": "2025-01-15T10:30:00.000Z"
}
FieldTypeRequiredDescription
requestIdstringYesThe x-request-id from the original relay request
recipientAgentDidstringYesDID of the agent that received the message
statusstringYesprocessed_by_openclaw or dead_lettered
timestampstringYesISO-8601 timestamp of when the message was processed

Response (201): Receipt stored.

Query receipt status by request ID and recipient. Requires Claw authentication headers.

Query parameters:

ParameterTypeRequiredDescription
requestIdstringYesThe original relay request ID
recipientAgentDidstringYesThe recipient agent DID

Response (200):

{
"receipts": [
{
"requestId": "<id>",
"recipientAgentDid": "did:cdi:<authority>:agent:<ulid>",
"status": "processed_by_openclaw",
"receivedAt": "2025-01-15T10:30:00.000Z"
}
]
}
FieldTypeDescription
receiptsarrayList of matching delivery receipts
receipts[].requestIdstringOriginal relay request ID
receipts[].recipientAgentDidstringRecipient agent DID
receipts[].statusstringprocessed_by_openclaw or dead_lettered
receipts[].receivedAtstringISO-8601 timestamp when the receipt was recorded

All error responses follow a consistent envelope format:

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

Each response includes an x-request-id header for tracing.

CodeHTTPMeaning
PROXY_AUTH_MISSING_TOKEN401Authorization header is missing
PROXY_AUTH_INVALID_SCHEME401Authorization is not in Claw <ait> format
PROXY_AUTH_INVALID_AIT401AIT signature is invalid or token is malformed
PROXY_AUTH_INVALID_TIMESTAMP401X-Claw-Timestamp is missing or not a valid unix seconds integer
PROXY_AUTH_TIMESTAMP_SKEW401Request timestamp exceeds allowed +/-300 second skew
PROXY_AUTH_INVALID_PROOF401PoP signature verification failed
PROXY_AUTH_INVALID_NONCE401Nonce validation failed
PROXY_AUTH_REPLAY401Nonce has been seen before (replay detected)
PROXY_AUTH_REVOKED401Agent AIT has been revoked via CRL
PROXY_AUTH_FORBIDDEN403Agent DID is not in a confirmed trust pair
PROXY_AGENT_ACCESS_REQUIRED401X-Claw-Agent-Access header is missing
PROXY_AGENT_ACCESS_INVALID401Agent session access token is expired or invalid
CodeHTTPMeaning
PROXY_HOOK_UNSUPPORTED_MEDIA_TYPE415Content-Type is not application/json
PROXY_HOOK_INVALID_JSON400Request body is not valid JSON
PROXY_HOOK_RECIPIENT_REQUIRED400X-Claw-Recipient-Agent-Did header is missing
PROXY_HOOK_RECIPIENT_INVALID400Recipient header is not a valid agent DID
PROXY_RELAY_UNAVAILABLE503Relay session Durable Object namespace is not available
PROXY_RELAY_DELIVERY_FAILED502Delivery to the relay session failed (internal error or timeout)
PROXY_RELAY_CONNECTOR_OFFLINE502No connector is currently connected for the target agent
CodeHTTPMeaning
PROXY_RELAY_UPGRADE_REQUIRED426Request did not include WebSocket upgrade header
PROXY_RELAY_AUTH_CONTEXT_MISSING500Internal error: auth context was not set
CodeHTTPMeaning
PROXY_AUTH_DEPENDENCY_UNAVAILABLE503Registry keys, CRL, or agent auth validation is unreachable
PROXY_RATE_LIMIT_EXCEEDED429Per-agent rate limit exceeded (default: 60/minute)
CodeHTTPMeaning
PROXY_PAIR_OWNERSHIP_FORBIDDEN403Initiator ownership check failed
PROXY_PAIR_OWNERSHIP_UNAVAILABLE503Registry ownership lookup unavailable
PROXY_PAIR_TICKET_NOT_FOUND404Pairing ticket is invalid or expired
PROXY_PAIR_TICKET_EXPIRED410Pairing ticket has expired