API

Your sources, from your own code

Everything the web app does with your sources, a script can do too. Ask a question and stream the answer back, or just measure what it would cost.

Base address

Every path below hangs off this. One address for the browser and for your code — the same one the web app itself uses, so there is no second hostname or certificate to keep working.

https://worclaude.com/api/v1

Authentication

A key on every request. Make one in Settings → API access; it is shown once, and it is the only time we can show it.

curl https://worclaude.com/api/v1/sources \
  -H "Authorization: Bearer wcl_..."

Keep it server-side

A key acts as you. Put it in a browser and you have published it — anyone reading the page can take it and spend against your budget. Call this from your own backend, and if a key does get out, switch it off in Settings; that takes effect immediately.

What a key may do

Pick the narrowest set that does the job. A key for a dashboard has no reason to be able to start turns.

read

List sources, models, chats and messages; download a file

Changes nothing and spends nothing.

measure

Ask what a question would carry

Returns counts, never the answer.

chat

Create chats, run turns, attach files, compact, stop

The one that spends money. Grant it deliberately.

There is no administration scope. The console is reachable only from a signed-in browser, so no key — however broadly scoped, however stolen — can reach it.

Endpoints

Fourteen calls. Each one shows what to send and what comes back, trimmed to the fields you are likely to use.

GET/sourcesread

Your sources

Everything this account may search, with the tools each one offers and whether it is reachable right now. Take the ids from here — a chat is started with ids, not with the names you see in the sidebar.

Request

curl https://worclaude.com/api/v1/sources \
  -H "Authorization: Bearer $WORCLAUDE_TOKEN"

Response

[
  {
    "id": "e1bdd864-3197-4773-837c-8b466ddf5105",
    "label": "Canary Knowledge Base",
    "template_name": "Knowledge base",
    "runtime_mode": "managed",
    "status": "ready",
    "status_detail": "Connected. 3 tools available.",
    "tool_count": 3
  }
]
GET/modelsread

Models you can run

Every enabled model, with what it costs and — the field worth checking — whether this account has the credentials to run it. A model without them will fail when you ask, not when you pick it.

Request

curl https://worclaude.com/api/v1/models \
  -H "Authorization: Bearer $WORCLAUDE_TOKEN"

Response

[
  {
    "id": "ff258132-e20c-4db0-a8b4-d3d82b928ed1",
    "model_id": "claude-haiku-4-5",
    "display_name": "Claude Haiku 4.5",
    "provider_name": "Anthropic",
    "context_window": 200000,
    "input_cost_per_mtok": 1.0,
    "output_cost_per_mtok": 5.0,
    "requires_user_credentials": true,
    "user_has_credentials": true
  }
]
POST/chatschat

Start a chat

A chat holds the history and decides which sources are in play. Make one per conversation and keep the id; every message goes to it.

Request

curl -X POST https://worclaude.com/api/v1/chats \
  -H "Authorization: Bearer $WORCLAUDE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Support bot",
    "model_definition_id": "ff258132-e20c-4db0-a8b4-d3d82b928ed1",
    "connection_ids": ["e1bdd864-3197-4773-837c-8b466ddf5105"]
  }'

Response

{
  "id": "16a46d17-faef-44cd-be5b-78bdb56a28d6",
  "title": "Support bot",
  "model_label": "Anthropic · Claude Haiku 4.5",
  "sources": [],
  "message_count": 0,
  "created_at": "2026-09-10T08:53:47.503890Z"
}
POST/chats/{id}/messageschat

Ask, and stream the answer

The one call that spends money. The reply is server-sent events on the same connection — see Streaming a turn below for what arrives and in what order.

Attach files by uploading them first and naming the ids here as attachment_ids. Check the model can read them: a model whose input_media is empty takes no files at all, and one that does not list the file's kind is refused before the message is stored rather than failing halfway through an answer.

Request

curl -N -X POST \
  https://worclaude.com/api/v1/chats/$CHAT_ID/messages \
  -H "Authorization: Bearer $WORCLAUDE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"content": "What changed in the deployment guide?"}'

Response

data: {"type": "turn.start", "seq": 1, "model": "Anthropic · Claude Haiku 4.5"}
data: {"type": "sources", "sources": [{"label": "Canary Knowledge Base", "ok": true}]}
data: {"type": "tool.start", "name": "search", "source_label": "Canary Knowledge Base"}
data: {"type": "tool.end", "name": "search", "ok": true, "duration_ms": 412}
data: {"type": "text.delta", "text": "The deployment guide "}
data: {"type": "text.delta", "text": "gained a section on rollbacks."}
data: {"type": "usage", "input_tokens": 6019, "output_tokens": 128}
data: {"type": "turn.end", "latency_ms": 3184, "error": null}
data: [DONE]
GET/chats/{id}/messagesread

The history

Every turn in order, including what each source returned and what the turn cost. Useful for rebuilding a conversation after a restart, and for auditing what a source actually said.

Request

curl https://worclaude.com/api/v1/chats/$CHAT_ID/messages \
  -H "Authorization: Bearer $WORCLAUDE_TOKEN"

Response

[
  {
    "id": "0a3f…",
    "role": "user",
    "seq": 1,
    "content": "What changed in the deployment guide?"
  },
  {
    "id": "9b21…",
    "role": "assistant",
    "seq": 2,
    "content": "The deployment guide gained a section on rollbacks.",
    "model_snapshot": "Anthropic · Claude Haiku 4.5",
    "cost_usd": "0.004120",
    "latency_ms": 3184
  }
]
GET/chatsread

Your chats

Newest first. Titles are written from the first question unless you set one.

Request

curl https://worclaude.com/api/v1/chats \
  -H "Authorization: Bearer $WORCLAUDE_TOKEN"

Response

[
  {
    "id": "1e585c9b-c2e7-4051-a452-0113f372cb73",
    "title": "What changed in the deployment guide?",
    "model_label": "Anthropic · Claude Haiku 4.5",
    "message_count": 2,
    "last_message_at": "2026-09-10T08:53:00.392896Z"
  }
]
GET/chats/{id}read

One chat

The same shape as a row in the list, for when you have the id already.

Request

curl https://worclaude.com/api/v1/chats/$CHAT_ID \
  -H "Authorization: Bearer $WORCLAUDE_TOKEN"

Response

{
  "id": "16a46d17-faef-44cd-be5b-78bdb56a28d6",
  "title": "Support bot",
  "model_label": "Anthropic · Claude Haiku 4.5",
  "message_count": 4,
  "is_mine": true
}
DELETE/chats/{id}chat

Remove a chat

Takes the history with it, and cannot be undone.

Request

curl -X DELETE https://worclaude.com/api/v1/chats/$CHAT_ID \
  -H "Authorization: Bearer $WORCLAUDE_TOKEN"

Response

{ "ok": true }
POST/measuremeasure

What a question would cost

Counts what a question would carry without asking it, which is free and instant. Add "execute": true — and the chat scope — to run it for real and get the billed usage back, still without the answer text.

Request

curl -X POST https://worclaude.com/api/v1/measure \
  -H "Authorization: Bearer $WORCLAUDE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "question": "What changed in the deployment guide?",
    "connection_ids": ["e1bdd864-3197-4773-837c-8b466ddf5105"],
    "execute": false
  }'

Response

{
  "question": "What changed in the deployment guide?",
  "executed": false,
  "model": "Claude Haiku 4.5",
  "tool_definition_tokens": 562,
  "sources": [
    { "label": "Canary Knowledge Base", "tool_count": 3 }
  ],
  "usage": {},
  "error": null
}
POST/chats/{id}/attachmentschat

Attach a file

Upload the file first, then name its id when you send the message. Two steps rather than one multipart request, because a question is usually reworded a few times and the bytes should not cross the wire again each time. The type is read from the file itself, never from the Content-Type you send. Files nobody sends are deleted after six hours.

Request

curl -X POST https://worclaude.com/api/v1/chats/$CHAT_ID/attachments \
  -H "Authorization: Bearer $WORCLAUDE_TOKEN" \
  -F "[email protected]"

Response

{
  "id": "5a65c260-88ef-4dd0-a543-fd693a283ab1",
  "filename": "sales-q3.csv",
  "mime": "text/csv",
  "kind": "document",
  "byte_size": 18422,
  "origin": "upload",
  "url": "/api/v1/chats/16a46d17/attachments/5a65c260",
  "inline": false,
  "preview": ""
}
DELETE/chats/{id}/attachments/{fileId}chat

Take a file back

Only while it is still a draft. Once a message has been sent with the file it is part of the transcript, and removing one thing a question was asked about would leave an answer referring to something nobody can see — delete the chat for that. Files you never send are swept after six hours anyway.

Request

curl -X DELETE \
  https://worclaude.com/api/v1/chats/$CHAT_ID/attachments/$FILE_ID \
  -H "Authorization: Bearer $WORCLAUDE_TOKEN"

Response

{
  "ok": true,
  "message": "File removed."
}
GET/chats/{id}/attachments/{fileId}read

Fetch a file

Files a turn produced as well as ones you uploaded — a chart a source returned, or a CSV the model wrote. The bytes are encrypted at rest and decrypted on the way out. Anything that could execute in a browser is served as a download rather than inline, whatever it is called.

Request

curl -L -o chart.png \
  https://worclaude.com/api/v1/chats/$CHAT_ID/attachments/$FILE_ID \
  -H "Authorization: Bearer $WORCLAUDE_TOKEN"

Response

HTTP/1.1 200 OK
Content-Type: image/png
Content-Length: 41028
Content-Disposition: inline; filename="chart.png"
X-Content-Type-Options: nosniff
POST/chats/{id}/compactchat

Compact the history

Fold the older half of a long conversation into a summary so it can keep going. Watch the context block on GET /chats/{id}: when should_compact turns true the prompt is filling up, and this is the way out of it. Costs one summarising turn and is billed like any other. Nothing is deleted — the messages stay in the transcript, they just stop being re-read.

Request

curl -X POST https://worclaude.com/api/v1/chats/$CHAT_ID/compact \
  -H "Authorization: Bearer $WORCLAUDE_TOKEN"

Response

{
  "id": "16a46d17-faef-44cd-be3e-8bd3ba0f2a1b",
  "title": "Weekly orders review",
  "message_count": 18,
  "context": null
}
POST/chats/{id}/stopchat

Stop a turn

Halt the answer being written in this chat. This cancels the call to the model, so you stop paying for tokens within milliseconds rather than when the answer would have finished. Whatever was already written is kept, and the turn is recorded as unpriced — the provider charged for it and never told us how much. Closing the connection does not do this; see the note under Streaming. Answers 200 with ok: false if there was nothing running, which is the common case by the time a stop arrives.

Request

curl -X POST https://worclaude.com/api/v1/chats/$CHAT_ID/stop \
  -H "Authorization: Bearer $WORCLAUDE_TOKEN"

Response

{
  "ok": true,
  "message": "Stopped. What was written has been kept."
}

Streaming a turn

The answer arrives as server-sent events over an ordinary HTTP response, so you can render it as it is written rather than waiting for the end. Each line is a JSON object prefixed with data:, and the stream closes with data: [DONE].

Your codeone requestWorclauderuns the turnPOST /chats/{id}/messagesone connection, held openyour messageturn.starttext.deltatext.deltaturn.end

The events

turn.start
The turn began, and the question that started it.
sources
Which sources came up, and which are unavailable.
text.delta
A piece of the answer. Concatenate these in order.
tool.start / tool.end
A source being consulted, and how it went.
usage
Tokens in and out, once the model reports them.
turn.end
Finished. Carries the total and, if something failed, why.
error
The turn could not run. The stream ends after this.

Read the type on every event and ignore ones you do not know. New kinds get added, and a client that treats an unfamiliar type as an error breaks on a Tuesday for no reason.

If the connection drops

The turn keeps going and the answer is saved. Your process can be restarted, your laptop can sleep, a proxy can give up on a long response — fetch the messages afterwards and the answer is there. This is deliberate: the model has already been paid for by the time the connection breaks, so throwing the result away would charge you for nothing.

Which means closing the connection is not how you cancel. To actually stop a turn, call POST /chats/{id}/stop — that one cancels the model call, so the tokens stop being generated and charged for straight away. A dropped socket cannot mean both "I changed my mind" and "my network died", so stopping is something you say.

Rate limits

Counted per key, not per address — so one noisy integration cannot use up another's allowance.

Requests

120 a minute

Turns

20 a minute

Over either and you get 429. Turns are also checked against your monthly budget before they start; over that and you get 402, which no amount of waiting clears.

When something goes wrong

Every failure is JSON with a code and a message meant to be read by a person.

StatusMeaning
401No key, or one that has been switched off. Do not retry with it.
403The key is valid but lacks the scope this call needs.
402Over the monthly budget. Waiting will not help.
404No such chat, or not yours.
429Too fast. Back off and retry.

A working example

A chat bot in two files — a page, and a small server that holds the key. The browser never sees it: it talks to your server, your server talks to Worclaude, and the stream is piped straight through.

WORCLAUDE_TOKEN=wcl_... node server.mjs
# then open http://localhost:4000

Keep it server-side

Putting the key in the page would work on the first try and publish it to everyone who opens it. There is no way to un-publish a key, so the demo shows the shape that stays safe.

Read the example

Make a key

Settings → API access, in your account.

Open Settings

See it used

Our benchmarking tool talks to this API and is open source — a working example of the whole flow.

worclaude-bench on GitHub