ThreadPage
All docs

Notes

A note is one entry in a thread: long text, lists, checklists, tables, code, links, images and files.

View as Markdown

On this page
  1. The note object
  2. Add a note
  3. Add a note to the Inbox
  4. Read a note
  5. Search notes
  6. Edit a note safely
  7. Add an image to a note
  8. Read an image preview
  9. Add a file to a note
  10. Read a file
  11. Move a note to Trash and restore it
  12. Restore an earlier version
  13. Pin a note or mark it done
  14. Move a note
  15. In shared threads
  16. If it fails
  17. Next

The note object

GET /notes/{note_id} answers a note in full:

  • text, markdown and document: the whole content in three forms. document is the rich document the editor saves.
  • revision changes with any change; content_revision only when the text changes.
  • context_receipt and editable: what you send to change the note, and whether this connection may change it now. context_receipt is null when it may not.
  • origin and last_change_origin: who added the note and who last changed its text.
  • added_via and changed_via: the connection that added it, and the one that made the latest content change.
  • images and files: previews and descriptions of what the document holds. Originals are not available through connections.
  • pinned, done, trashed, thread, url, created_at and updated_at.

Lists and search answer shorter items with a text_preview. truncated: true means the preview is cut: read the note before you quote it.

Add a note

Add a note at the end of a thread. Send the content in one of three forms:

JSON
{"content": {"text": "Build 412 passed.\n\nDeployed to staging."}}
JSON
{"content": {"markdown": "## Release 412\n\n- [x] Tests passed\n- [ ] Tag the build\n\n| Check | Result |\n| --- | --- |\n| Smoke | OK |"}}
JSON
{
  "content": {
    "document": {
      "format": "work-threads.richtext",
      "schemaVersion": 4,
      "doc": {
        "type": "doc",
        "content": [
          {"type": "heading", "attrs": {"level": 2}, "content": [{"type": "text", "text": "Release 412"}]},
          {"type": "bulletList", "content": [
            {"type": "listItem", "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Tests passed", "marks": [{"type": "bold"}]}]}]},
            {"type": "listItem", "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Deployed to staging"}]}]}
          ]}
        ]
      }
    }
  }
}

A document keeps everything the editor can hold: headings go up to level 3, a list item starts with a paragraph, and every table row has the same number of cells. The formatting guide lists every element.

Add a note to a thread

POST /api/integrations/v1/threads/{thread_id}/notes

Idempotent: repeat the same Operation-Id header and body to get the same receipt; a different body with a used Operation-Id is rejected with 409.

Requirements
RequirementValue
LevelsEdit and organize · Read and add · Add only
PlanPro with Sync. On Free, add to the Inbox instead (POST /inbox/notes).
IdempotencyOperation-Id header: one UUID per logical write, the same on every retry.
MCPcreate_note
Shared threadsOnly where the owner chose Allow here.
Parameters
NameInType
thread_idpathuuid, required
Body (application/json)
FieldType
contenttext, Markdown or a document, required

Answer 201: Accepted, or the same receipt again.

Answer fields
FieldType
operation_iduuid, required
statusaccepted, conflict, not_completed, required
note_iduuid
revisioninteger
accepted_atdate-time
urluri
codestring
curl
OP=$(uuidgen)
curl -m 30 --retry 3 --retry-all-errors -X POST "https://thread.page/api/integrations/v1/threads/$THREAD_ID/notes" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN" \
  -H "Operation-Id: $OP" \
  -H "Content-Type: application/json" \
  -d '{"content":{"text":"Build 412 passed.\n\nDeployed to staging."}}'

Errors: write_not_allowed 403, 409, unavailable 404, invalid_json 400, invalid_content 400, invalid_document 400, content_too_large 413, unsupported_content 413, 422, unsupported_media_type 415, operation_id_reused 409, shared_place_write 409, thread_archived 409, entity_deleted 409, image_unavailable 409.

Also the errors of every call, and pro_required or cloud_required without Pro and Sync.

Add a note to the Inbox

POST /inbox/notes takes the same body and works on every plan. It is the one write a connection has on Free.

  • With Pro and Sync, the note is in the Inbox thread at once: the receipt says "delivery": "account" and has its url.
  • Otherwise it waits for the browser that collects the owner’s Inbox, for up to 30 days: the receipt says "delivery": "inbox" and url is null. That browser keeps the note_id.
  • On Free, connections add up to 3,000 notes a month (UTC), all of them together. Then 429 quota_exhausted answers without Retry-After, and details.resets_at says when the count starts again.
  • Up to 1,000 notes or 20 MB can wait at once. Past that, 409 inbox_full.
  • A note with an image needs Pro and a level above Add to Inbox.

Add a note to the owner's Inbox

POST /api/integrations/v1/inbox/notes

Every plan, and the one write on Free. With Pro and Sync the note goes into the owner's cloud Inbox at once (delivery account); otherwise it waits up to 30 days for the browser that collects the owner's Inbox, which keeps note_id (delivery inbox). Idempotent: repeat the same Operation-Id header and body to get the same receipt, even after the month's notes are used or Pro ended; a different body is rejected with 409. On Free, connections add up to capabilities.free.connection_notes notes a month (UTC): then 429 quota_exhausted without Retry-After, details.resets_at saying when. Images need Pro.

Requirements
RequirementValue
LevelsEdit and organize · Read and add · Add only · Add to Inbox
PlanEvery plan.
IdempotencyOperation-Id header: one UUID per logical write, the same on every retry.
MCPcreate_note
Body (application/json)
FieldType
contenttext, Markdown or a document, required

Answer 201: Accepted, or the same receipt.

Answer fields
FieldTypeDescription
operation_iduuid, required
statusaccepted, required
note_iduuid, requiredThe note's id: the browser that collects it keeps this id.
deliveryinbox, account, requiredinbox: it waits for the browser that collects the owner's Inbox; account: it is in the cloud Inbox already (Pro with Sync).
accepted_atdate-time, required
urluri or null, requiredThe note in ThreadPage; null while it waits.
curl
OP=$(uuidgen)
curl -m 30 --retry 3 --retry-all-errors -X POST "https://thread.page/api/integrations/v1/inbox/notes" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN" \
  -H "Operation-Id: $OP" \
  -H "Content-Type: application/json" \
  -d '{"content":{"text":"Build 412 passed."}}'

Errors: write_not_allowed 403, 409, invalid_json 400, invalid_content 400, invalid_document 400, content_too_large 413, unsupported_media_type 415, operation_id_reused 409, image_unavailable 409, quota_exhausted 429, inbox_full 409.

Also the errors of every call.

Read a note

Read one note in full, or list a thread’s notes, newest first, as previews.

Get a note with its full content

GET /api/integrations/v1/notes/{note_id}

Plain text, Markdown and the rich document (RichDocument v4), with content_revision and context_receipt for a later edit.

Requirements
RequirementValue
LevelsEdit and organize · Read and add · Read only
PlanPro with Sync. On Free, add to the Inbox instead (POST /inbox/notes).
MCPget_note
Parameters
NameInType
note_idpathuuid, required

Answer 200.

Answer fields
FieldTypeDescription
iduuid, required
thread_iduuid, required
threadobject, required
revisioninteger, required
content_revisioninteger, requiredMoves with every change to the text; compare it to tell newer text from older.
created_atdate-time, required
updated_atdate-time, required
pinnedboolean, required
doneboolean, required
trashedboolean, required
originhuman, service, agent, participant, required
last_change_originhuman, service, agent, participant, requiredWho made the latest change to the text, in the same terms as origin.
added_viaobject or or null or null, required
changed_viaobject or or null or null, requiredThe connection whose change is the note's latest content change; null when a person changed it last or nobody changed it since it was added.
editableboolean, requiredWhether this connection may change this note now: its level, Read and add's own notes, shared places and request notes.
textstring, requiredPlain text of the whole note.
markdownstring, requiredThe whole note as Markdown, as events and requests carry it; an image or a note reference becomes a link to the note.
documentobject, requiredRichDocument v4 (format work-threads.richtext).
imagesarray of object, requiredThe images the document holds, in its order; an image whose bytes have not reached the server yet is left out.
filesarray of object, requiredThe other files the document holds, in its order (getNoteFile): those of its file blocks, and a file an image block holds that is no image (such as a TIFF); one whose bytes have not reached the server yet, or that was deleted, is left out.
urluri, required
context_receiptstring or null, requiredSend it with PUT /notes/{note_id}/content or a version restore; null when this connection may not change the note.
curl
curl -m 30 "https://thread.page/api/integrations/v1/notes/$NOTE_ID" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN"

Errors: read_not_allowed 403, unavailable 404.

Also the errors of every call, and pro_required or cloud_required without Pro and Sync.

List notes in a thread, newest first

GET /api/integrations/v1/threads/{thread_id}/notes

Each item carries a text preview; read a note for its full content.

Requirements
RequirementValue
LevelsEdit and organize · Read and add · Read only
PlanPro with Sync. On Free, add to the Inbox instead (POST /inbox/notes).
MCPget_thread
Parameters
NameInTypeDescription
cursorquerystringFrom next_cursor.
limitqueryinteger1–50, default 20.
thread_idpathuuid, required

Answer 200.

Answer fields
FieldType
itemsarray of object, required
next_cursorstring or null, required
curl
curl -m 30 "https://thread.page/api/integrations/v1/threads/$THREAD_ID/notes" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN"

Errors: read_not_allowed 403, unavailable 404, context_changed 409.

Also the errors of every call, and pro_required or cloud_required without Pro and Sync.

Search notes

GET /search?q= finds notes whose text, or whose thread’s title or description, contains a phrase of 1 to 200 characters, ignoring case. It answers notes, not threads.

  • thread_id searches one thread. folder_id searches the threads directly in that folder, not those in its subfolders.
  • state is active (the default), trash or all.
  • limit takes 1 to 50 and defaults to 20.

Search notes by text or thread title or description

GET /api/integrations/v1/search

Requirements
RequirementValue
LevelsEdit and organize · Read and add · Read only
PlanPro with Sync. On Free, add to the Inbox instead (POST /inbox/notes).
MCPsearch
Parameters
NameInTypeDescription
cursorquerystringFrom next_cursor.
folder_idqueryuuidOnly notes of threads directly in this folder.
limitqueryinteger1–50, default 20.
qquerystring, required1–200 characters.
statequeryactive, all, trashDefault active.
thread_idqueryuuid

Answer 200. Its fields are those under List notes in a thread, newest first.

curl
curl -m 30 "https://thread.page/api/integrations/v1/search?q=release" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN"

Errors: read_not_allowed 403, context_changed 409.

Also the errors of every call, and pro_required or cloud_required without Pro and Sync.

Edit a note safely

To change a note, read it, change its document, and send the result with the content_revision and context_receipt of that read. ThreadPage merges your change with edits made since the revision you read; only an overlap is refused. If it is refused, read the note again and apply your change again.

// Edit a note without losing edits made since you read it.
// Run with Node.js 24: THREADPAGE_TOKEN=tp_… NOTE_ID=… node edit_note.mjs
import { randomUUID } from 'node:crypto';
import { setTimeout as sleep } from 'node:timers/promises';

const API = 'https://thread.page/api/integrations/v1';
const AUTH = { Authorization: `Bearer ${process.env.THREADPAGE_TOKEN}` };

// PUT /notes/{note_id}/content. After a lost answer, or a retryable 429 or 503, it sends the
// identical request again with the same Operation-Id.
async function putContent(noteId, body, operationId) {
  for (let attempt = 1; ; attempt++) {
    let answer;
    try {
      answer = await fetch(`${API}/notes/${noteId}/content`, {
        method: 'PUT',
        headers: { ...AUTH, 'Operation-Id': operationId, 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
        signal: AbortSignal.timeout(30_000),
      });
    } catch (error) {
      if (attempt === 3) throw error;
      await sleep(attempt * 2_000);
      continue;
    }
    const wait = Number(answer.headers.get('Retry-After'));
    if ([429, 503].includes(answer.status) && wait && wait <= 60 && attempt < 3) {
      await sleep(wait * 1_000);
      continue;
    }
    return answer;
  }
}

// Apply change(document) -> document without losing edits made since you read the note.
async function editNote(noteId, change, attempts = 3) {
  for (let attempt = 0; attempt < attempts; attempt++) {
    const read = await fetch(`${API}/notes/${noteId}`, { headers: AUTH });
    if (!read.ok) throw new Error(`${read.status}: ${await read.text()}`);
    const current = await read.json();
    if (!current.editable) throw new Error('This connection may not change this note; add a new note instead.');
    const body = {
      content: { document: change(current.document) },
      content_revision: current.content_revision,
      context_receipt: current.context_receipt,
    };
    const answer = await putContent(noteId, body, randomUUID()); // one ID for this write
    if (answer.status === 409) {
      const error = await answer.json();
      if (['stale_block', 'reread_required'].includes(error.code)) continue; // nothing was written: read again
      throw new Error(`409 ${error.code}: ${error.message}`);
    }
    if (!answer.ok) throw new Error(`${answer.status}: ${await answer.text()}`);
    return answer.json(); // the receipt
  }
  throw new Error('The note kept changing; ask the person to check it.');
}

// The change this example makes: one paragraph at the end of the note.
const addLine = document => ({
  ...document,
  doc: {
    ...document.doc,
    content: [...document.doc.content, { type: 'paragraph', content: [{ type: 'text', text: 'Checked by the release script.' }] }],
  },
});

const receipt = await editNote(process.env.NOTE_ID, addLine);
console.log(receipt.status, receipt.content_revision, receipt.url);

A new Operation-Id for each pass of the loop is right: after a 409 nothing was written, so the next pass is a new write. A lost answer is different: send the identical request with the same ID, as the examples do.

Who may change which note:

  • Edit and organize changes any note in its scope.
  • Read and add changes the notes it added, also after a person edited them, and no others. Changing another note answers 403 edit_not_allowed. When it changes a note, the note names the connection in changed_via, and ThreadPage keeps the last version a person wrote and the note’s first version until the note is deleted permanently.
  • A note a recipient wrote for a request (a question, a result or an answer) never changes, on any level: 409 request_note_locked.
  • Disconnecting a connection and connecting it again makes a new connection: the notes the old one added are not its own.

Markdown or text replaces the whole note. When the note holds formatting that form can’t keep, the answer is 400 lossy_markdown and nothing changes: send the document.

Replace a note's content

PUT /api/integrations/v1/notes/{note_id}/content

The MCP tool update_note. Send the content_revision you read in full with GET /notes/{note_id} and the context_receipt from that read. Edits others made meanwhile to other parts are kept; an overlap answers 409 stale_block, an out-of-date read 409 reread_required. A Markdown or text replace that would drop formatting answers 400 lossy_markdown: send the document instead. Read and add changes the notes it added, also after a person edited them, and no others (403 edit_not_allowed); a note a recipient wrote for a request never changes (409 request_note_locked). Edit and organize changes any note. Scope, shared places (shared_place_write) and the open-request rule (request_open) apply to every write.

Requirements
RequirementValue
LevelsEdit and organize · Read and add
PlanPro with Sync. On Free, add to the Inbox instead (POST /inbox/notes).
IdempotencyOperation-Id header: one UUID per logical write, the same on every retry.
Revisioncontent_revision and context_receipt from GET /notes/{note_id}, in the body.
MCPupdate_note
Shared threadsOnly where the owner chose Allow here.
Parameters
NameInType
note_idpathuuid, required
Body (application/json)
FieldTypeDescription
contenttext, Markdown or a document, required
content_revisioninteger, requiredThe content_revision you read in full with GET /notes/{note_id}.
context_receiptstring, requiredFrom that GET /notes/{note_id}.
evidencearray of objectOther notes this edit relies on, each with the content_revision read.

Answer 200: Accepted, or the same receipt again.

Answer fields
FieldTypeDescription
operation_iduuid, required
statusaccepted, conflict, not_completed, required
kindnote.update, note.properties, note.move, note.trash, note.restore, note.restore_version, thread.create, thread.update, thread.archive, thread.unarchive, thread.trash, thread.restore, thread.move, folder.create, folder.rename, folder.move, folder.trash, folder.restore, reminder.set, reminder.remove, required
targetobject, required
revisioninteger or nullThe target's revision after the change (informational).
content_revisioninteger or nullA note's content revision after a content change.
accepted_atdate-time, required
urluri or null
thread_statusstringthread.update: the status the Thread has after the change, which is the replacement when the one you sent was deleted.
reminderobject or or null or nullreminder.set: the reminder as set; reminder.remove: null.
curl
OP=$(uuidgen)
curl -m 30 --retry 3 --retry-all-errors -X PUT "https://thread.page/api/integrations/v1/notes/$NOTE_ID/content" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN" \
  -H "Operation-Id: $OP" \
  -H "Content-Type: application/json" \
  -d '{
  "content": {
    "markdown": "## Release 412\n\n- [x] Tests passed\n- [x] Tagged v1.8.0"
  },
  "content_revision": 3,
  "context_receipt": ".eJyrVkrLz1eyUspNzMlRKs7PTVWyUsrJLFKqBQBKxwbR:1vQx2a:Hk3mQ0Zp8Wc5T1nL4yJr2aVbE9sDf6gUo7iXqRb1sM"
}'

Errors: edit_not_allowed 403, unavailable 404, invalid_json 400, invalid_content 400, invalid_document 400, lossy_markdown 400, content_too_large 413, unsupported_media_type 415, stale_block 409, reread_required 409, operation_id_reused 409, request_note_locked 409, request_open 409, shared_place_write 409, thread_archived 409, entity_deleted 409, image_unavailable 409.

Also the errors of every call, and pro_required or cloud_required without Pro and Sync.

Add an image to a note

  1. Upload the image to the thread: one PNG, JPEG or WebP file up to 5 MB, 20 megapixels and 300 frames, as multipart/form-data under any field name, with an Operation-Id.
  2. Put the receipt’s markdown on its own line in a note’s Markdown in the same thread, outside lists, quotes, tables and headings, with a description between the brackets: ![Build summary](attachment:IMAGE_ID).
Shell
OP=$(uuidgen)
curl -m 30 --retry 3 --retry-all-errors https://thread.page/api/integrations/v1/threads/$THREAD_ID/images \
  -H "Authorization: Bearer $THREADPAGE_TOKEN" \
  -H "Operation-Id: $OP" \
  -F [email protected]

The same bytes with the same Operation-Id get the same receipt; other bytes with a used ID answer 409 operation_id_reused. ThreadPage stores the file exactly as sent, metadata included. A note can use an image this connection uploaded to its thread, or one a note it can read already holds; any other answers 409 image_unavailable. Images need Pro.

In n8n, use HTTP Request with Body Content Type Form-Data, one parameter of type n8n Binary File, and an Operation-Id header. For a new UUID per image, put a Crypto node before it (Action Generate, Type UUID) and set the header to {{ $json.data }}. In Make or Zapier, create the UUID in a step before the request, so a retried run sends the same one.

Upload an image for a new note in a thread

POST /api/integrations/v1/threads/{thread_id}/images

Send one PNG, JPEG or WebP file up to 5 MB as multipart/form-data, under any field name; other fields are ignored. The type comes from the bytes, which are stored exactly as sent, with any metadata in the file. Put the returned markdown on its own line in a note's Markdown, in this thread, with a description between the brackets. Idempotent by the Operation-Id header: the same bytes get the same receipt; other bytes with a used Operation-Id are rejected with 409. Each connection uploads up to 100 MB of new images a day.

Requirements
RequirementValue
LevelsEdit and organize · Read and add · Add only
PlanPro with Sync. On Free, add to the Inbox instead (POST /inbox/notes).
IdempotencyOperation-Id header: one UUID per logical write, the same on every retry.
MCPupload_note_image
Shared threadsOnly where the owner chose Allow here.
Parameters
NameInType
thread_idpathuuid, required
Body (multipart/form-data)
FieldTypeDescription
filebinary, requiredOne PNG, JPEG or WebP image up to 5 MB, under any field name; other fields are ignored.

Answer 201: Accepted, or the same receipt again.

Answer fields
FieldTypeDescription
operation_iduuid, required
statusaccepted, required
image_iduuid, required
thread_iduuid, required
media_typeimage/png, image/jpeg, image/webp, requiredFound in the bytes.
widthinteger, required
heightinteger, required
markdownstring, required![](attachment:<image_id>): put it on its own line in a note's Markdown in this thread, with a description between the brackets.
accepted_atdate-time, required
curl
OP=$(uuidgen)
curl -m 30 --retry 3 --retry-all-errors -X POST "https://thread.page/api/integrations/v1/threads/$THREAD_ID/images" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN" \
  -H "Operation-Id: $OP" \
  -F [email protected]

Errors: write_not_allowed 403, 409, unavailable 404, content_too_large 413, operation_id_reused 409, shared_place_write 409, thread_archived 409, entity_deleted 409, invalid_attachment 422, attachment_quota_exceeded 413, attachments_unavailable 503, image_busy 503.

Also the errors of every call, and pro_required or cloud_required without Pro and Sync.

Read an image preview

Each entry of a note’s images has a url you read with the same token. It answers a new PNG at most 1600 px on its longer side and 1 MiB, without the original’s metadata. A GIF, AVIF or HEIC image, or one larger than 20 MB or 20 megapixels, answers its ready WebP or JPEG preview, up to 2048 px. Each connection reads up to 20 previews a minute and 500 a day.

Read a preview of an image a note holds

GET /api/integrations/v1/notes/{note_id}/images/{image_id}

A new PNG of a PNG, JPEG or WebP image, oriented, at most 1600 px on its longer side and 1 MiB, without the original's metadata (no location); any other image (GIF, AVIF, HEIC, or larger than 20 MB or 20 megapixels) answers its ready WebP or JPEG preview, up to 2048 px, as it is. Originals are not available through connections. The note must be readable and hold the image; it may be in Trash. Each connection reads up to 20 previews a minute and 500 a day.

Requirements
RequirementValue
LevelsEdit and organize · Read and add · Read only
PlanPro with Sync. On Free, add to the Inbox instead (POST /inbox/notes).
MCPread_note_attachment
Parameters
NameInType
image_idpathuuid, required
note_idpathuuid, required

Answer 200: The preview.

curl
curl -m 30 "https://thread.page/api/integrations/v1/notes/$NOTE_ID/images/$IMAGE_ID" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN"

Errors: read_not_allowed 403, unavailable 404, unsupported_content 413, 422, attachment_blocked 403, attachment_checksum_mismatch 409, attachment_unavailable 503.

Also the errors of every call, and pro_required or cloud_required without Pro and Sync.

Add a file to a note

Upload a file of any type up to 2 GB to a thread in three steps. POST /threads/THREAD_ID/files with a new Operation-Id starts the upload and returns URLs for its first parts, and POST /files/FILE_ID/parts gives the next ones. PUT each part, then send every part’s ETag to POST /files/FILE_ID/complete. When GET /operations/OPERATION_ID says uploaded, add the file to a note in that thread with the [name](attachment:FILE_ID) line it returns, on a line of its own. The OpenAPI description lists every field.

Add a file to a note
RequestReturns or does
POST /threads/{id}/filesStart uploading a file for a note in the thread
POST /files/{id}/partsMore part URLs for a file upload
POST /files/{id}/completeComplete a file upload

Each of them needs a token that can add notes. GET /notes/NOTE_ID lists the note’s files in files, each with its name, type and size; Read a file gives their text and previews.

Each connection can upload 2 GB of files a day, and all connections of an account together 4 GB; over that the API answers 429 with rate_limited and details.reason file_bytes_day. Files also count toward your account’s upload limits. A file sent through MCP can be up to 5 MB in one request. It counts toward the same daily limit as images: 100 MB for each connection and 250 MB for your account.

409 with image_unavailable means a note names a file this connection didn’t upload to that thread, or hasn’t finished uploading. 422 with invalid_attachment means the file can’t be stored. 413 with attachment_quota_exceeded means the workspace’s storage is full. 410 with attachment_erased means the file was deleted and can’t be uploaded again. 503 with attachment_upload_in_progress means ThreadPage is still checking the file: send the note again after Retry-After. 403 with attachment_blocked means ThreadPage blocked the file; trying again won’t help.

Read a file

files lists the other files a note holds. For each, read what it is, its ready preview and, for a text file or a PDF, its text.

  • The preview is a WebP of an image (up to 2048 px) or of a PDF’s first page (up to 1600 px), or a JPEG of a video’s poster frame. Previews count toward the image preview limits.
  • The text is the first 1 MB of the file’s text as UTF-8; truncated says it reached that limit. The first request has the text made and waits up to 10 seconds; until it is ready the answer is 503 timed_out with Retry-After.
  • A PDF over 200 MB has no text: 422 unsupported_content. A PDF of scans has an empty text. A PDF that asks for a password has no pages, text or preview.

Describe a file a note holds

GET /api/integrations/v1/notes/{note_id}/files/{file_id}

What the file is: its name, kind, type, size and media details, and where to read its text and preview. Any file the note's document holds, its images included. Originals are not available through connections. The note must be readable; it may be in Trash.

Requirements
RequirementValue
LevelsEdit and organize · Read and add · Read only
PlanPro with Sync. On Free, add to the Inbox instead (POST /inbox/notes).
MCPread_note_file
Parameters
NameInType
file_idpathuuid, required
note_idpathuuid, required

Answer 200.

Answer fields
FieldTypeDescription
iduuid, required
namestring, requiredThe name it goes by, with the extension of what it is.
kindimage, video, audio, pdf, text, document, archive, other, required
media_typestring, requiredFound in the bytes.
bytesinteger, required
widthinteger or null, requiredImages and video.
heightinteger or null, required
media_infoobject or or null or null, requiredDuration and codecs; a PDF's pages.
created_atdate-time, required
preview_urluri or null, requiredAn image's, a PDF's first page's or a video's ready preview.
text_urluri or null, requiredA text file's or a PDF's text.
curl
curl -m 30 "https://thread.page/api/integrations/v1/notes/$NOTE_ID/files/$FILE_ID" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN"

Errors: read_not_allowed 403, unavailable 404, unsupported_content 413, 422, attachment_blocked 403, attachment_checksum_mismatch 409, attachment_unavailable 503.

Also the errors of every call, and pro_required or cloud_required without Pro and Sync.

Read the preview of a file a note holds

GET /api/integrations/v1/notes/{note_id}/files/{file_id}/preview

The ready preview, as it is: a WebP of an image (up to 2048 px) or of a PDF's first page (up to 1600 px), or a JPEG of a video's poster frame, without the original's metadata. Other files have none. Previews count toward the same limits as image previews: 20 a minute and 500 a day for each connection.

Requirements
RequirementValue
LevelsEdit and organize · Read and add · Read only
PlanPro with Sync. On Free, add to the Inbox instead (POST /inbox/notes).
MCPread_note_file
Parameters
NameInType
file_idpathuuid, required
note_idpathuuid, required

Answer 200: The preview.

curl
curl -m 30 "https://thread.page/api/integrations/v1/notes/$NOTE_ID/files/$FILE_ID/preview" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN"

Errors: read_not_allowed 403, unavailable 404, unsupported_content 413, 422, attachment_blocked 403, attachment_checksum_mismatch 409, attachment_unavailable 503.

Also the errors of every call, and pro_required or cloud_required without Pro and Sync.

Read the text of a text file or a PDF a note holds

GET /api/integrations/v1/notes/{note_id}/files/{file_id}/text

Up to the first 1 MB of the file's text, as UTF-8. The first request has the text made, waiting up to 10 seconds; until it is ready the answer is 503 timed_out with Retry-After, and later requests answer it at once. A PDF of scans has an empty text; truncated says the text reached the 1 MB limit.

Requirements
RequirementValue
LevelsEdit and organize · Read and add · Read only
PlanPro with Sync. On Free, add to the Inbox instead (POST /inbox/notes).
MCPread_note_file
Parameters
NameInType
file_idpathuuid, required
note_idpathuuid, required

Answer 200.

Answer fields
FieldTypeDescription
iduuid, required
textstring, requiredUTF-8; empty when the file holds none.
truncatedboolean, requiredThe text reached the 1 MB limit (a text of exactly that size counts too).
curl
curl -m 30 "https://thread.page/api/integrations/v1/notes/$NOTE_ID/files/$FILE_ID/text" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN"

Errors: read_not_allowed 403, unavailable 404, unsupported_content 413, 422, attachment_blocked 403, attachment_checksum_mismatch 409, attachment_unavailable 503.

Also the errors of every call, and pro_required or cloud_required without Pro and Sync.

Move a note to Trash and restore it

Trash keeps the note; restoring brings it back where it was. When its thread is in Trash too, restore the thread first. Connections never delete permanently.

A note whose request is unfinished stays where it is: 409 request_open.

Move a note to Trash

POST /api/integrations/v1/notes/{note_id}/trash

The MCP tool trash_note; restore brings it back. Read and add, for the notes it added; Edit and organize, any. Scope, shared places (shared_place_write) and the open-request rule (request_open) apply to every write.

Requirements
RequirementValue
LevelsEdit and organize · Read and add
PlanPro with Sync. On Free, add to the Inbox instead (POST /inbox/notes).
IdempotencyOperation-Id header: one UUID per logical write, the same on every retry.
MCPtrash_note
Shared threadsOnly where the owner chose Allow here.
Parameters
NameInType
note_idpathuuid, required

Answer 200. Its fields are those under Replace a note's content.

curl
OP=$(uuidgen)
curl -m 30 --retry 3 --retry-all-errors -X POST "https://thread.page/api/integrations/v1/notes/$NOTE_ID/trash" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN" \
  -H "Operation-Id: $OP"

Errors: edit_not_allowed 403, unavailable 404, operation_id_reused 409, request_note_locked 409, request_open 409, shared_place_write 409, entity_deleted 409.

Also the errors of every call, and pro_required or cloud_required without Pro and Sync.

Restore a note

POST /api/integrations/v1/notes/{note_id}/restore

The MCP tool restore_note. When its thread is in Trash too, restore the thread first. Read and add, for the notes it added; Edit and organize. Scope, shared places (shared_place_write) and the open-request rule (request_open) apply to every write.

Requirements
RequirementValue
LevelsEdit and organize · Read and add
PlanPro with Sync. On Free, add to the Inbox instead (POST /inbox/notes).
IdempotencyOperation-Id header: one UUID per logical write, the same on every retry.
MCPrestore_note
Shared threadsOnly where the owner chose Allow here.
Parameters
NameInType
note_idpathuuid, required

Answer 200. Its fields are those under Replace a note's content.

curl
OP=$(uuidgen)
curl -m 30 --retry 3 --retry-all-errors -X POST "https://thread.page/api/integrations/v1/notes/$NOTE_ID/restore" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN" \
  -H "Operation-Id: $OP"

Errors: edit_not_allowed 403, unavailable 404, operation_id_reused 409, request_open 409, shared_place_write 409, parent_deleted 409.

Also the errors of every call, and pro_required or cloud_required without Pro and Sync.

Restore an earlier version

Versions are kept for 90 days after they were replaced. When a connection changed a note, the last version a person wrote and the note’s first version stay until the note is deleted permanently.

  1. GET /notes/{note_id}/versions lists them. history_starts_at and oldest_available_at say how far back the history reaches.
  2. GET /notes/{note_id}/versions/{version_id} reads one. Note references in an old version are not followed.
  3. POST /notes/{note_id}/versions/{version_id}/restore with the content_revision and context_receipt of a fresh GET /notes/{note_id} makes it current as a new revision. A note that changed since answers 409 preview_changed: read it again.

List a note's earlier versions

GET /api/integrations/v1/notes/{note_id}/versions

Versions still kept: 90 days after they were replaced; a note a connection changed keeps the last version a person wrote and its first version until it is purged. Read only and up.

Requirements
RequirementValue
LevelsEdit and organize · Read and add · Read only
PlanPro with Sync. On Free, add to the Inbox instead (POST /inbox/notes).
MCPget_note_history
Parameters
NameInTypeDescription
cursorquerystringFrom next_cursor.
note_idpathuuid, required

Answer 200.

Answer fields
FieldType
itemsarray of object, required
next_cursorstring or null, required
history_starts_atdate-time or null, required
oldest_available_atdate-time or null, required
curl
curl -m 30 "https://thread.page/api/integrations/v1/notes/$NOTE_ID/versions" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN"

Errors: read_not_allowed 403, unavailable 404, context_changed 409.

Also the errors of every call, and pro_required or cloud_required without Pro and Sync.

Read one earlier version

GET /api/integrations/v1/notes/{note_id}/versions/{version_id}

The version's text, Markdown and document beside the note's current content revision. Note references in an old version are not followed. Read only and up.

Requirements
RequirementValue
LevelsEdit and organize · Read and add · Read only
PlanPro with Sync. On Free, add to the Inbox instead (POST /inbox/notes).
MCPget_note_version
Parameters
NameInType
note_idpathuuid, required
version_idpathuuid, required

Answer 200.

Answer fields
FieldType
versionobject, required
textstring, required
markdownstring, required
documentobject, required
current_content_revisioninteger, required
curl
curl -m 30 "https://thread.page/api/integrations/v1/notes/$NOTE_ID/versions/$VERSION_ID" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN"

Errors: read_not_allowed 403, unavailable 404.

Also the errors of every call, and pro_required or cloud_required without Pro and Sync.

Make an earlier version current

POST /api/integrations/v1/notes/{note_id}/versions/{version_id}/restore

The MCP tool restore_note_version: a new revision, the history in between is kept. Send the content_revision and context_receipt you read; a note that changed since answers 409 preview_changed. Read and add, for the notes it added; Edit and organize. Scope, shared places (shared_place_write) and the open-request rule (request_open) apply to every write.

Requirements
RequirementValue
LevelsEdit and organize · Read and add
PlanPro with Sync. On Free, add to the Inbox instead (POST /inbox/notes).
IdempotencyOperation-Id header: one UUID per logical write, the same on every retry.
Revisioncontent_revision and context_receipt from GET /notes/{note_id}, in the body.
MCPrestore_note_version
Shared threadsOnly where the owner chose Allow here.
Parameters
NameInType
note_idpathuuid, required
version_idpathuuid, required
Body (application/json)
FieldTypeDescription
content_revisioninteger, requiredThe content_revision you read in full.
context_receiptstring, requiredFrom the GET /notes/{note_id} you read.

Answer 200. Its fields are those under Replace a note's content.

curl
OP=$(uuidgen)
curl -m 30 --retry 3 --retry-all-errors -X POST "https://thread.page/api/integrations/v1/notes/$NOTE_ID/versions/$VERSION_ID/restore" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN" \
  -H "Operation-Id: $OP" \
  -H "Content-Type: application/json" \
  -d '{
  "content_revision": 5,
  "context_receipt": ".eJyrVkrLz1eyUspNzMlRKs7PTVWyUsrJLFKqBQBKxwbR:1vQx2a:Hk3mQ0Zp8Wc5T1nL4yJr2aVbE9sDf6gUo7iXqRb1sM"
}'

Errors: edit_not_allowed 403, unavailable 404, invalid_json 400, unsupported_media_type 415, reread_required 409, preview_changed 409, operation_id_reused 409, request_note_locked 409, request_open 409, shared_place_write 409, thread_archived 409, entity_deleted 409.

Also the errors of every call, and pro_required or cloud_required without Pro and Sync.

Pin a note or mark it done

Send only what changes: {"pinned": true} or {"done": false}. Edit and organize only.

Pin a note or mark it done

PATCH /api/integrations/v1/notes/{note_id}

The MCP tool update_note_properties. Edit and organize. Scope, shared places (shared_place_write) and the open-request rule (request_open) apply to every write.

Requirements
RequirementValue
LevelsEdit and organize
PlanPro with Sync. On Free, add to the Inbox instead (POST /inbox/notes).
IdempotencyOperation-Id header: one UUID per logical write, the same on every retry.
MCPupdate_note_properties
Shared threadsOnly where the owner chose Allow here.
Parameters
NameInType
note_idpathuuid, required
Body (application/json)
FieldType
pinnedboolean
doneboolean

Answer 200. Its fields are those under Replace a note's content.

curl
OP=$(uuidgen)
curl -m 30 --retry 3 --retry-all-errors -X PATCH "https://thread.page/api/integrations/v1/notes/$NOTE_ID" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN" \
  -H "Operation-Id: $OP" \
  -H "Content-Type: application/json" \
  -d '{"pinned":true}'

Errors: organize_not_allowed 403, unavailable 404, invalid_json 400, unsupported_media_type 415, operation_id_reused 409, request_note_locked 409, request_open 409, shared_place_write 409, thread_archived 409, entity_deleted 409.

Also the errors of every call, and pro_required or cloud_required without Pro and Sync.

Move a note

Moving keeps the same note: its ID, history, pin and images go with it. The target must be a live, unarchived thread within the connection’s scope. Read and add moves the notes it added; Edit and organize, any.

Move a note to another thread

POST /api/integrations/v1/notes/{note_id}/move

The MCP tool move_note: the same note, its id, history, pin and images go with it. Read and add, for the notes it added; Edit and organize. Scope, shared places (shared_place_write) and the open-request rule (request_open) apply to every write.

Requirements
RequirementValue
LevelsEdit and organize · Read and add
PlanPro with Sync. On Free, add to the Inbox instead (POST /inbox/notes).
IdempotencyOperation-Id header: one UUID per logical write, the same on every retry.
MCPmove_note
Shared threadsOnly where the owner chose Allow here.
Parameters
NameInType
note_idpathuuid, required
Body (application/json)
FieldTypeDescription
thread_iduuid, requiredA live, unarchived thread within this connection's content.

Answer 200. Its fields are those under Replace a note's content.

curl
OP=$(uuidgen)
curl -m 30 --retry 3 --retry-all-errors -X POST "https://thread.page/api/integrations/v1/notes/$NOTE_ID/move" \
  -H "Authorization: Bearer $THREADPAGE_TOKEN" \
  -H "Operation-Id: $OP" \
  -H "Content-Type: application/json" \
  -d '{"thread_id":"0e7c4b2a-91d3-4f6e-8a25-3c1b9d7f4e60"}'

Errors: edit_not_allowed 403, unavailable 404, invalid_json 400, unsupported_media_type 415, operation_id_reused 409, request_note_locked 409, request_open 409, shared_place_write 409, thread_archived 409, entity_deleted 409, target_deleted 409.

Also the errors of every call, and pro_required or cloud_required without Pro and Sync.

In shared threads

In a thread the owner shares with other people, a connection that reads adds, changes and moves notes only where the owner allowed it. This covers every note write: adding, images, edits, checklist ticks, pin, Done, moves into or out of the thread, Trash, restore and version restores. Otherwise the write answers 409 shared_place_write. See Shared threads and folders.

If it fails

Errors
CodeHTTPWhenWhat to doRetryable
stale_block409Someone changed the same part of the note since your read.Read the note again and apply your change again.No
reread_required409The 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
lossy_markdown400Markdown or text would drop formatting the note holds.Read the note and send its document with your change.No
operation_id_reused409The 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
edit_not_allowed403Changing 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_allowed403Threads, folders, pin or Done below Edit and organize.Give the person details.manage_url.No
request_note_locked409A note written for a request: it never changes.Add a new note instead.No
shared_place_write409A 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
request_open409The note, or a note in the thread or folder, is with a recipient.Wait until its request ends, or ask the owner.No
preview_changed409The note changed since the read a version restore is based on.Read the note again, then restore.No
content_too_large413A 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_content413, 422A document over 256 KiB, or a file or image with no preview or text.Split the note; for a file, tell the person.No
invalid_content400Markdown or text the converter can't take, such as too many lines.Fix the content or split it into several notes.No
invalid_document400A document that breaks the RichDocument v4 schema.Fix the document; the formatting guide lists every element.No
image_unavailable409The note names an image this connection may not use there.Upload the image to that thread first.No
invalid_attachment422Not a PNG, JPEG or WebP image within 5 MB, 20 megapixels and 300 frames.Send a supported image.No
attachment_quota_exceeded413The workspace's storage is full.Tell the person.No
image_busy503Another image is being processed.Wait Retry-After seconds and send the same request again.Yes
thread_archived409The thread is archived, so it takes no new notes.Unarchive it first, or ask the owner.No
entity_deleted409The item is in Trash.Restore it first, or ask the owner.No
parent_deleted409Its thread or folder is in Trash.Restore the thread or folder first.No
target_deleted409The destination is in Trash.Restore it, or choose another destination.No
quota_exhausted429Free's 3,000 connection notes this month are used.Stop until details.resets_at. There is no Retry-After.No
inbox_full4091,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
pro_required403It 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

Next