Errors
Every refusal has a stable code you can branch on.
On this page
- Error shape
- Diagnostic ID
- How to handle
- Errors every call can get
- All codes
- Next
Error shape
Integration API
{
"code": "stale_block",
"message": "Someone changed the same part of the note. Read it again.",
"retryable": false,
"diagnostic_id": "9d2e6c41-3b7a-4f05-8e1d-6a0c2f9b5e73",
"details": {
"content_revision": 8,
"current": [{"type": "paragraph", "content": [{"type": "text", "text": "Ship on Monday."}]}]
}
}
details.current holds the current content of each part that both you and someone else changed.
code: stable and machine-readable. Branch on it, never on message.message: a short explanation for a person. Its wording can change.retryable: true when the same request may succeed later. Never a reason to repeat a write with a new Operation-Id.diagnostic_id: quote it to support.details: more about some codes, listed with each code below.
429 and 503 also carry a Retry-After header in seconds.
MCP server
A refused tool call answers a result with isError: true and the same codes, as JSON in its text content:
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"isError": true,
"content": [
{
"type": "text",
"text": "{\"status\": \"rejected\", \"code\": \"stale_block\", \"message\": \"Someone changed the same part of the note. Read it again.\", \"details\": {\"content_revision\": 8, \"current\": [{\"type\": \"paragraph\", \"content\": [{\"type\": \"text\", \"text\": \"Ship on Monday.\"}]}]}}"
}
]
}
}
The JSON is {"status": "rejected", "code", "message", "details"}: it has no retryable and no diagnostic_id. A rate limit carries details.retry_after. A scope miss is unavailable on MCP, where the API answers 404.
Endpoint URLs
POST /in/{capability} answers with outcome (accepted, duplicate, ignored, blocked, failed or rejected) and a diagnostic_id, which is also the X-Request-ID header. A refusal adds code and message. See Endpoint URLs.
Diagnostic ID
Every API and endpoint URL error carries a diagnostic_id. Quote it when you contact support at https://thread.page/contact or [email protected].
Contact support
How to handle
How to handle| Status | Means | What to do |
|---|
400 | The request is malformed: bad JSON, content in no supported form, a token in the query string, or lossy_markdown. | Fix the request. Sending it unchanged fails again. |
|---|
401 | The token is missing, wrong, revoked, replaced or retired, or its term ended. | For token_expired, ask the owner to choose Renew. Otherwise ask the owner for a new token. |
|---|
403 | The level, the plan or the owner’s settings don’t allow it. | Read code. For a level refusal, give the owner details.manage_url. |
|---|
404 | Not found within the connection’s scope. | Don’t guess other IDs. Ask the owner whether the item is in scope. |
|---|
409 | A conflict with the current state. | Read code: some need a fresh read, some a confirmation, some the owner. |
|---|
410 | A pull feed cursor expired. | Continue from details.resync_cursor. |
|---|
413 | The content or the body is too large, or the workspace’s storage is full. | Split the note, or send a smaller image. For attachment_quota_exceeded, tell the person. |
|---|
415 | Not sent as JSON (or, for an image, as multipart/form-data). | Fix the Content-Type. |
|---|
422 | A parameter or header has an unsupported value, or a write has no Operation-Id. | Fix what the message names. |
|---|
429 | A rate limit or the monthly Free quota. | Wait Retry-After seconds and retry with the same Operation-Id. quota_exhausted has no Retry-After: stop until details.resets_at. |
|---|
503 | ThreadPage is busy or the call took too long. | Wait Retry-After seconds and retry with the same Operation-Id. |
|---|
Errors every call can get
Any call with a token can answer these, whatever it does. Each operation lists its own codes under its block.
Errors every call can get| Code | HTTP | When | What to do | Retryable |
|---|
authentication_required | 401 | No token was sent. | Send Authorization: Bearer tp_…. | No |
|---|
invalid_token | 401 | The token is wrong, revoked or replaced, or it was retired: the owner changed their password, or reset or restored the workspace. /mcp also answers it for a token that only answers its connection's requests. | Ask the owner for a new token. | No |
|---|
token_expired | 401, 409 | The token's term ended, or it went 90 days without a call: details.reason is term or inactivity, details.expired_at when. An endpoint URL answers 409 with outcome blocked. | Ask the owner to renew it in Settings → Connections; the same token then works again. | No |
|---|
invalid_request | 400, 422 | A parameter, field or header is missing or has an unsupported value (a write without Operation-Id too), or the token was sent in the query string. | Fix what the message names. Sending it unchanged fails again. | No |
|---|
rate_limited | 429 | A technical limit or a change limit; details.reason names it and details.scope whose it is. | Wait Retry-After seconds (MCP: details.retry_after) and send the identical request. | Yes |
|---|
timed_out | 503 | The call took longer than 10 seconds, or a file's text or preview is still being made. | Wait Retry-After seconds and send the identical request. | Yes |
|---|
database_busy | 503 | ThreadPage is busy. | Wait Retry-After seconds and send the identical request. | Yes |
|---|
All codes
Each code has its own anchor, #error-<code>, so you can link to it.
Authentication
Errors| Code | HTTP | When | What to do | Retryable |
|---|
authentication_required | 401 | No token was sent. | Send Authorization: Bearer tp_…. | No |
|---|
invalid_token | 401 | The token is wrong, revoked or replaced, or it was retired: the owner changed their password, or reset or restored the workspace. /mcp also answers it for a token that only answers its connection's requests. | Ask the owner for a new token. | No |
|---|
token_expired | 401, 409 | The token's term ended, or it went 90 days without a call: details.reason is term or inactivity, details.expired_at when. An endpoint URL answers 409 with outcome blocked. | Ask the owner to renew it in Settings → Connections; the same token then works again. | No |
|---|
oauth_required | 403 | A token on Edit and organize was sent to /mcp, which takes that level only through OAuth; /mcp answers it before any method, the Integration API keeps the level. | Connect the assistant to /mcp and sign in instead of using the token, or ask the owner to lower the token's level in Settings → Connections. | No |
|---|
reconnect_required | 403, 409 | An assistant's sign-in ended or was retired (the owner changed their password, or reset or restored the workspace); at an endpoint URL, its connection's token was retired (409 with outcome blocked). | The owner reconnects the assistant, or replaces the custom connection's token. | No |
|---|
Access
Errors| Code | HTTP | When | What to do | Retryable |
|---|
read_not_allowed | 403 | Reading, or subscribing to events, on Add only or Add to Inbox. | Tell the person and give them details.manage_url. | No |
|---|
write_not_allowed | 403, 409 | Adding on Read only, or receiving requests below Read and add (details.needs and details.manage_url); an assistant whose sign-in holds no write scope; a connection whose access leaves out the Inbox thread. An endpoint URL answers 409 with outcome blocked. | Give the person details.manage_url where there is one; otherwise tell them. | No |
|---|
edit_not_allowed | 403 | Changing a note this connection did not add, or a reminder it did not set, on Read and add; any change below Read and add. | Add a new note instead, or give the person details.manage_url. | No |
|---|
organize_not_allowed | 403 | Threads, folders, pin or Done below Edit and organize. | Give the person details.manage_url. | No |
|---|
unavailable | 404 | Not found within this connection's scope. | Don't guess other IDs; ask the person whether the item is in scope. | No |
|---|
not_found | 404 | No such request, subscription, delivery, endpoint, reminder or operation for this connection. | Check the ID; it may have expired or been deleted. | No |
|---|
connection_unavailable | 409 | The connection can't act now: its token is not current, or its state stops it. | Tell the owner. | No |
|---|
connection_paused | 409, 503 | The owner paused this connection: every call but GET /connection (get_access) and GET /operations/{id} (get_operation) waits for Resume, which keeps its token. An endpoint URL answers 503 and keeps the event's receipt. | Tell the person they can resume it in Settings → Connections (details.manage_url); don't retry until then. An endpoint's sender retries. | No |
|---|
A level refusal carries details.needs, the level value the call needs, and details.manage_url, where the owner changes only the level.
Content
Errors| Code | HTTP | When | What to do | Retryable |
|---|
invalid_request | 400, 422 | A parameter, field or header is missing or has an unsupported value (a write without Operation-Id too), or the token was sent in the query string. | Fix what the message names. Sending it unchanged fails again. | No |
|---|
invalid_json | 400 | The body is not a JSON object, or it nests too deep. | Send a JSON object. | No |
|---|
invalid_content | 400 | Markdown or text the converter can't take, such as too many lines. | Fix the content or split it into several notes. | No |
|---|
invalid_document | 400 | A document that breaks the RichDocument v4 schema. | Fix the document; the formatting guide lists every element. | No |
|---|
invalid_link | 400 | A thread description link without href, or with a label that is not text or null. | Send {href, label}. | No |
|---|
invalid_rule | 400 | A reminder rule ThreadPage can't take, or a one-time reminder already past. | Fix the rule as the reminder routes describe it. | No |
|---|
lossy_markdown | 400 | Markdown or text would drop formatting the note holds. | Read the note and send its document with your change. | No |
|---|
content_too_large | 413 | A note over 256 KiB, Markdown over 32 KiB, an image or an MCP file over 5 MB, or a body over 1 MiB. | Split the note, or send a smaller image. | No |
|---|
unsupported_content | 413, 422 | A document over 256 KiB, or a file or image with no preview or text. | Split the note; for a file, tell the person. | No |
|---|
unsupported_media_type | 415 | Not sent as JSON (an image: not multipart/form-data). | Fix the Content-Type. | No |
|---|
Conflicts
Errors| Code | HTTP | When | What to do | Retryable |
|---|
stale_block | 409 | Someone changed the same part of the note since your read. | Read the note again and apply your change again. | No |
|---|
reread_required | 409 | The read your edit is based on is out of date, or belongs to another connection. | Read the note again and apply your change again. | No |
|---|
context_changed | 409 | A list cursor is older than an hour, or the owner changed the connection's access since. | Start the list again without a cursor. | No |
|---|
preview_changed | 409 | The note changed since the read a version restore is based on. | Read the note again, then restore. | No |
|---|
operation_id_reused | 409 | The Operation-Id (MCP: operation_id) was already used with another body. | If you are repeating a lost write, read GET /operations/{id} first. Otherwise use a new ID. | No |
|---|
request_note_locked | 409 | A note written for a request: it never changes. | Add a new note instead. | No |
|---|
request_open | 409 | The note, or a note in the thread or folder, is with a recipient. | Wait until its request ends, or ask the owner. | No |
|---|
shared_place_write | 409 | A thread or folder shared with people where the owner has not allowed this connection to write; a request there ends with it before its run. | Tell the owner: they choose Allow here in ThreadPage. | No |
|---|
confirmation_required | 409 | Moving a folder, moving it to Trash or deleting it alone when it holds threads. | Show the counts to the person, then send the same call with details.confirm within 10 minutes. | No |
|---|
declined | MCP only | The person did not accept the confirmation question. | Don't repeat the call; ask the person. | No |
|---|
reminder_exists | 409 | The person already has a reminder there and replace was not set. | Send replace true only when the person asked to change it. | No |
|---|
State
Errors| Code | HTTP | When | What to do | Retryable |
|---|
thread_archived | 409 | The thread is archived, so it takes no new notes. | Unarchive it first, or ask the owner. | No |
|---|
entity_deleted | 409 | The item is in Trash. | Restore it first, or ask the owner. | No |
|---|
parent_deleted | 409 | Its thread or folder is in Trash. | Restore the thread or folder first. | No |
|---|
target_deleted | 409 | The destination is in Trash. | Restore it, or choose another destination. | No |
|---|
folder_cycle | 409 | A folder can't move inside itself. | Choose another destination. | No |
|---|
folder_name_conflict | 409 | A folder next to it already has this name. | Choose another name. | No |
|---|
Images and files
Errors| Code | HTTP | When | What to do | Retryable |
|---|
image_unavailable | 409 | The note names an image this connection may not use there. | Upload the image to that thread first. | No |
|---|
invalid_attachment | 422 | Not a PNG, JPEG or WebP image within 5 MB, 20 megapixels and 300 frames. | Send a supported image. | No |
|---|
attachment_quota_exceeded | 413 | The workspace's storage is full. | Tell the person. | No |
|---|
attachment_blocked | 403 | The operator blocked this file. | Tell the person. | No |
|---|
attachment_checksum_mismatch | 409 | The stored original does not match its checksum. | Tell the person; quote the diagnostic_id to support. | No |
|---|
attachment_unavailable | 503 | A file's bytes can't be read now. | Wait Retry-After seconds and send the same request again; if it stays, tell the person. | Yes |
|---|
attachments_unavailable | 503 | File storage is unavailable for a moment. | Wait Retry-After seconds and send the same request again. | Yes |
|---|
image_busy | 503 | Another image is being processed. | Wait Retry-After seconds and send the same request again. | Yes |
|---|
Plan and quota
Errors| Code | HTTP | When | What to do | Retryable |
|---|
pro_required | 403 | It needs Pro: on Free a connection adds notes only to the Inbox. | Use POST /inbox/notes (MCP: create_note without thread_id), or tell the owner. | No |
|---|
cloud_required | 403 | It needs the owner's cloud copy: Pro without Sync. | Tell the owner to turn on Sync; adding to the Inbox works meanwhile. | No |
|---|
entitlement_required | 403 | The owner's Pro ended while the call ran. | Tell the owner; adding to the Inbox works on Free. | No |
|---|
quota_exhausted | 429 | Free's 3,000 connection notes this month are used. | Stop until details.resets_at. There is no Retry-After. | No |
|---|
inbox_full | 409 | 1,000 notes or 20 MB wait for the owner's browser to collect them. | Ask the owner to open ThreadPage, then retry later with the same Operation-Id. | No |
|---|
Limits
Errors| Code | HTTP | When | What to do | Retryable |
|---|
rate_limited | 429 | A technical limit or a change limit; details.reason names it and details.scope whose it is. | Wait Retry-After seconds (MCP: details.retry_after) and send the identical request. | Yes |
|---|
reminders_limit | 422 | The thread, or the person, has as many reminders as they may (details.scope). | Tell the person. | No |
|---|
timed_out | 503 | The call took longer than 10 seconds, or a file's text or preview is still being made. | Wait Retry-After seconds and send the identical request. | Yes |
|---|
database_busy | 503 | ThreadPage is busy. | Wait Retry-After seconds and send the identical request. | Yes |
|---|
Subscriptions
Errors| Code | HTTP | When | What to do | Retryable |
|---|
webhooks_not_allowed | 403 | The owner has not turned on Let the script add note-update URLs through the API. | Use a pull feed, or ask the owner. | No |
|---|
content_not_allowed | 403 | A token asked for payload_mode content, or for origins other than human, for a subscription or a sample. | Read note text by ID, or ask the owner to add the webhook in ThreadPage. | No |
|---|
invalid_endpoint | 422 | The webhook URL may not receive deliveries. | Use a public https URL. | No |
|---|
subscription_limit | 409 | The connection already has 10 webhooks and pull feeds. | Delete one first. | No |
|---|
webhook_limit | 409 | The account already has 50 live webhooks. | Delete one first, or ask the owner. | No |
|---|
subscription_paused | 409 | The subscription is paused. | Resume it first. | No |
|---|
subscription_expired | 409 | The webhook's endpoint answered 410, so it stopped. | Create a new subscription. | No |
|---|
paused_by_owner | 409 | The owner paused it, so only the owner resumes it. | Ask the owner. | No |
|---|
not_retryable | 409 | Only the latest failed delivery of an event can be sent again. | Retry that delivery instead. | No |
|---|
content_removed | 409 | The event's content is no longer kept, so its delivery can't be sent again. | Read the note through the API instead. | No |
|---|
delivery_limit | 409 | Resuming while the account's deliveries for the day are used. | Wait until after midnight UTC. | No |
|---|
cursor_expired | 410 | A pull feed cursor older than 7 days, or from another feed. | Continue from details.resync_cursor and read current notes through the API. | No |
|---|
Requests
Errors| Code | HTTP | When | What to do | Retryable |
|---|
not_receiving | 403 | Receiving requests is off for this connection. | Ask the owner to switch on Receive requests. | No |
|---|
already_claimed | 409 | Another run claimed the request with a different claim_id. | Don't start the work. | No |
|---|
not_claimed | 409 | A report or result before the request was claimed. | Claim it first. | No |
|---|
cancellation_requested | 409 | The owner asked to stop. | Send status cancelled (or outcome_unknown), or the result if the work is done. | No |
|---|
request_closed | 409 | The request has ended: completed, cancelled, failed, expired or with an unknown outcome. | Stop; nothing more is accepted. | No |
|---|
Endpoint URLs
Errors| Code | HTTP | When | What to do | Retryable |
|---|
invalid_signature | 401 | The signature or its webhook-id is missing, wrong or older than 5 minutes. | Sign the raw body with the endpoint's secret, with a fresh timestamp. | No |
|---|
secret_unavailable | 401 | The endpoint can no longer check signatures. | Ask the owner to add the endpoint again. | No |
|---|
method_not_allowed | 405 | Not sent with POST. | Send POST. | No |
|---|
endpoint_paused | 503 | The owner paused the endpoint; the event is not kept. | Ask the owner to resume it, then send the event again. | Yes |
|---|
missing_field | 422 | The event lacks a field the template needs. | Send the field, or mark it optional with ? in the template. | No |
|---|
empty_note | 422 | The event makes an empty note. | Send text or markdown. | No |
|---|
Next