What the OpenClaw API is
Everything in OpenClaw runs through the gateway, a local server that listens on port 18789 by default. The Control UI, the CLI and the mobile apps talk to it over WebSocket. The same port also serves a small set of HTTP endpoints you can call from scripts, apps and other services.
The chat endpoints follow the OpenAI format, so most OpenAI client libraries work by changing the base URL. The difference is that the model field picks one of your OpenClaw agents, not a provider model. Your agent then uses whichever model, skills and memory you've configured.
| Endpoint | What it does | Default | Credential |
|---|---|---|---|
POST/v1/chat/completions | Send messages to an agent, OpenAI Chat Completions format | Off | Gateway token |
POST/v1/responses | OpenResponses format with session reuse and client tools | Off | Gateway token |
GET/v1/models | List the agents you can target | With either above | Gateway token |
POST/v1/embeddings | Create embeddings | With either above | Gateway token |
POST/tools/invoke | Run one tool directly, without a chat turn | On | Gateway token |
POST/hooks/agent, /hooks/wake | Let other services trigger your assistant | Off | Separate hook token |
WSws://127.0.0.1:18789 | Control UI, CLI and apps; full protocol | On | Token + device pairing |
How OpenClaw API authentication works
The gateway supports four auth modes, set with gateway.auth.mode in ~/.openclaw/openclaw.json. For API access, use token mode.
Authorization: Bearer <token>.RecommendedStep 1: Create a gateway token
Let OpenClaw generate a strong token for you:
openclaw doctor --generate-gateway-tokenOr set your own value:
openclaw config set gateway.auth.token "$(openssl rand -hex 32)"Step 2: Keep the token out of your code
Store it in an environment variable. OpenClaw itself also reads OPENCLAW_GATEWAY_TOKEN, so the same name works on the gateway host and in your scripts.
export OPENCLAW_GATEWAY_TOKEN="paste-your-token-here"Step 3: Send it with every request
Authorization: Bearer $OPENCLAW_GATEWAY_TOKENDon't mix them up. The gateway token lets your code talk to OpenClaw. Model provider keys, like ANTHROPIC_API_KEY or OPENAI_API_KEY, let OpenClaw talk to an AI model. Put provider keys in ~/.openclaw/.env and check them with openclaw models status. See our models guide.
Enable the HTTP API
The OpenAI-compatible endpoints are off by default. Turn on the ones you need in ~/.openclaw/openclaw.json, then restart the gateway.
{
gateway: {
auth: { mode: "token" },
http: {
endpoints: {
chatCompletions: { enabled: true },
responses: { enabled: true },
},
},
},
}Check it works by listing your agents:
curl http://127.0.0.1:18789/v1/models \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN"Your first request
Send a message to your default agent. Pick a language:
curl http://127.0.0.1:18789/v1/chat/completions \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "openclaw/default",
"messages": [
{"role": "user", "content": "What is on my calendar tomorrow?"}
]
}'import os
import requests
resp = requests.post(
"http://127.0.0.1:18789/v1/chat/completions",
headers={"Authorization": f"Bearer {os.environ['OPENCLAW_GATEWAY_TOKEN']}"},
json={
"model": "openclaw/default",
"messages": [
{"role": "user", "content": "What is on my calendar tomorrow?"}
],
},
timeout=120,
)
resp.raise_for_status()
print(resp.json()["choices"][0]["message"]["content"])const res = await fetch("http://127.0.0.1:18789/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENCLAW_GATEWAY_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "openclaw/default",
messages: [{ role: "user", content: "What is on my calendar tomorrow?" }],
}),
});
if (!res.ok) throw new Error(`OpenClaw returned ${res.status}`);
const data = await res.json();
console.log(data.choices[0].message.content);import os
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:18789/v1",
api_key=os.environ["OPENCLAW_GATEWAY_TOKEN"], # sent as the Bearer token
)
reply = client.chat.completions.create(
model="openclaw/default",
messages=[{"role": "user", "content": "What is on my calendar tomorrow?"}],
)
print(reply.choices[0].message.content)Agent turns can take a while when your assistant uses tools, so set a generous client timeout.
Stream the reply
Add "stream": true to receive the answer as Server-Sent Events while the agent is still writing.
curl -N http://127.0.0.1:18789/v1/chat/completions \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "openclaw/default",
"stream": true,
"messages": [{"role": "user", "content": "Draft a reply to my last email"}]
}'stream = client.chat.completions.create(
model="openclaw/default",
stream=True,
messages=[{"role": "user", "content": "Draft a reply to my last email"}],
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="", flush=True)Choose an agent and keep a conversation
The model field chooses which OpenClaw agent answers:
model value | Goes to |
|---|---|
openclaw or openclaw/default | Your default agent |
openclaw/<agentId> | A specific agent, for example openclaw/work |
agent:<agentId> | Same as above, kept for compatibility |
Requests are stateless by default, so each call starts a fresh session. To keep context across calls, send the same user value each time and OpenClaw will route them to one stable session:
curl http://127.0.0.1:18789/v1/chat/completions \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "openclaw/work",
"user": "crm-integration",
"messages": [{"role": "user", "content": "Remember: the Q4 review is on Friday."}]
}'Use the Responses API
POST /v1/responses follows the OpenResponses format. It accepts input and instructions, supports your own function tools, and lets you continue a thread with previous_response_id.
curl http://127.0.0.1:18789/v1/responses \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "openclaw/default",
"instructions": "Answer in two sentences.",
"input": "Summarize my unread messages from today."
}'To follow up in the same session, pass the id from the previous reply as previous_response_id.
Invoke a tool directly
POST /tools/invoke runs a single tool without a chat turn. It is on by default and obeys the same tool policy as your agents. High-risk tools such as exec, shell, fs_write and fs_delete are always blocked here.
curl http://127.0.0.1:18789/tools/invoke \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"tool": "sessions_list", "action": "json", "args": {}}'Optional fields: agentId, sessionKey, idempotencyKey and dryRun. Use "dryRun": true to check a call before it runs.
Trigger OpenClaw with webhooks
Webhooks let services like GitHub, Stripe or a form tool start an agent turn. They use their own token, separate from the gateway token, so a leaked webhook secret can't be used to reach the rest of the API.
{
hooks: {
enabled: true,
token: "<long-random-hook-token>",
path: "/hooks",
allowedAgentIds: ["main"],
allowRequestSessionKey: false,
},
}Send the hook token as Authorization: Bearer or x-openclaw-token. Tokens in the query string (?token=) are rejected.
curl http://127.0.0.1:18789/hooks/agent \
-H "Authorization: Bearer $OPENCLAW_HOOK_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-10482" \
-d '{
"message": "New order #10482 received. Send me a one-line summary.",
"name": "Shop orders",
"agentId": "main",
"deliver": true
}'
# Response: {"ok": true, "runId": "..."}curl http://127.0.0.1:18789/hooks/wake \
-H "Authorization: Bearer $OPENCLAW_HOOK_TOKEN" \
-H "Content-Type: application/json" \
-d '{"text": "Nightly backup finished", "mode": "now", "agentId": "main"}'
# Response includes "eventOutcome": "queued" or "coalesced"Useful /hooks/agent fields: message (required), agentId, channel, to, deliver, sessionKey and waitForCompletion. Add an Idempotency-Key header so retries don't run twice.
Common errors
The token is missing or wrong. Check the Authorization: Bearer header and that you used the gateway token, not a model API key or the hook token.
Tool policy blocked the call, or the agent isn't allowed. Check tools.allow and, for webhooks, allowedAgentIds.
The endpoint isn't enabled in gateway.http.endpoints, or the tool you named isn't available.
The JSON body is invalid or a required field is missing, like messages or message.
The gateway isn't running or is bound to a different address. Start it and check the port in your config.
More fixes on our troubleshooting page.
A gateway token is a master key
OpenClaw's docs describe these endpoints as full operator access. Anyone holding a valid token can act as the owner of your assistant, so protect it like a password to your whole computer.
- Keep the gateway on loopback, a tailnet or private network. Never expose it straight to the internet.
- Use a long random token and rotate it if it might have leaked.
- Give webhooks their own hook token and limit
allowedAgentIds. - Only enable the HTTP endpoints you actually use.
- Never commit tokens to Git or paste them into shared chats.
- Use
wss://and TLS when connecting remotely.
Read the full OpenClaw security guide.
OpenClaw API questions
Does OpenClaw have an API?
Yes. The OpenClaw gateway serves OpenAI-compatible HTTP endpoints, including /v1/chat/completions and /v1/responses, plus /tools/invoke, inbound webhooks and a WebSocket protocol. All run on the gateway port, 18789 by default.
Where do I find my OpenClaw API key?
OpenClaw uses a gateway token rather than an API key. Generate one with openclaw doctor --generate-gateway-token, or set your own with openclaw config set gateway.auth.token. It is stored in ~/.openclaw/openclaw.json.
Can I use the OpenAI SDK with OpenClaw?
Yes. Point the SDK's base URL at http://127.0.0.1:18789/v1, pass your gateway token as the API key, and set the model to openclaw/default or openclaw/<agentId>.
Why do I get a 404 from /v1/chat/completions?
The endpoint is off by default. Set gateway.http.endpoints.chatCompletions.enabled to true in your config and restart the gateway.
Is it safe to expose the OpenClaw API to the internet?
No. A valid token gives full operator access. Keep the gateway on localhost, a private network or a tailnet, and use a VPN or tunnel for remote access.