opboxDocs
Sign inBook a demo
DocsOpenClaw ProviderAI - Foundations

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.

HeaderPurpose
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:

HeaderPurpose
CF-Access-Client-IdFrom the Cloudflare Access service-token.
CF-Access-Client-SecretFrom 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:

ContextSurface
cmdkSpotlight + sidebar chat (Cmd+K)
mentionAI-Agent @-mention in matter / step comments
agent-callAgentTask worker invocations
workflowAI step inside an automation workflow
extractionDocument extraction pipeline
doc-genAI Doc Generation Agent
title-genChat-thread title generation
otherCatch-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:

FieldRequiredDescription
OpenClaw bearer tokenyesSent as Authorization: Bearer <token>. AES-256-GCM encrypted at rest.
Endpoint URLyesGateway 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 IDyesSent as X-OpenClaw-Agent: <id>. Selects which agent (main, foreman, ...) the runtime dispatches to.

Plus the optional Cloudflare Access subsection (recommended for production):

FieldRequiredDescription
CF Access Client IDboth-or-neitherSent as the CF-Access-Client-Id header.
CF Access Client Secretboth-or-neitherSent 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). AGENT is per-workspace, narrow, and minted explicitly. OPBOX_AGENT is 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

  1. On save, opbox computes SHA-256(openclawBearerToken) and stores it as openclawBearerHash on OrganizationAiConfig (indexed).
  2. When a request hits /api/mcp/* with a Bearer token that doesn't match any cp_live_* key, opbox falls back to hashing the token and looking it up in openclawBearerHash.
  3. 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 from openclawInboundAutonomyLevel on the org config (default L3 - full).
  4. The caller MUST pass _targetWorkspaceId to 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 _targetWorkspaceId works. OpenClaw can call across all workspaces in the org, plus any subordinate workspaces under oversight relationships. Same semantics as a cp_live_* overseer key.
  • Audit attribution. Every call logs as AI_INTEGRATION_EXECUTE with apiKeyId = openclaw:<orgId> (sentinel) attributed to the OpenClaw principal user. Distinguishable from cp_live_* activity in the audit log.

Configuration

Settings > AI > Organisation > OpenClaw section > Inbound MCP access subsection:

FieldDefaultEffect
Inbound autonomy levelL3 (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. main and foreman should attribute differently in audit logs), each agent should have its own cp_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 from ANTHROPIC and OPENAI so 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 actualCostUsd from 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:

SurfaceStatus
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 stepPhase 1.5
Document extractionPhase 1.5
AI Doc Generation AgentPhase 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

EndpointMethodsNotes
/api/organizations/[orgId]/ai-configGET / PUT / DELETEOrg-tier OpenClaw config. PUT triggers auto-seat / revoke.
/api/settings/ai-configGET / PATCH / DELETEWorkspace-tier override. Same validation rules.

PUT/PATCH bodies accept these new fields (all optional):

FieldTypeNotes
openclawBearerTokenstring | nullEncrypted at rest. Blank string + null both treated as "clear".
openclawEndpointUrlstring | nullMust be valid http:// or https:// URL when set.
openclawAgentIdstring | nullPlain string.
cfAccessClientIdstring | nullPlain string.
cfAccessClientSecretstring | nullEncrypted at rest.

GET responses include the masked equivalents:

  • openclawBearerTokenMasked: '****abcd' | null
  • cfAccessClientSecretMasked: '****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:

StatusWhat it meansopbox handling
401 / 403Auth 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.
404Endpoint not implementedSurfaces "OpenClaw gateway returned 404 - the OpenAI-compatible endpoint is not implemented on this gateway version yet".
400Bad request shapeGeneric "AI API error (400): ..." with the body slice. Suggests the gateway expects a different shape than what's documented here.
5xxUpstream errorSurfaces "OpenClaw gateway returned 5xx - runtime may be unavailable".
timeout120s without a responseGeneric 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:

SignalLayerAction
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 gatewayOpenClaw (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-Agent becomes 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

We use cookies

Strictly necessary cookies keep you signed in and protect requests. We also use optional cookies for preferences and (when enabled) analytics. Learn more.