Agent API quickstart
Quickstart
This walks through one full turn with curl: find your workspace and agent, start a chat, send a
message, and read the reply. You need a workspace with at least one agent, and you need to be a
workspace owner or admin to generate the key. The examples use jq to pick
fields out of responses.
1. Generate a key
- In the sidebar, choose Agents under Workspace settings, then open the API keys tab
(or go straight to
/agents?tab=api-keysin the app). - Choose Generate key.
- Name the key (for example Quickstart), leave the agent on Any agent, and under permissions select Read chats, Send messages, List agents, and Read workspace.
- Choose Generate, then copy the key. It starts with
bc_sk_and is only shown this once.
Put it in your shell so the commands below can use it:
export BETTERCLAW_API_KEY="bc_sk_…"
2. Find your workspace
curl -s https://api.betterclaw.io/workspaces \
-H "Authorization: Bearer $BETTERCLAW_API_KEY"
A key only ever sees its own workspace, so the list has one entry:
[
{
"id": "6f1c2d4e-8a90-4b1c-9d2e-3f4a5b6c7d8e",
"name": "Acme",
"status": "running",
"role": "owner",
"plan": "pro",
"createdAt": "2026-08-01T09:00:00.000Z"
}
]
export WORKSPACE_ID="6f1c2d4e-8a90-4b1c-9d2e-3f4a5b6c7d8e"
3. Pick an agent
curl -s "https://api.betterclaw.io/workspaces/$WORKSPACE_ID/agents" \
-H "Authorization: Bearer $BETTERCLAW_API_KEY" \
| jq '.[] | {id, displayName, locked}'
{ "id": "0b7e9f3a-1c2d-4e5f-8a9b-0c1d2e3f4a5b", "displayName": "Scout", "locked": false }
export AGENT_ID="0b7e9f3a-1c2d-4e5f-8a9b-0c1d2e3f4a5b"
4. Start a chat
curl -s https://api.betterclaw.io/chats \
-H "Authorization: Bearer $BETTERCLAW_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"workspaceId\": \"$WORKSPACE_ID\", \"agentId\": \"$AGENT_ID\", \"title\": \"Hello from the API\"}" \
| jq '{id, agentName, title}'
{ "id": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d", "agentName": "Scout", "title": "Hello from the API" }
export CHAT_ID="9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d"
The chat appears in Chats in the app too, as one of yours.
5. Send a message
curl -s "https://api.betterclaw.io/chats/$CHAT_ID/messages" \
-H "Authorization: Bearer $BETTERCLAW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content": "In three bullet points, what can you help me with?"}' \
| jq '.assistantMessage | {id, status, content}'
The request returns as soon as the message is accepted. The reply is written afterwards, so the
assistant message comes back empty and streaming:
{ "id": "c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f", "status": "streaming", "content": "" }
export MESSAGE_ID="c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f"
6. Read the reply
Fetch the chat until that message is no longer streaming. Its content fills in as the agent
writes, and ends as complete (or error, with an errorMessage):
while :; do
msg=$(curl -s "https://api.betterclaw.io/chats/$CHAT_ID" \
-H "Authorization: Bearer $BETTERCLAW_API_KEY" \
| jq --arg id "$MESSAGE_ID" '.messages[] | select(.id == $id)')
[ "$(echo "$msg" | jq -r .status)" != "streaming" ] && break
sleep 2
done
echo "$msg" | jq -r '.status, .content'
7. Stream instead of polling
To show the reply as it's written, open the chat stream. This Node.js script (using the
ws package) prints the reply to the message you send in step 5
as it arrives. Start it first, then send the message:
import WebSocket from 'ws';
const { BETTERCLAW_API_KEY, WORKSPACE_ID, CHAT_ID } = process.env;
const ws = new WebSocket(`wss://api.betterclaw.io/ws/chats?workspaceId=${WORKSPACE_ID}`, {
headers: { Authorization: `Bearer ${BETTERCLAW_API_KEY}` },
});
let printed = '';
ws.on('message', (data) => {
const frame = JSON.parse(data);
if (frame.chatId !== CHAT_ID) return;
// Live progress arrives as `message_streaming`; the saved reply as `message_upserted`.
const reply =
frame.type === 'message_streaming'
? frame
: frame.type === 'message_upserted' && frame.message.role === 'assistant'
? frame.message
: null;
if (!reply) return;
// `content` is the whole reply so far, not just the new part.
process.stdout.write(reply.content.slice(printed.length));
printed = reply.content;
if (reply.status === 'error' && reply.errorMessage) console.error(`\n${reply.errorMessage}`);
if (reply.status !== 'streaming') ws.close();
});
ws.on('close', (code, reason) => console.log(`\n[closed ${code} ${reason}]`));
See Streaming replies for every event the stream sends.
Next steps
- Building in JavaScript or TypeScript? The JavaScript SDK
(
npm install @better-claw/sdk) does all of the above for you. - Calling from a browser or mobile app? Exchange the key for a session token on your server first.
- Browse every request in the endpoint reference.
- When you're done testing, revoke the quickstart key from the API keys tab.