Thoute has a public HTTP and WebSocket API so you can build your own integrations and automations against your vault: a cron job that writes a daily journal entry, a script that archives attachments, a dashboard that reports your task counts, or an AI agent connected through the Thoute MCP server.
Access uses long-lived API keys that you create in the app. This guide walks through creating a key, authenticating, scopes, rate limits, every endpoint with examples, and how to work with the encrypted data the API returns.
Your vault is end-to-end encrypted. The server only ever stores ciphertext, and an API key does not change that. The text of your blocks, your page titles, and your attachments are all encrypted with a key derived from your passphrase, and that passphrase never leaves your devices.
So an API key lets a program reach your vault, but the content it receives is ciphertext. To read the actual text, the program has to decrypt it locally with your passphrase. See Working with encrypted data below.
What this means in practice:
1. Create a key. Verify your email if you have not already (key creation is only available to verified accounts), then open Settings → Integrations, click New API key, give it a name, pick its scopes, and optionally limit it to one vault or set an expiry.
2. Copy the key. The full key (thoute_pk_…) is shown only once and is never recoverable. The server stores only a hash of it. If you lose it, revoke it and make a new one.
3. Make your first request. List your vaults:
curl https://app.thoute.com/api/sync/vaults \
-H "Authorization: Bearer thoute_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
{
"vaults": [
{
"id": "vault-1771087410473-racgxme76",
"name": "John Brain",
"keystore": { "version": 1, "salt": "…", "iv": "…", "ciphertext": "…" },
"lastSyncedAt": "2026-05-23T17:09:00.007Z",
"isDefault": false,
"epoch": 7
}
]
}
That id is the vaultId you pass to the other endpoints.
Send the key as a Bearer token on every request:
Authorization: Bearer thoute_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
The base URL is https://app.thoute.com. The same header authenticates the real-time sync WebSocket; the WebSocket also accepts the key as a token query parameter.
You can revoke a key at any time from Settings → Integrations. A revoked key stops working on its next request.
Each key carries one or more scopes. A request that lacks the scope an endpoint requires is rejected with 403 and code: "INSUFFICIENT_SCOPE". Grant the narrowest set your integration needs.
| Scope | Grants |
|---|---|
read:vault |
Read encrypted vault data: vault list, keystore, attachments, and read-only sync. |
write:vault |
Create and update content, upload encrypted attachments, and write over sync. |
read:account |
Read account usage and storage stats. |
A key can be bound to a single vault. A bound key only ever sees that vault: the vault list is filtered to it, and any request aimed at a different vault is rejected with 403 and code: "VAULT_SCOPE_MISMATCH". A key with no vault binding can reach every vault on your account. Binding a key to one vault is a good way to limit the blast radius of a key you hand to a third-party script.
Each key is limited to 1000 requests per minute. Every response carries the standard headers so you can pace yourself:
RateLimit-Policy: 1000;w=60
RateLimit: limit=1000, remaining=995, reset=42
reset is the seconds until the window refills. When you exceed the limit the API returns 429 with a Retry-After header.
Errors are JSON with a human-readable error and, where useful, a machine-readable code:
{ "error": "Missing required scope: write:vault", "code": "INSUFFICIENT_SCOPE" }
| Status | Meaning | Codes you may see |
|---|---|---|
400 |
The request was malformed (missing or invalid body). | |
401 |
The key is missing, invalid, expired, or revoked. | |
403 |
The key is valid but not permitted. | INSUFFICIENT_SCOPE, VAULT_SCOPE_MISMATCH, ACCOUNT_SUSPENDED |
404 |
The vault, attachment, or resource does not exist. | |
429 |
Rate limit exceeded. |
For privacy, every authentication failure returns the same generic 401, so the API never reveals whether a particular key exists.
The base URL is https://app.thoute.com. Everything that touches vault content returns ciphertext (see Working with encrypted data).
GET /api/sync/vaults · scope read:vault
Returns your vaults, each with its encrypted keystore. A vault-bound key returns only its vault.
curl https://app.thoute.com/api/sync/vaults \
-H "Authorization: Bearer $KEY"
{
"vaults": [
{
"id": "vault-1771087410473-racgxme76",
"name": "John Brain",
"keystore": { "version": 1, "salt": "…", "iv": "…", "ciphertext": "…" },
"lastSyncedAt": "2026-05-23T17:09:00.007Z",
"isDefault": false,
"epoch": 7
}
]
}
GET /api/sync/keystore?vaultId={vaultId} · scope read:vault
Returns the encrypted keystore for one vault. You decrypt this with your passphrase to derive the vault key (see below).
curl "https://app.thoute.com/api/sync/keystore?vaultId=vault-1771087410473-racgxme76" \
-H "Authorization: Bearer $KEY"
{
"keystore": { "version": 1, "salt": "…", "iv": "…", "ciphertext": "…" },
"vaultId": "vault-1771087410473-racgxme76",
"updatedAt": "2026-05-23T17:09:00.007Z"
}
GET /api/sync/vault-status/{vaultId} · scope read:vault
Reports whether a vault exists on the server and when it last synced. Useful as a lightweight health check before connecting the sync WebSocket.
{
"existsOnServer": true,
"syncEnabled": true,
"vaultName": "John Brain",
"resetEpoch": 7,
"lastSyncedAt": "2026-05-23T17:09:00.007Z",
"lastSyncedDevice": "MacBook Pro",
"lastSyncedDeviceId": "…"
}
GET /api/sync/attachments/{vaultId}/{filename}/encrypted · scope read:vault
Returns the encrypted attachment as a JSON payload. The filename is the opaque id stored in the attachment block (not the original file name). Decrypt the payload with the vault key to recover the file bytes.
{ "v": 1, "iv": "…", "data": "…" }
POST /api/sync/attachments/{vaultId}/encrypted · scope write:vault
Uploads an already-encrypted payload. Encrypt the file with the vault key on your side, then send the ciphertext. extHint is an optional file-type hint used only to label the stored blob.
curl -X POST "https://app.thoute.com/api/sync/attachments/$VAULT/encrypted" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{ "payload": { "v": 1, "iv": "…", "data": "…" }, "extHint": "pdf" }'
{ "filename": "a1b2c3d4.pdf.enc", "uploadedAt": "2026-05-23T17:30:00.000Z", "encrypted": true }
GET /api/account/stats · scope read:account
{
"createdAt": "2026-01-16T15:21:57.630Z",
"lastSeenAt": "2026-05-23T17:08:06.881Z",
"vaultCount": 4,
"attachmentCount": 11,
"totalStorageBytes": 1657711
}
The vault list and keystore endpoints hand you a small JSON object shaped like { version, salt, iv, ciphertext }. That is your keystore: the vault key, encrypted under your passphrase.
The decryption chain is:
salt using PBKDF2.ciphertext (with its iv) to recover the vault key.{ v, iv, data }), and anything else the API returns for that vault.Implementing this by hand is involved, and getting it wrong is easy. Unless you have a specific reason to roll your own, use the Thoute MCP server, which performs the whole chain for you and exposes plain read and write tools. For the details of the scheme, see Privacy.
Vault content lives in CRDT documents synced over a WebSocket at wss://app.thoute.com/yjs, the same channel the Thoute apps use. This is how an integration reads and writes blocks and pages (as opposed to attachments, which use the HTTP endpoints above).
Authorization: Bearer header or a token query parameter.vault-{vaultId}-global and vault-{vaultId}-page-{pageId}.read:vault is required to connect. Without write:vault the connection is read-only: the server sends you changes but rejects any writes you send.The document contents are encrypted, so the same client-side decryption applies. The simplest way to consume this is the MCP server, which wraps both the HTTP API and the sync channel.
The Thoute MCP server is a small program you run on your own machine. You give it an API key and your passphrase, and it exposes your vault to any MCP-aware client (such as Claude Desktop) as a set of natural tools: search your notes, read a page, append to today's journal, and so on. Because it runs locally and holds your passphrase, it does the decryption the cloud never can, which keeps the zero-knowledge guarantee intact.
It is the recommended way to connect Thoute to AI agents, and the reason the API exists in its current shape.
Exposed tools include search_vault (hybrid keyword and on-device semantic search), get_block, get_page, list_today_journal, and summarize_recent, plus opt-in create_block and update_block. Search runs locally over your decrypted notes using the same embedding model as the app, so your content never leaves your machine.
It's published as @thoute/mcp on npm, so there's nothing to install — your MCP client runs it with npx @thoute/mcp. To set it up: create a key in Settings → Integrations, then add a thoute server to your client's MCP config with command: "npx", args: ["-y", "@thoute/mcp"], and your key, passphrase, and (optional) vault id in the env block. Writes are off by default and require both a write:vault key and an explicit opt-in (THOUTE_MCP_ALLOW_WRITE=1). Full details are in the mcp/ README in the Thoute repository.
The Create key button is greyed out. Key creation requires a verified email address. Verify your email, then reload Settings.
I lost my key. Keys are shown only once and cannot be recovered. Revoke the old one in Settings → Integrations and create a new one.
A request returns 403 with INSUFFICIENT_SCOPE. The key does not carry the scope that endpoint needs. Create a key with the right scopes, or widen an existing one by making a new key (scopes are fixed at creation).
A request returns 403 with VAULT_SCOPE_MISMATCH. The key is bound to a different vault than the one in the request. Use a key bound to that vault, or an unbound key.
Why is the content gibberish? That is expected. The API returns ciphertext. See Working with encrypted data.