Add per-agent `publicMode` configuration that suppresses local runtime details (hostname, OS, architecture, workspace paths, skills snapshot) from the system prompt when serving public-facing agents via the HTTP API. - New `publicMode?: boolean` field in agent config schema - `resolveAgentPublicMode()` in agent-scope for config lookup - System prompt emits `"Runtime: public-facing agent"` instead of host/OS/arch details when publicMode is active - Skills snapshot suppressed for publicMode agents - New `X-OpenClaw-Sender-Is-Owner` HTTP header for both chat completions and OpenResponses endpoints, replacing the hardcoded `senderIsOwner: true` - For publicMode agents, missing header defaults to non-owner semantics; for normal agents, missing header defaults to owner semantics - `resolveIngressSenderIsOwner()` utility in http-utils - Documentation updates for security boundary, header usage, and config - Tests for system prompt suppression, param redaction, and header parsing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4.0 KiB
| summary | read_when | title | |
|---|---|---|---|
| Expose an OpenAI-compatible /v1/chat/completions HTTP endpoint from the Gateway |
|
OpenAI Chat Completions |
OpenAI Chat Completions (HTTP)
OpenClaw’s Gateway can serve a small OpenAI-compatible Chat Completions endpoint.
This endpoint is disabled by default. Enable it in config first.
POST /v1/chat/completions- Same port as the Gateway (WS + HTTP multiplex):
http://<gateway-host>:<port>/v1/chat/completions
Under the hood, requests are executed as a normal Gateway agent run (same codepath as openclaw agent), so routing/permissions/config match your Gateway.
Authentication
Uses the Gateway auth configuration. Send a bearer token:
Authorization: Bearer <token>
Notes:
- When
gateway.auth.mode="token", usegateway.auth.token(orOPENCLAW_GATEWAY_TOKEN). - When
gateway.auth.mode="password", usegateway.auth.password(orOPENCLAW_GATEWAY_PASSWORD). - If
gateway.auth.rateLimitis configured and too many auth failures occur, the endpoint returns429withRetry-After.
Security boundary (important)
Treat this endpoint as an operator-access surface for the gateway instance unless you
intentionally target a publicMode agent through a trusted upstream.
- HTTP bearer auth here is not a narrow per-user scope model.
- A valid Gateway token/password for this endpoint should still be treated like an owner/operator credential.
- Requests run through the same control-plane agent path as trusted operator actions.
- For normal agents, missing
x-openclaw-sender-is-ownerdefaults to owner semantics. - For agents with
publicMode: true, missingx-openclaw-sender-is-ownerdefaults to non-owner semantics; only an explicitx-openclaw-sender-is-owner: truerestores owner access. publicModeis not a full per-user sandbox. It redacts local runtime details in prompts and strips owner-only tools, but any non-owner tools still allowed by the target agent remain callable.- Keep this endpoint on loopback/tailnet/private ingress only; do not expose it directly to the public internet.
See Security and Remote access.
Choosing an agent
No custom headers required: encode the agent id in the OpenAI model field:
model: "openclaw:<agentId>"(example:"openclaw:main","openclaw:beta")model: "agent:<agentId>"(alias)
Or target a specific OpenClaw agent by header:
x-openclaw-agent-id: <agentId>(default:main)
Advanced:
x-openclaw-session-key: <sessionKey>to fully control session routing.
Enabling the endpoint
Set gateway.http.endpoints.chatCompletions.enabled to true:
{
gateway: {
http: {
endpoints: {
chatCompletions: { enabled: true },
},
},
},
}
Disabling the endpoint
Set gateway.http.endpoints.chatCompletions.enabled to false:
{
gateway: {
http: {
endpoints: {
chatCompletions: { enabled: false },
},
},
},
}
Session behavior
By default the endpoint is stateless per request (a new session key is generated each call).
If the request includes an OpenAI user string, the Gateway derives a stable session key from it, so repeated calls can share an agent session.
Streaming (SSE)
Set stream: true to receive Server-Sent Events (SSE):
Content-Type: text/event-stream- Each event line is
data: <json> - Stream ends with
data: [DONE]
Examples
Non-streaming:
curl -sS http://127.0.0.1:18789/v1/chat/completions \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-H 'x-openclaw-agent-id: main' \
-d '{
"model": "openclaw",
"messages": [{"role":"user","content":"hi"}]
}'
Streaming:
curl -N http://127.0.0.1:18789/v1/chat/completions \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-H 'x-openclaw-agent-id: main' \
-d '{
"model": "openclaw",
"stream": true,
"messages": [{"role":"user","content":"hi"}]
}'