Clatri API
The Clatri API lets you read and change your Clatri data from your own apps, scripts and automations, over HTTPS and JSON. It runs the same tools the Clatri agent and the Clatri MCP use: log an expense, read your income statement, create a task, list your medications…
It uses the same token as the MCP. If you already have one, you don't need another key.
- MCP: when you want an AI assistant (Claude, ChatGPT, Cursor…) to look at your Clatri while you chat with it. See Clatri MCP.
- API: when you write code. Your program decides which tool to call and with what data, and gets JSON back.
How it works
- Base URL:
https://api.clatri.com/v1 - Authentication: an
Authorization: Bearer clt_mcp_XXXXheader on every call. - Everything is a tool. To run one, you
POSTto/v1/entities/{entity_id}/tools/{tool_name}with its arguments as the JSON body. The entity (personal, business, project) always goes in the path.
| Method and path | What it's for |
|---|---|
GET /v1/me | The token: your user, permissions, areas, entities and expiry date. |
GET /v1/entities | The entities this token can use (id, type, name). |
GET /v1/tools | The tools this token can call, with their input schema. |
POST /v1/entities/{entity_id}/tools/{tool_name} | Runs a tool on that entity. |
GET /v1/openapi.json | The full OpenAPI 3.1 specification. It's public: no token needed. |
The full list of tools, with their arguments, is in the API reference.
Requirements
You need an active Clatri subscription
Like the MCP, the API requires an active subscription. Without one you can't create tokens, and existing tokens respond 402 subscription_inactive until you renew. They're not deleted: they start working again on their own when you renew.
You need to call it from a server or a script
The API doesn't accept calls from a browser: it sends no CORS headers. That's on purpose, so nobody puts your token in a web page's code. Call it from a backend, a script, a serverless function or an automation tool.
Step 1 — Create your token
- In the Clatri app, open Settings → Connections → Agents and APIs. On the web, Settings → Agents and APIs.
- Tap Create token and give it a name you'll recognize (for example, "My expenses script").
- Choose the permissions: Read only, Read and write or Full access (includes delete).
- Optionally, limit the areas (Money, Health, Administration, General) and entities it can use. By default it has all of them.
- Tap Create token and copy it.
The token (it starts with clt_mcp_) is shown only once. If you lose it, revoke it and create another. Treat it like a password: anyone who has it can use your data with the permissions you chose.
Tokens expire after 6 months and you can have up to 10 active ones. After creating one, you can change its name, permissions, areas and entities without generating a new one.
In the examples on this page the token is in an environment variable:
export CLATRI_TOKEN="clt_mcp_XXXX"
Step 2 — Your first call
Check that the token works with GET /v1/me:
- curl
- Python
- JavaScript
curl https://api.clatri.com/v1/me \
-H "Authorization: Bearer $CLATRI_TOKEN"
import os
import requests
BASE = "https://api.clatri.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['CLATRI_TOKEN']}"}
me = requests.get(f"{BASE}/me", headers=HEADERS, timeout=30)
me.raise_for_status()
print(me.json())
// Node.js 18 or later (fetch built in)
const BASE = "https://api.clatri.com/v1";
const headers = { Authorization: `Bearer ${process.env.CLATRI_TOKEN}` };
const res = await fetch(`${BASE}/me`, { headers });
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
console.log(await res.json());
The response looks like this (made-up data):
{
"user_id": "11111111-1111-4111-8111-111111111111",
"credential": "personal_token",
"token_name": "My expenses script",
"permissions": ["read", "write"],
"areas": ["money", "administration"],
"entity_ids": null,
"entities": [
{"id": "22222222-2222-4222-8222-222222222222", "type": "personal", "name": "Personal", "is_shared": false},
{"id": "33333333-3333-4333-8333-333333333333", "type": "business", "name": "My company", "is_shared": false}
],
"expires_at": "2027-04-29T15:00:00+00:00",
"request_id": "req_0f1e2d3c4b5a69788796a5b4c3d2e1f0"
}
areas: nullmeans all areas;entity_ids: null, all your entities.is_shared: truemarks an entity someone else shared with you.
Pick the entity
GET /v1/entities returns just the token's entities. Keep the id of the one you'll use:
curl https://api.clatri.com/v1/entities \
-H "Authorization: Bearer $CLATRI_TOKEN"
{
"entities": [
{"id": "22222222-2222-4222-8222-222222222222", "type": "personal", "name": "Personal", "is_shared": false}
],
"request_id": "req_…"
}
Discover the tools
GET /v1/tools lists what this token can do: it's already filtered by its permissions and areas. Each tool comes with its input schema (JSON Schema):
curl https://api.clatri.com/v1/tools \
-H "Authorization: Bearer $CLATRI_TOKEN"
{
"tools": [
{
"name": "create_task",
"title": "Create task",
"description": "Create a task. …",
"area": "administration",
"permission": "write",
"destructive": false,
"supports_dry_run": false,
"requires_confirmation": false,
"requires_idempotency_key": true,
"path": "/v1/entities/{entity_id}/tools/create_task",
"input_schema": {"type": "object", "properties": {"title": {"type": "string"}}, "required": ["title"]}
}
],
"request_id": "req_…"
}
Step 3 — Run a tool
A read
Income statement for September 2026 (what you earned and spent) on the personal entity:
- curl
- Python
- JavaScript
curl -X POST \
https://api.clatri.com/v1/entities/22222222-2222-4222-8222-222222222222/tools/read_income_statement \
-H "Authorization: Bearer $CLATRI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"start_date": "2026-09-01", "end_date": "2026-09-30", "include_analytics": false}'
ENTITY = "22222222-2222-4222-8222-222222222222"
res = requests.post(
f"{BASE}/entities/{ENTITY}/tools/read_income_statement",
headers=HEADERS,
json={"start_date": "2026-09-01", "end_date": "2026-09-30", "include_analytics": False},
timeout=60,
)
res.raise_for_status()
body = res.json()
print(body["text"])
const ENTITY = "22222222-2222-4222-8222-222222222222";
const res = await fetch(`${BASE}/entities/${ENTITY}/tools/read_income_statement`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ start_date: "2026-09-01", end_date: "2026-09-30", include_analytics: false }),
});
const body = await res.json();
console.log(body.text);
Every successful response has the same shape:
{
"data": {"…": "structured result, when the tool returns JSON"},
"text": "The tool's answer as text",
"request_id": "req_6c1d0e2a9b7f4e3c8a5d1f0b2e4c6a8d",
"idempotent_replay": false
}
| Field | What it holds |
|---|---|
data | The result as JSON when the tool returns it. If the tool only answers with text, it's null. |
text | The tool's full answer, as text. Always present. |
request_id | The id of the call. Also sent in the Clatri-Request-Id header. Include it if you contact support. |
idempotent_replay | true when this is the stored response of an earlier call with the same Idempotency-Key (see below). |
Don't send entity_id in the body: it goes in the path. An argument the tool doesn't know is also rejected with 400 validation_error, before anything runs.
A write
Tools that create, edit or delete require the Idempotency-Key header. Creating a task on the personal entity:
- curl
- Python
- JavaScript
curl -X POST \
https://api.clatri.com/v1/entities/22222222-2222-4222-8222-222222222222/tools/create_task \
-H "Authorization: Bearer $CLATRI_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5b0d7f8e-3c2a-4e1b-9f6d-1a2b3c4d5e6f" \
-d '{"title": "Renew the car insurance", "due_date": "2026-11-15"}'
import uuid
res = requests.post(
f"{BASE}/entities/{ENTITY}/tools/create_task",
headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
json={"title": "Renew the car insurance", "due_date": "2026-11-15"},
timeout=60,
)
res.raise_for_status()
print(res.json()["text"])
const res = await fetch(`${BASE}/entities/${ENTITY}/tools/create_task`, {
method: "POST",
headers: {
...headers,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({ title: "Renew the car insurance", due_date: "2026-11-15" }),
});
console.log((await res.json()).text);
Permissions, areas and entities
Every token has three limits. A call that goes beyond any of them is rejected before anything runs.
Permissions. Each tool requires one permission:
| Permission in the API | What it allows | App level that includes it |
|---|---|---|
read | Query | Read only, Read and write, Full access |
write | Create and edit | Read and write, Full access |
delete | Delete or cancel | Full access |
Without the permission → 403 scope_denied.
Areas. Each tool belongs to one area:
| Area in the app | In the API | What it covers |
|---|---|---|
| Money | money | Accounts, cards, movements, debts, savings, investments, sales and accounting |
| Health | health | Conditions, episodes, medications, prescriptions, metrics and health documents |
| Administration | administration | Spaces, pages, boards, tasks, habits, calendar, contacts and places |
| General | general | Context, memory, documents, schedules and activity |
Outside the token's areas → 403 domain_denied.
Entities. If you limited the token to some entities, any other one responds 403 entity_denied. The same happens if the body mentions a record (an account, a task…) from another entity.
A script that only reads expenses doesn't need to write or see your health. Create a Read only token, with the Money area and the right entity. If it ever leaks, the damage stays contained.
Safe writes
Idempotency-Key
Tools with the write or delete permission require the Idempotency-Key header. Without it → 400 idempotency_key_required.
Generate a unique value per operation (a UUID works; 1 to 255 visible ASCII characters) and reuse it when you repeat that same operation, for example after a network drop. That way a retry doesn't log the expense twice:
| Situation | Result |
|---|---|
| Same key and same body, the first call already finished | You get the stored response, with idempotent_replay: true. It doesn't run again. |
| Same key with another body, another tool or another entity | 409 idempotency_key_reused |
| Same key while the first call is still running | 409 idempotency_in_progress, with Retry-After |
- A key belongs to the token that used it and is remembered for 24 hours.
- If the first call ended with a server error (5xx), the key is released and you can retry with the same one.
- Reads don't need a key.
Preview with ?dry_run=true
Some tools can show you the result without saving anything. They're the ones with supports_dry_run: true in GET /v1/tools. Add ?dry_run=true to the URL:
curl -X POST \
"https://api.clatri.com/v1/entities/22222222-2222-4222-8222-222222222222/tools/record_consumable_balance_observation?dry_run=true" \
-H "Authorization: Bearer $CLATRI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"balance_identifier": "Transit card", "observed_amount": 25000, "source": "api"}'
- With
?dry_run=truethe tool only computes (preview: true), whatever the body says. - A preview doesn't need an
Idempotency-Keyand its response isn't stored. - On a tool without a preview,
?dry_run=trueresponds400 dry_run_not_supported: it never runs "for real" by mistake.
Confirm deletes with ?confirm=true
Tools with the delete permission (deleting or cancelling data) require ?confirm=true in the URL, on top of the Idempotency-Key. Without the confirmation → 428 confirmation_required and nothing is deleted:
curl -X POST \
"https://api.clatri.com/v1/entities/22222222-2222-4222-8222-222222222222/tools/delete_task?confirm=true" \
-H "Authorization: Bearer $CLATRI_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9e8d7c6b-5a49-4382-9170-6f5e4d3c2b1a" \
-d '{"task_id": "44444444-4444-4444-8444-444444444444"}'
In GET /v1/tools these tools have requires_confirmation: true. The destructive: true field also flags the ones that overwrite data.
Errors
Errors arrive as application/problem+json (RFC 9457), always with a stable code you can use in your code:
{
"type": "https://docs.clatri.com/api/errors#scope_denied",
"title": "Permission not granted",
"status": 403,
"detail": "This token does not have the permission this tool requires.",
"code": "scope_denied",
"request_id": "req_6c1d0e2a9b7f4e3c8a5d1f0b2e4c6a8d"
}
The wording of title and detail can change: decide with code and status.
| HTTP | Codes |
|---|---|
| 400 | validation_error, idempotency_key_required, dry_run_not_supported, https_required |
| 401 | unauthorized, token_revoked, token_expired |
| 402 | subscription_inactive |
| 403 | scope_denied, domain_denied, entity_denied |
| 404 | tool_not_found, tool_not_available, not_found |
| 405 | method_not_allowed |
| 409 | idempotency_key_reused, idempotency_in_progress |
| 413 | payload_too_large |
| 422 | tool_error |
| 428 | confirmation_required |
| 429 | rate_limited |
| 500 | internal_error |
| 503 | service_unavailable |
What each code means and what to do: API errors.
422 tool_error is the most common day to day: the tool ran but couldn't complete what you asked (for example, an account that doesn't exist). The detail explains why.
Limits
| Limit | Value |
|---|---|
| Reads per token | 120 per minute |
| Writes and deletes per token | 30 per minute |
| Calls per entity (all tokens combined) | 300 per minute |
| Maximum body size | 256 KB |
- Windows are fixed one-minute windows.
GET /v1/me,/v1/entitiesand/v1/toolscount as token reads. - Responses carry
RateLimit-Limit,RateLimit-RemainingandRateLimit-Reset(seconds until the window resets). When several limits apply, the headers show the one closest to running out. - Going over returns
429 rate_limitedwithRetry-Afterin seconds. Wait that long before retrying. - Blocking after repeated failures: 20
401or403responses within 5 minutes, for the same token or the same IP, block that token or IP for 15 minutes (429withRetry-After). If your script gets a401or403, fix the cause instead of retrying in a loop. - Some tools (web search, maps, images) use your account's credits, as when the agent uses them.
Useful headers
| Header | Where | What for |
|---|---|---|
Authorization: Bearer … | Request | Your token. Required except on /v1/openapi.json. |
Idempotency-Key | Request | Required on writes and deletes. |
Content-Type: application/json | Request | On calls with a body. |
Clatri-Request-Id | Response | Id of the call, on every response. |
RateLimit-*, Retry-After | Response | Rate limits (see above). |
The API only works over HTTPS. Responses carry Cache-Control: no-store.
Versioning and deprecation
- The version is in the path:
/v1. A breaking change ships as a new version. - Before a version is retired, its responses carry the
DeprecationandSunsetheaders, with at least 90 days of notice. - Error codes are part of the contract: new codes can appear, but existing ones aren't renamed or removed within
v1. - The tools are the same ones the Clatri agent uses, so the catalog grows with the product. Check
GET /v1/toolsor the reference for the current schema.
Security
Take care of your token
- Never share it or paste it into chats, screenshots, tickets or forums.
- Don't commit it to a repository or put it in the code of a mobile app or a web page. Keep it in an environment variable or a secrets manager.
- One token per integration. That way you can revoke one without affecting the others, and the activity shows what each one did.
- If it leaked, revoke it in Settings → Connections → Agents and APIs. Revocation is immediate. Then create another.
What a token can and can't do
A token only acts inside Clatri, with the permissions, areas and entities you gave it. It can't create other tokens, change your password or email, touch your subscription, delete an entity or see entities outside its scope.
Every tool call is recorded in the token's recent activity (Settings → Connections → Agents and APIs → tap the token), just like MCP calls. If you see something you don't recognize, revoke it.
Tools that aren't available through the API
Some of the agent's tools can't be called through the API even if the token has the permission. They respond 404 tool_not_available and don't appear in GET /v1/tools or in the reference:
- Free-form database queries. They're not a stable, reviewable contract.
- Document passwords. Saving or using a protected PDF's password is credential handling.
- Access management. Inviting or removing members of a space, seeing who else is in it, or linking a contact to another Clatri account: they change who sees your data.
- Looking up other entities or people. A token never sees entities outside its own.
- Schedules. Creating, editing or running a schedule later puts the full agent to work with all your permissions, outside the token's limits.
whoamiandlist_entitiesfrom the MCP: in the API,GET /v1/meandGET /v1/entitiesreplace them.
For these actions, use the Clatri app or chat.
If you connect the API to an AI model
What the API returns is your data: movement descriptions, document text, notes. If you pass it to an AI model, treat it as data, not instructions. Text inside a document could try to give the model orders.
OAuth tokens from connected apps
Besides personal tokens (clt_mcp_), the API accepts the OAuth access tokens (clt_at_) that apps you connected to Clatri through OAuth receive. Those tokens only see the reduced catalog of tools those apps use in the MCP.
API reference
- Interactive reference: every tool, with its arguments, permissions, areas and body examples.
- OpenAPI 3.1:
https://api.clatri.com/v1/openapi.json(public, always current). Import it into Postman, Insomnia or your client generator. - API errors: each code, its cause and what to do.