The Contract That Actually Exists
OpenClaw is not a collection of REST resources for messages, memories, and dynamically registered Python functions. Its primary integration boundary is a long-running Gateway that exposes typed request, response, and event frames over WebSocket. The Gateway owns channel connections, session state, agent runs, approvals, and connected nodes.
That distinction changes the engineering plan:
- build a stateful client, not a sequence of unrelated HTTP calls;
- complete device identity and pairing, not only bearer-token authentication;
- request explicit operator scopes;
- treat accepted agent work and streamed completion as separate protocol states;
- recover subscriptions and state after reconnect;
- pin both package and wire compatibility.
As of October 3, 2026, the official Gateway client guide identifies @openclaw/[email protected] and @openclaw/[email protected] as the verified stable client packages. Those packages use wire version 4. Package version and wire version are different compatibility dimensions, so pinning only one is insufficient.
For the broader product model, start with the OpenClaw architecture guide. This article stays at the application boundary: how another system should connect without relying on undocumented SDKs or imagined endpoints.
Choose the Right Integration Surface
OpenClaw currently presents two materially different surfaces.
| Surface | Use it for | What the caller must own |
|---|---|---|
| Gateway WebSocket/RPC | chat clients, dashboards, session views, approvals, device or node management, streamed agent runs | connection lifecycle, device identity, pairing, scopes, event ordering, reconnect recovery |
POST /tools/invoke |
narrow server-to-server invocation of an allowed tool | trusted ingress, shared-token protection, application authorization, tool allowlist, audit and timeout policy |
Do not model /tools/invoke as a general OpenClaw REST API. It intentionally rejects dangerous tool classes such as shell execution and filesystem mutation by default. Conversely, do not open a WebSocket merely to emulate stateless HTTP if the real requirement is a tightly bounded internal tool call.
The decision should be based on required semantics:
- Use the Gateway protocol when the caller needs sessions, streaming, subscriptions, approvals, or durable device identity.
- Use
/tools/invokeonly when one trusted service needs a small, reviewed tool surface. - Put an application-owned API in front of either surface when end users or tenants have different permissions.
Gateway Protocol Model
The official Gateway architecture defines text WebSocket frames with three top-level forms:
{"type":"req","id":"req-17","method":"chat.send","params":{"sessionKey":"main","message":"Summarize the incident"}}
{"type":"res","id":"req-17","ok":true,"payload":{"runId":"run-92","status":"accepted"}}
{"type":"event","event":"agent","payload":{"runId":"run-92","phase":"completed"},"seq":481,"stateVersion":73}
The first client frame must be connect. A successful handshake returns hello-ok, including the negotiated snapshot and capability metadata. hello-ok.features.methods helps discovery, but it is not permission: role, approved scopes, pairing, tool policy, and method-specific rules still apply.
Requests Are Not Completion Events
An accepted agent or chat.send response means the Gateway accepted work. It does not prove that:
- the model completed;
- every requested tool ran;
- a channel delivered a message;
- the business action succeeded.
Correlate the returned runId with subsequent events and the authoritative session or task state. Keep these states distinct in your application:
requested -> accepted -> running -> terminal
-> waiting_for_approval
-> failed
-> cancelled
This prevents a common reliability bug: marking a job successful as soon as the RPC acknowledgement arrives.
Model Frames as a Versioned Contract
Even if a Go service uses a WebSocket library rather than the published TypeScript client, it should preserve the same frame discipline:
package main
import (
"encoding/json"
"fmt"
)
type RequestFrame struct {
Type string `json:"type"`
ID string `json:"id"`
Method string `json:"method"`
Params any `json:"params"`
}
type AgentParams struct {
SessionKey string `json:"sessionKey"`
Message string `json:"message"`
IdempotencyKey string `json:"idempotencyKey"`
}
func main() {
frame := RequestFrame{
Type: "req",
ID: "req-17",
Method: "agent",
Params: AgentParams{
SessionKey: "incident-review",
Message: "Summarize the approved incident evidence.",
IdempotencyKey: "incident-2026-10-03-v1",
},
}
payload, err := json.Marshal(frame)
if err != nil {
panic(err)
}
fmt.Println(string(payload))
}
The transport layer should validate inbound frames against the pinned protocol schema, reject unknown incompatible wire versions, bound payload sizes, and preserve structured Gateway errors. Converting every failure to a string discards whether the client should pair, request a scope, retry, or stop.
Authentication, Device Pairing, and Scopes
Authentication answers who can begin connecting. Pairing binds a durable device identity. Scopes constrain what an approved operator can request. These are separate controls.
The documented client lifecycle is:
- Generate and persist an Ed25519 device identity.
- Receive
connect.challengeand sign the challenge-bound device payload. - Send
connectwith the requested role, scopes, and configured bootstrap token or password. - If the Gateway returns
PAIRING_REQUIRED, display the request ID and stop privileged work. - Have an operator review and approve that exact request.
- Reconnect and persist the issued device token with the approved role and scopes.
A role or scope expansion creates a new approval request. Rotating a token does not silently expand authority.
Scope by User Action
| User-visible capability | Minimum relevant scope |
|---|---|
| view history, sessions, model status | operator.read |
| send chat and mutate ordinary sessions | operator.write |
| display or resolve execution/plugin approvals | operator.approvals |
| answer interactive agent questions | operator.questions |
| administer paired devices or nodes | operator.pairing |
| modify administrative configuration | operator.admin |
Requesting every scope because it is convenient defeats the pairing review. A read-only wallboard should not hold approval or admin authority. A chat client that cannot render approval details should not request the ability to approve them.
For node integrations, device pairing and node capability approval are also separate. A paired node cannot expose newly declared commands until that capability expansion is approved and normal command policy permits it.
Reconnect Without Duplicating Side Effects
Networks fail between any two protocol observations. A client may send a request, lose the connection, and never receive the acknowledgement even though the Gateway accepted it.
Use this recovery sequence:
- persist the client request ID, idempotency key, session key, and last observed sequence or state version before sending;
- reconnect with the same durable device identity and device token;
- restore subscriptions;
- fetch authoritative history or session state;
- reconcile the existing run before retrying;
- retry a side effect only with the same idempotency key.
The Gateway requires idempotency keys for side-effecting methods such as send and agent and keeps a short-lived deduplication cache. That cache reduces duplicates; it is not an eternal business ledger. Your application still needs a durable operation ID and a domain-level rule for actions such as sending an invoice or publishing a message.
Do not assume event delivery is exactly once. Use seq and stateVersion when present to detect gaps and stale state, then refresh from the authoritative read API rather than inventing missing events.
Using /tools/invoke Safely
The HTTP endpoint is useful for a bounded internal integration:
curl --fail-with-body \
--request POST \
--header "Authorization: Bearer ${OPENCLAW_GATEWAY_TOKEN}" \
--header "Content-Type: application/json" \
--data '{
"tool": "message",
"action": "send",
"idempotencyKey": "maintenance-2026-10-03-v1",
"args": {
"channel": "telegram",
"target": "ops-room",
"message": "The approved maintenance window has started."
}
}' \
"http://127.0.0.1:18789/tools/invoke"
Treat the example as a contract shape to verify against the pinned release, not a reason to expose port 18789 publicly. The shared Gateway bearer is broad operator authority. Put the endpoint on loopback, a private network, an authenticated service mesh, or an application proxy that enforces:
- caller identity and tenant;
- exact tool and action allowlists;
- bounded argument schemas;
- rate and concurrency limits;
- egress restrictions;
- audit IDs and redaction;
- confirmation for consequential effects.
OpenClaw's endpoint restrictions are defense in depth. They do not replace authorization in the system that knows which user may contact which channel.
Versioning and Upgrade Gate
An integration should record four versions:
| Version | Why it matters |
|---|---|
| Gateway release | runtime behavior and server method surface |
| client package | reconnect, identity, and host integration behavior |
| protocol package/wire version | frame schemas and compatibility |
| application contract | the methods, events, scopes, and error classes your product relies on |
Before an upgrade, replay a small compatibility suite:
- new-device pairing and subsequent reconnect;
- read-only connection with no write access;
- accepted agent run through terminal event;
- lost acknowledgement followed by idempotent recovery;
- approval-required action;
- revoked device token;
- unknown method or incompatible frame;
- event-gap recovery from authoritative state.
Pin exact versions in production. A floating latest tag or package range turns a protocol upgrade into an unreviewed runtime change.
Production Checklist
- Keep the Gateway on loopback or trusted private ingress; use SSH or a VPN for remote administration.
- Store device private keys and tokens in an OS keychain or secret manager, never browser local storage without a threat review.
- Separate read, write, approval, pairing, and admin clients.
- Bound frame size, queue depth, reconnect rate, and event retention.
- Preserve structured error codes and correlation IDs.
- Redact messages and tool arguments before telemetry export.
- Test revocation and recovery, not only the happy-path handshake.
- Review the AI agent tool security guide before connecting business systems.
- Use the OpenClaw workflow guide for scheduled and multi-step automation.
- Use the OpenClaw Docker deployment guide for container operations.
Sources
- OpenClaw Gateway architecture
- Building a Gateway client
- Gateway protocol
- Operator scopes
- Tools invoke endpoint
- Node pairing and capability approval
Conclusion
A reliable OpenClaw integration starts from the Gateway's stateful protocol, not from invented REST resources. Pair devices, request the smallest scopes, distinguish acknowledgement from completion, reconcile state after reconnect, and make side effects idempotent. Use /tools/invoke only as a narrow trusted-service surface, with application authorization in front of it. Those boundaries make the integration testable and keep an AI agent from becoming an unscoped control plane.