OpenResponses API: Expose /v1/responses Endpoint from Gateway
Learn how to enable an OpenResponses-compatible HTTP endpoint on your Gateway. This guide covers configuration, authentication, and routing for developers using the Gateway.
Read this when
- Integrating clients that speak the OpenResponses API
- You want item-based inputs, client tool calls, or SSE events
The Gateway can expose an POST /v1/responses endpoint that is compatible with OpenResponses. This feature is turned off by default and operates on the same port as the Gateway itself (via WS + HTTP multiplexing): http://<gateway-host>:<port>/v1/responses.
Requests follow the same execution path as a standard Gateway agent run (identical code path to openclaw agent), meaning routing, permissions, and configuration align with your existing Gateway setup.
Toggle this feature on or off using gateway.http.endpoints.responses.enabled. When activated, the same compatibility layer also provides GET /v1/models, GET /v1/models/{id}, POST /v1/embeddings, and POST /v1/chat/completions.
Authentication, security, and routing
Runtime behavior follows the OpenAI Chat Completions specification:
- The authentication path mirrors
gateway.auth.mode: shared-secret mode (token/password) relies onAuthorization: Bearer <token-or-password>; trusted-proxy mode uses identity-aware proxy headers (same-host loopback proxies requiregateway.auth.trustedProxy.allowLoopback = true, with a direct same-host fallback throughgateway.auth.password/OPENCLAW_GATEWAY_PASSWORDwhen noForwarded/X-Forwarded-*/X-Real-IPheader exists);noneon private ingress does not need an auth header. Refer to Trusted proxy auth. - Consider the endpoint as granting full operator access to the gateway instance.
- Shared-secret auth modes disregard a more restrictive bearer-declared
x-openclaw-scopesand restore the complete default operator scope set:operator.admin,operator.approvals,operator.pairing,operator.read,operator.talk.secrets,operator.write. Chat turns on this endpoint are handled as owner-sender turns. - Trusted identity-bearing HTTP modes (trusted-proxy, or
gateway.auth.mode="none") respectx-openclaw-scopeswhen provided, otherwise they fall back to the operator default scope set. Owner semantics are only lost if the caller explicitly narrows scopes and leaves outoperator.admin. - Choose agents using
model: "openclaw","openclaw/default","openclaw/<agentId>", or thex-openclaw-agent-idheader. - Apply
x-openclaw-modelto replace the selected agent's backend model (requiresoperator.adminon identity-bearing auth paths). - Use
x-openclaw-session-keyfor explicit session routing (returns400 invalid_request_errorif it involves a reserved namespace:subagent:,cron:,acp:). - Use
x-openclaw-message-channelto specify a non-default synthetic ingress channel context.
For a complete description of agent-target models, openclaw/default, embeddings pass-through, and backend model overrides, consult OpenAI Chat Completions.
Check Operator scopes and Security for more details.
Session behavior
The endpoint is stateless per request by default, meaning a fresh session key is generated for every call.
When a request contains an OpenResponses user string, the Gateway creates a stable session key from it, allowing repeated calls to share an agent session.
previous_response_id reuses the session from the prior response as long as the request stays within the same agent, user, and requested session scope (matched by auth subject, agent id, and x-openclaw-session-key).
Request shape
| Field | Support |
|---|---|
input | Can be a string or an array of item objects. |
instructions | Gets folded into the system prompt. |
tools | Definitions for client tools (function tools). |
tool_choice | Use "auto", "none", "required", or { "type": "function", "name": "..." } to restrict or mandate which client tools are used. |
stream | Turns on SSE streaming. |
max_output_tokens | A best-effort cap on output length (depends on the provider). |
temperature | Best-effort sampling temperature. The ChatGPT-based Codex Responses backend ignores it, applying a fixed server-side value instead. |
top_p | Best-effort nucleus sampling. The same Codex Responses limitation applies as with temperature. |
user | Ensures stable session routing. |
previous_response_id | Maintains session continuity (described above). |
max_tool_calls, reasoning, metadata, store, truncation | Accepted but produce no effect at this time. |
Items (input)
message
Roles: system, developer, user, assistant.
systemanddeveloperget added to the end of the system prompt.- The most recent
userorfunction_call_outputentry serves as the "current message." - Earlier user and assistant messages are supplied as conversation history.
function_call_output (turn-based tools)
Return tool results to the model:
{
"type": "function_call_output",
"call_id": "call_123",
"output": "{\"temperature\": \"72F\"}"
}
reasoning and item_reference
Accepted for schema compatibility but omitted when constructing the prompt.
Tools (client-side function tools)
Provide tools using tools: [{ type: "function", name, description?, parameters? }].
When the agent invokes a tool, the response includes an function_call output item. To continue the turn, send a follow-up request containing function_call_output.
For tool_choice: "required" and function-pinned tool_choice, the endpoint reduces the set of exposed client function tools, directs the runtime to call a client tool before replying, and rejects the turn if it lacks a matching structured client-tool call, consistent with the /v1/chat/completions contract. Non-streaming requests return 502 with an api_error; streaming requests produce a response.failed event.
Images (input_image)
Accepts base64 or URL sources:
{
"type": "input_image",
"source": { "type": "url", "url": "https://example.com/image.png" }
}
Permitted MIME types (default): image/jpeg, image/png, image/gif, image/webp, image/heic, image/heif. Default maximum size: 10MB.
Files (input_file)
Accepts base64 or URL sources:
{
"type": "input_file",
"source": {
"type": "base64",
"media_type": "text/plain",
"data": "SGVsbG8gV29ybGQh",
"filename": "hello.txt"
}
}
Permitted MIME types (default): text/plain, text/markdown, text/html, text/csv, application/json, application/pdf. Default maximum size: 5MB.
Current behavior:
- File contents are decoded and inserted into the system prompt rather than the user message, making them temporary (they are not stored in session history).
- Before addition, decoded file text is enclosed as untrusted external content, so file bytes are handled as data rather than trusted commands. The injected block uses explicit boundary markers (
<<<EXTERNAL_UNTRUSTED_CONTENT id="...">>>/<<<END_EXTERNAL_UNTRUSTED_CONTENT id="...">>>) together with aSource: Externalmetadata line. The lengthySECURITY NOTICE:banner is deliberately left out to conserve the prompt budget; the boundary markers and metadata remain active. - PDFs are initially scanned for text. When minimal text is detected, the initial pages are converted to raster images and sent to the model, with the injected file block employing the placeholder
[PDF content rendered to images].
PDF processing relies on the bundled document-extract plugin, which leverages clawpdf along with its included PDFium WebAssembly runtime for both text extraction and page rendering.
Default settings for URL fetching:
files.allowUrl:trueimages.allowUrl:truemaxUrlParts:8(combined total ofinput_fileplusinput_imageparts per request)- Requests are protected by DNS resolution checks, private IP blocking, redirect limits, and timeouts.
- Per input type (
files.urlAllowlist,images.urlAllowlist), optional hostname allowlists are supported: exact host matching ("cdn.example.com") or wildcard subdomains ("*.assets.example.com", which does not match the apex domain). An empty or omitted allowlist means no hostname allowlist restriction is applied. - To completely disable URL-based fetches, set
files.allowUrl: falseand/orimages.allowUrl: false.
File + image limits
The endpoint enforces a built-in 20 MB request-body limit. File and image source policy remains adjustable under gateway.http.endpoints.responses:
{
gateway: {
http: {
endpoints: {
responses: {
enabled: true,
maxUrlParts: 8,
files: {
allowUrl: true,
urlAllowlist: ["cdn.example.com", "*.assets.example.com"],
allowedMimes: [
"text/plain",
"text/markdown",
"text/html",
"text/csv",
"application/json",
"application/pdf",
],
maxBytes: 5242880,
maxChars: 60000,
maxRedirects: 3,
timeoutMs: 10000,
pdf: {
maxPages: 4,
maxPixels: 4000000,
minTextChars: 200,
},
},
images: {
allowUrl: true,
urlAllowlist: ["images.example.com"],
allowedMimes: [
"image/jpeg",
"image/png",
"image/gif",
"image/webp",
"image/heic",
"image/heif",
],
maxBytes: 10485760,
maxRedirects: 3,
timeoutMs: 10000,
},
},
},
},
},
}
Values used when not explicitly provided:
| Key | Default |
|---|---|
maxUrlParts | 8 |
files.maxBytes | 5MB |
files.maxChars | 60k |
files.maxRedirects | 3 |
files.timeoutMs | 10s |
files.pdf.maxPages | 4 |
files.pdf.maxPixels | 4,000,000 |
files.pdf.minTextChars | 200 |
images.maxBytes | 10MB |
images.maxRedirects | 3 |
images.timeoutMs | 10s |
HEIC/HEIF input_image sources are converted to JPEG before provider delivery using the shared OpenClaw image processor (Rastermill), which falls back to a system converter (sips, ImageMagick, GraphicsMagick, or ffmpeg) for formats that need external codec support.
Security note: URL allowlists are applied both before the fetch and on redirect hops. Allowing a hostname does not bypass private or internal IP blocking. For internet-facing gateways, apply network egress controls alongside application-level safeguards. See Security.
Streaming (SSE)
Set stream: true to receive Server-Sent Events:
Content-Type: text/event-stream- Each event line is
event: <type>anddata: <json> - The stream terminates with
data: [DONE]
Currently emitted event types: response.created, response.in_progress, response.output_item.added, response.content_part.added, response.output_text.delta, response.output_text.done, response.content_part.done, response.output_item.done, response.completed, response.failed (on error).
Usage
usage is filled when the underlying provider reports token counts. OpenClaw normalizes common OpenAI-style aliases before those counters reach downstream status and session surfaces, including input_tokens / output_tokens and prompt_tokens / completion_tokens.
Errors
Errors are returned as a JSON structure:
{ "error": { "message": "...", "type": "invalid_request_error" } }
Typical error scenarios include: 400 malformed request body, 401 authentication that is missing or invalid, 403 operator scope not granted, 405 HTTP method not allowed, 429 repeated authentication failures exceeding the limit (accompanied by Retry-After).
Examples
Without streaming:
curl -sS http://127.0.0.1:18789/v1/responses \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-H 'x-openclaw-agent-id: main' \
-d '{
"model": "openclaw",
"input": "hi"
}'
With streaming:
curl -N http://127.0.0.1:18789/v1/responses \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-H 'x-openclaw-agent-id: main' \
-d '{
"model": "openclaw",
"stream": true,
"input": "hi"
}'