The endpoints the apps use: sign-in, models, chat, conversations and the agent protocol.
The Peridot apps (web, desktop, CLI and mobile) all talk to one HTTP API. This page describes the endpoints they use. It is the contract of the first-party clients: it can change, so for anything you build on it, pin the behaviour you need with tests.
| Base URL | https://api.tryperidot.io/api |
| Format | JSON requests and responses (Content-Type: application/json). |
| Authentication | Authorization: Bearer <token>. Get a token from /auth/login, /auth/register or /auth/google. |
| Streaming | Server-sent events (text/event-stream). |
| Errors | A non-2xx status and { "error": "message" }. Some errors also carry a code. |
Browsers can only call the API from origins it allows (CORS). Native clients and servers are not affected.
POST /auth/login#{ "email": "[email protected]", "password": "••••••••" }Returns { "token": "...", "user": { "id": 1, "email": "[email protected]", "name": "You", "role": "user", "plan": "free" } }.
POST /auth/register#Body { "email", "password", "name" }. The password needs at least 8 characters. Returns the same shape as login.
POST /auth/google#Body { "accessToken": "..." } or { "idToken": "..." }, a Google token issued for the Peridot application. Returns { token, user }.
GET /auth/me#Returns the signed-in user, usage stats, and limits: your plan, daily messages used and allowed, the percentage of the monthly allowance used, and when it resets.
GET /models#{
"models": [
{ "id": "claude-sonnet-5-5", "name": "Claude Sonnet 5.5", "badge": null, "desc": "…", "locked": false }
]
}locked is true for models your plan does not include. See Models and effort.
Messages use the roles user and model (a system role is accepted for instructions).
POST /chat#{
"messages": [{ "role": "user", "content": "Hello" }],
"model": "claude-sonnet-5-5",
"effort": "Medium",
"systemInstruction": "Answer briefly."
}Returns the whole answer: { "text", "model", "usage": { "inputTokens", "outputTokens", "totalTokens" } }.
POST /chat/stream#Same request as /chat, answered as a stream of events:
event: message
data: {"chunk":"Hel"}
event: message
data: {"chunk":"lo!"}
event: done
data: {"done":true}An error event (data: {"error":"…"}) ends the stream on failure.
POST /chat/transcribe#Speech to text: { "audio": "<base64>", "mimeType": "audio/webm" }. Returns { "text": "…" }.
Require a token. They store the history you see in the sidebar.
| Endpoint | Description |
|---|---|
GET /conversations |
{ "conversations": [{ id, title, updated_at }] }, newest first. |
POST /conversations |
Body { "title" }. Returns 201 and { "conversation": { id, title } }. |
GET /conversations/:id |
{ "conversation", "messages": [{ role, content }] }. |
POST /conversations/:id/messages |
Body { "messages": [{ "role": "user" | "model", "content" }] }. Appends messages. |
PATCH /conversations/:id |
Body { "title" }. Renames it. |
DELETE /conversations/:id |
Deletes one conversation. |
DELETE /conversations |
Deletes all of yours. |
POST /chat/agent is one model turn of the coding agent. The server does not run tools: the client does, and calls again with the results. This is the loop every Peridot client implements.
{
"messages": [
{ "role": "user", "content": "How does login work?" }
],
"model": "claude-sonnet-5-5",
"effort": "Medium",
"workspace": "my-project",
"platform": "linux",
"tools": []
}| Field | Description |
|---|---|
messages |
The conversation so far, in content block format: content is a string or an array of blocks (text, tool_use, tool_result). Send back the assistant's blocks exactly as received. |
model, effort |
As in chat. |
workspace |
The project's folder name, shown to the model. |
platform |
The operating system, e.g. linux, win32, darwin. |
tools |
Extra tools (from plugins), each { name, description, input_schema }. Names look like plugin__tool. |
team, actor, caller, depth |
AI team fields, below. |
Requires a signed-in user. The agent has the six built-in tools described in Tools and approvals.
A stream:
event: message
data: {"chunk":"Let me look at the code. "}
event: done
data: {"done":true,"stopReason":"tool_use","content":[
{"type":"text","text":"Let me look at the code. "},
{"type":"tool_use","id":"toolu_01","name":"search","input":{"pattern":"login"}}
]}message events carry text as it is generated. The final done event carries the complete content blocks and the stopReason:
tool_use: the model wants tools run. Run every tool_use block, then call again with messages extended by the assistant message (content as received) and a user message whose content is the matching tool_result blocks:
{ "role": "user", "content": [
{ "type": "tool_result", "tool_use_id": "toolu_01", "content": "src/auth.ts:12: …" }
] }Add "is_error": true to a result that failed.
anything else (end_turn): the model is finished.
An error event ends the stream with { "error": "…" }.
To let the agent hand work to bots, send the project's team with every call:
| Field | Description |
|---|---|
team |
{ "lead": { "delegates": [ids] }, "bots": [{ id, name, role, prompt, tools, delegates }] }, as in the team file. The server validates it and drops what is invalid. |
actor |
The id of the bot this call is for. Omit it (or send null) for the lead. |
caller |
Who gave the bot its task: "lead" or a bot id. |
depth |
0 for the lead, plus one for every hand-off. Values above 2 are capped. |
With a team and no actor, the lead's tool list gets one extra tool:
{
"name": "delegate",
"input_schema": {
"type": "object",
"properties": {
"bot": { "type": "string", "enum": ["researcher", "reviewer"] },
"task": { "type": "string" }
},
"required": ["bot", "task"]
}
}and its system prompt lists the bots it may use. With an actor, the server builds that bot's system prompt from its name, role and instructions, and offers only its allowed tools (plus delegate if it has bots to hand work to and depth is below 2). An unknown actor is rejected with 400.
The client runs the hand-off: when the lead's done event contains tool_use blocks named delegate, it starts a new conversation for that bot, with only the task as its first message, sets actor, caller and depth + 1, runs the same loop, and returns the bot's final text as the tool_result of the delegate call. See How hand-offs work.
| Endpoint | Description |
|---|---|
GET /plugins |
{ "plugins": [...] }, the approved plugins. |
GET /plugins/:name |
One approved plugin. |
| Status | code |
Meaning |
|---|---|---|
400 |
The request is invalid, for example an empty messages. |
|
401 |
auth |
Sign in first (agent endpoint). |
403 |
model_not_allowed |
The model is not included in your plan. |
413 |
input_too_large |
The conversation is too long for your plan. |
413 |
too_long |
The agent task grew past your plan's request size. Start a new one. |
429 |
daily_limit |
Today's message limit is reached. Resets at midnight UTC. |
429 |
monthly_limit |
The monthly allowance is used up. Resets on the 1st. |
429 |
Too many requests (rate limit). Wait and retry. | |
500, 502 |
Something went wrong on our side or with the model provider. |
See Accounts and plans for the limits per plan.