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.
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.
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.
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?"}'
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.
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
}'
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.
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.
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.
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"
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].
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.
Status
Meaning
401
No key, or one that has been switched off. Do not retry with it.
403
The key is valid but lacks the scope this call needs.
402
Over the monthly budget. Waiting will not help.
404
No such chat, or not yours.
429
Too 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.