OpenClaw Provider
OpenClaw is a long-lived stateful AI runtime that opbox can use as a BYOK provider alongside Anthropic, OpenAI, Groq, and Hermes. When an organisation selects OpenClaw on Settings > AI, every server-side AI call - chat (Cmd+K, sidebar), AI-Agent @-mentions, agent tool calls, and (Phase 1.5) workflow runs - is routed through the OpenClaw gateway as a stateful agent rather than a stateless model call.
This page covers how the integration works, what to configure, and the deployment model behind Cloudflare Tunnel + Access.
Mental Model
The principal model: OpenClaw is the brain at the centre of the application; opbox is one of the surfaces through which someone interacts with that brain. The Org AI page is the BYOK socket - existing today for Anthropic and OpenAI, now also for OpenClaw.
When the socket is OpenClaw:
- AI invocations hit a long-lived agent with memory, tools, and full org context - not a stateless model call.
- Each org-group's traffic targets a distinct agent identity at the gateway (
main,foreman, etc.), keeping principals from impersonating each other even when they share infrastructure. - The OpenClaw runtime sees a stable per-context session key so it can maintain coherent memory across successive turns (e.g. cmdk threads don't bleed into agent-task threads).
Wire Format
OpenClaw's gateway speaks OpenAI-compatible /v1/chat/completions. opbox emits OpenAI-shape requests (not Anthropic Messages API) with three opbox-specific headers attached on every call.
For implementers building or extending the gateway-side endpoint, the full request/response contract opbox depends on is documented under Gateway Wire-Format Spec below.
| Header | Purpose |
|---|---|
Authorization: Bearer <token> | Gateway auth. Phase 1: shared token. Phase 2: bound to a specific agent identity at the gateway. |
X-OpenClaw-Agent: <agent_id> | Selects which agent (main, foreman, etc.) dispatches the call. Becomes redundant in Phase 2 once tokens are agent-bound, but stays harmless. |
X-OpenClaw-Session-Key: agent:<agentId>:<context> | Stable per-context session key. The gateway resolves the target agent identity from the prefix of this header, not from the OpenAI-compat model field. Anything that doesn't match the agent:<agentId>: shape silently falls back to the main principal. |
When the deployment sits behind Cloudflare Tunnel + Cloudflare Access (recommended for production), two further headers are required:
| Header | Purpose |
|---|---|
CF-Access-Client-Id | From the Cloudflare Access service-token. |
CF-Access-Client-Secret | From the Cloudflare Access service-token. |
Cloudflare Access validates these at the edge; without them the request never reaches the gateway. Both fields must be set together (validated at the API write boundary).
Session-Key Contexts
The X-OpenClaw-Session-Key carries a <context> segment so different surfaces don't share memory:
| Context | Surface |
|---|---|
cmdk | Spotlight + sidebar chat (Cmd+K) |
mention | AI-Agent @-mention in matter / step comments |
agent-call | AgentTask worker invocations |
workflow | AI step inside an automation workflow |
extraction | Document extraction pipeline |
doc-gen | AI Doc Generation Agent |
title-gen | Chat-thread title generation |
other | Catch-all for unknown surfaces |
Two Phases
Phase 1 (current) - shared token + per-agent header
The gateway accepts a single shared bearer token; opbox sets X-OpenClaw-Agent on every request to select the agent identity. Works against unmodified OpenClaw without gateway changes.
Phase 2 (planned, OpenClaw runtime side) - per-token agent binding
Each org-group gets a distinct OpenClaw key. The key is bound at the gateway to a single agent - it can only invoke that agent. One workspace's key cannot impersonate another's agent. Same isolation model as having two separate Anthropic accounts.
When Phase 2 ships on the OpenClaw runtime, opbox config switches to per-org-group keys with no other change. The X-OpenClaw-Agent header becomes redundant but harmless to keep sending. No second opbox migration.
Configuration
Settings > AI > Organisation (OWNER/ADMIN). Set the chat provider to OpenClaw and fill the OpenClaw section:
| Field | Required | Description |
|---|---|---|
| OpenClaw bearer token | yes | Sent as Authorization: Bearer <token>. AES-256-GCM encrypted at rest. |
| Endpoint URL | yes | Gateway root URL, e.g. http://localhost:18789 (local dev) or https://openclaw.example.com (Cloudflare Tunnel). The /v1/chat/completions path is appended automatically. |
| Agent ID | yes | Sent as X-OpenClaw-Agent: <id>. Selects which agent (main, foreman, ...) the runtime dispatches to. |
Plus the optional Cloudflare Access subsection (recommended for production):
| Field | Required | Description |
|---|---|---|
| CF Access Client ID | both-or-neither | Sent as the CF-Access-Client-Id header. |
| CF Access Client Secret | both-or-neither | Sent as the CF-Access-Client-Secret header. AES-256-GCM encrypted at rest. |
Validation: if exactly one of the CF Access fields is set, save fails with 400 CF_ACCESS_HALF_CONFIGURED. Both, or neither.
The same fields are also available at the workspace tier (Settings > AI > Workspace) for per-workspace overrides.
Auto-Seating: OPBOX_AGENT
When an organisation selects OpenClaw as its chat provider AND all three OpenClaw fields (bearer + endpoint + agent) are configured, opbox auto-seats an "OpenClaw Principal" user as a member of that organisation with role OPBOX_AGENT.
OPBOX_AGENT is a new role tier:
- Operationally peer-of-OWNER for read/write within the org's workspaces and member-list visibility.
- Distinct in name and UI badging so the principal is clearly identifiable in member lists and audit logs.
- Distinct from
AGENT(the existing third-class API-only seat for individual workspace agents).AGENTis per-workspace, narrow, and minted explicitly.OPBOX_AGENTis org-scoped, broad, and auto-seated by the OpenClaw selection itself.
The seat is revoked when:
- The chat provider is changed away from OpenClaw, OR
- Any of the three required OpenClaw fields is cleared, OR
- The org's AI config row is deleted (DELETE /api/organizations/[orgId]/ai-config).
The principal user record is not deleted on revocation - the user is shared across organisations and may still be seated elsewhere. Only the seat for this specific org is removed.
Bidirectional Auth (OpenClaw → opbox MCP)
The same bearer token opbox uses to call OpenClaw is also accepted in reverse when OpenClaw calls opbox MCP back. This means OpenClaw can autonomously execute opbox tools (matters, tables, documents, etc.) without minting a separate cp_live_* MCP key.
How it works
- On save, opbox computes
SHA-256(openclawBearerToken)and stores it asopenclawBearerHashonOrganizationAiConfig(indexed). - When a request hits
/api/mcp/*with aBearertoken that doesn't match anycp_live_*key, opbox falls back to hashing the token and looking it up inopenclawBearerHash. - On match, the call is authenticated as the OpenClaw principal user (the
OPBOX_AGENT-roled user auto-seated when the org enabled OpenClaw). The autonomy level comes fromopenclawInboundAutonomyLevelon the org config (default L3 - full). - The caller MUST pass
_targetWorkspaceIdto act on a specific workspace - the bearer is org-scoped, not workspace-scoped. Without it, the call falls through to the org's first workspace which is rarely useful.
OpenClaw runtime
│
▼
POST https://opbox.app/api/mcp/tools/call
Authorization: Bearer <same-bearer-as-egress>
X-MCP-Client: openclaw
Content-Type: application/json
{
"name": "list_matters",
"arguments": {
"_targetWorkspaceId": "ckws_balmoral",
"status": "OPEN"
}
}
Properties
- No second key. Same secret, both directions.
- Rotation symmetry. Rotating the bearer in opbox config invalidates the hash, automatically revoking previous OpenClaw → opbox access. opbox + OpenClaw must update in lockstep, but that's already the egress requirement.
- Cross-tenant
_targetWorkspaceIdworks. OpenClaw can call across all workspaces in the org, plus any subordinate workspaces under oversight relationships. Same semantics as acp_live_*overseer key. - Audit attribution. Every call logs as
AI_INTEGRATION_EXECUTEwithapiKeyId = openclaw:<orgId>(sentinel) attributed to the OpenClaw principal user. Distinguishable fromcp_live_*activity in the audit log.
Configuration
Settings > AI > Organisation > OpenClaw section > Inbound MCP access subsection:
| Field | Default | Effect |
|---|---|---|
| Inbound autonomy level | L3 (full) | What tools OpenClaw can invoke. L0 read-only, L1 + low-risk writes, L2 + external side effects, L3 unrestricted. |
When NOT to use bidirectional
- If you want per-call rate limits or per-key budgets, mint a
cp_live_*MCP key instead. The bidirectional path uses a generous default (600 req/min) without the same per-key controls. - If you want multiple OpenClaw agents calling opbox with distinct identities (e.g.
mainandforemanshould attribute differently in audit logs), each agent should have its owncp_live_*key. The bidirectional path always attributes to the single OpenClaw principal user.
For the typical case - one OpenClaw runtime, one Foreman agent, trusted infra - the bidirectional path is the right default.
Cloudflare Tunnel Deployment
The recommended production architecture:
opbox (Fly / Vercel / wherever)
│
▼
Cloudflare Edge ──> Cloudflare Access policy
│ (validates CF-Access-Client-Id +
│ CF-Access-Client-Secret)
▼
Cloudflare Tunnel
│
▼
OpenClaw VM (gateway loopback-only on :18789)
Properties:
- The OpenClaw gateway port stays loopback-only on the VM. No inbound public exposure.
- opbox is the only client allowed to reach it, gated at Cloudflare's edge before traffic ever touches the VM.
- This works regardless of where opbox runs (managed PaaS, on-prem, anywhere) - no VPN mesh required, no static IP allowlist.
For local development, the gateway is reachable directly on http://localhost:18789 and CF Access fields are left blank.
Cost Attribution
Every OpenClaw call writes a row to AiCostLedger with:
provider: 'OPENCLAW'- distinct fromANTHROPICandOPENAIso spend can be reasoned about per-provider.keySource: 'org' | 'workspace' | 'user' | 'server'- reports which credential tier supplied the bearer token (same precedence rules as Anthropic/OpenAI keys).- Token counts and
actualCostUsdfrom the OpenClaw response (OpenAI-compatible usage shape).
The spend breakdown at Settings > AI > Cost Control shows OpenClaw spend as a separate row in the "By feature" + "Attributed to" pivots.
Coverage Today
Phase 1 routes the following surfaces through OpenClaw when selected:
| Surface | Status |
|---|---|
| Cmd+K / Spotlight chat | ✓ Phase 1 |
| Sidebar AI chat | ✓ Phase 1 |
| Chat thread title generation | ✓ Phase 1 |
| AgentTask worker (assignments / mentions / scheduled) | Phase 1.5 |
| Workflow AI step | Phase 1.5 |
| Document extraction | Phase 1.5 |
| AI Doc Generation Agent | Phase 1.5 |
Surfaces marked Phase 1.5 continue to use Anthropic when OpenClaw is selected at the org level. They don't error - they fall back to whatever Anthropic/OpenAI key the workspace also has configured. The agent worker explicitly requires Anthropic today; expanding it to support OpenClaw is the largest single piece of work in Phase 1.5.
API Surface
| Endpoint | Methods | Notes |
|---|---|---|
/api/organizations/[orgId]/ai-config | GET / PUT / DELETE | Org-tier OpenClaw config. PUT triggers auto-seat / revoke. |
/api/settings/ai-config | GET / PATCH / DELETE | Workspace-tier override. Same validation rules. |
PUT/PATCH bodies accept these new fields (all optional):
| Field | Type | Notes |
|---|---|---|
openclawBearerToken | string | null | Encrypted at rest. Blank string + null both treated as "clear". |
openclawEndpointUrl | string | null | Must be valid http:// or https:// URL when set. |
openclawAgentId | string | null | Plain string. |
cfAccessClientId | string | null | Plain string. |
cfAccessClientSecret | string | null | Encrypted at rest. |
GET responses include the masked equivalents:
openclawBearerTokenMasked: '****abcd' | nullcfAccessClientSecretMasked: '****wxyz' | null- Plus the plain
openclawEndpointUrl,openclawAgentId,cfAccessClientId.
Gateway Wire-Format Spec
This is the contract opbox depends on when provider === 'openclaw'. Implementers building or extending the gateway endpoint should read this as authoritative - if any of these shapes change, opbox must change in lockstep.
Inbound Request
POST {endpoint}/v1/chat/completions
Authorization: Bearer cp_live_...
X-OpenClaw-Agent: foreman
X-OpenClaw-Session-Key: agent:<agentId>:<context>
CF-Access-Client-Id: <id> (when CF Access is configured)
CF-Access-Client-Secret: <secret> (when CF Access is configured)
Content-Type: application/json
{
"model": "openclaw",
"messages": [
{ "role": "system", "content": "..." },
{ "role": "user", "content": "..." }
],
"tools": [
{
"type": "function",
"function": {
"name": "list_matters",
"description": "List matters with filters.",
"parameters": { "type": "object", "properties": { ... } }
}
}
],
"max_tokens": 4096,
"stream": false
}
The <context> segment of X-OpenClaw-Session-Key is one of: cmdk, mention, agent-call, workflow, extraction, doc-gen, title-gen, other. Distinct contexts must get distinct session memory so cmdk threads don't bleed into agent-task threads.
Session-key is the routing key. The OpenClaw gateway resolves the target agent from the <agentId> segment of the session-key on every inbound request - that is what selects which agent runtime handles the call. The OpenAI-compat model field (openclaw/<agentId>) is decorative on this path: kept for cost-attribution and telemetry, but it does not drive dispatch. If you debug "why does opbox keep routing to main regardless of config", check the session-key shape first.
Outbound Response (text only)
When the agent has a final answer with no further tool calls:
{
"id": "...",
"choices": [{
"message": {
"role": "assistant",
"content": "Three UBOs - A (35%), B (25%), C (15%, beneficiary only)..."
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 123,
"completion_tokens": 456
}
}
Outbound Response (with tool calls)
When the agent wants opbox to execute one or more tools:
{
"id": "...",
"choices": [{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_xxx",
"type": "function",
"function": {
"name": "list_matters",
"arguments": "{\"status\":\"OPEN\"}"
}
}
]
},
"finish_reason": "tool_calls"
}],
"usage": {
"prompt_tokens": 123,
"completion_tokens": 456
}
}
function.arguments is a JSON-encoded string, not an object. Match OpenAI's quirk here exactly - opbox parses with JSON.parse().
Multi-Turn Loop
When opbox receives finish_reason: "tool_calls", it executes each tool locally (with its own autonomy / role / cell-locking gates) and POSTs the next request with the previous turn's assistant message + a tool message per result:
{
"messages": [
{ "role": "system", "content": "..." },
{ "role": "user", "content": "..." },
{ "role": "assistant", "content": null, "tool_calls": [...] },
{ "role": "tool", "tool_call_id": "call_xxx", "content": "<tool result as string>" }
],
"tools": [...],
...
}
The gateway must thread these turns back into the same agent session keyed by X-OpenClaw-Session-Key. Each session-key gets a persistent claude-code (or whatever backend) subprocess; turns continue against it. Spawning a fresh subprocess per turn is wrong - the agent loses its accumulated reasoning state and tool execution context.
Error Responses
When something goes wrong, opbox treats anything outside the 2xx range as a streamable error and surfaces it to the user:
| Status | What it means | opbox handling |
|---|---|---|
| 401 / 403 | Auth rejected (bearer + CF Access) | The chat route surfaces a layer-specific message identifying whether the rejection came from CF Access (edge) or the OpenClaw runtime (origin), plus a short response-body snippet for diagnosis. |
| 404 | Endpoint not implemented | Surfaces "OpenClaw gateway returned 404 - the OpenAI-compatible endpoint is not implemented on this gateway version yet". |
| 400 | Bad request shape | Generic "AI API error (400): ..." with the body slice. Suggests the gateway expects a different shape than what's documented here. |
| 5xx | Upstream error | Surfaces "OpenClaw gateway returned 5xx - runtime may be unavailable". |
| timeout | 120s without a response | Generic timeout error. |
The gateway should not return 200 with an error body - opbox cost-ledger logic assumes 200 means a real assistant turn. Errors must come back with the appropriate HTTP status.
403 diagnosis
A 403 from the gateway can come from either layer. The chat route distinguishes them via response headers (server: cloudflare + cf-ray indicate CF Access; their absence points at the OpenClaw runtime) and includes a short response-body snippet:
| Signal | Layer | Action |
|---|---|---|
cf-ray present; body has <title>Error ・ Cloudflare Access</title> | CF Access (edge) | Rotate the CF service-token pair in Settings -> AI -> OpenClaw -> Cloudflare Access. |
cf-ray absent; body is JSON or plain text from the gateway | OpenClaw (origin) | Bearer is wrong or the stored bearer hash is stale. Re-paste in Settings -> AI. |
Streaming
opbox currently uses stream: false for chat and the agent worker. The gateway is not required to support streaming. If the gateway implements streaming later, opbox can opt into it via the same OpenAI SSE format (stream: true + stream_options: {include_usage: true}).
Phase 2: Per-Token Agent Binding
Phase 2 adds gateway-side token binding so the bearer token alone selects the agent identity. When this lands:
X-OpenClaw-Agentbecomes redundant but harmless to keep sending. opbox still emits it.- The gateway must reject requests where the header disagrees with the token's binding (return 403 with a clear error). This is the isolation guarantee - one workspace's key cannot impersonate another's agent even if it tries to send the header.
- opbox's UI moves from a single shared key to per-org-group keys; no other change.
Until Phase 2 ships, the header is the agent selector.
See Also
- Overview & Mental Model - the four-layer model that OpenClaw slots into.
- BYOK Credentials - the resolver chain that picks OpenClaw vs the others.
- Security & Limits - injection scanning, rate limits, autonomy.
- Cost Control - how OpenClaw spend is recorded and attributed.