How Docs Work
This page explains the document model and the mechanics behind every Docs operation: how documents nest into a tree, how access is scoped, how to browse the hierarchy lazily, how markdown content is read and written asynchronously, and how full-text search works. For a quick orientation and copy-paste examples, start with the Docs introduction.
The document model
A document has:
- Title — its name.
- Body — the rich-text content, read and written as markdown.
- Parent — an optional reference to another document, which is what builds the tree.
- Icon and Cover — optional emoji/icon and cover image.
Documents nest through their parent, forming a hierarchy:
Project Notes
├── Kickoff
│ └── Action Items (parent = Kickoff)
└── Retrospective (parent = Project Notes)
Documents without a parent sit at the root.
Access model
Access follows ownership and sharing. With a Personal Access Token, the API returns documents the authenticated user owns or that have been shared with them — including shares granted through a team they belong to. Deleting a document is restricted to its owner and is a recoverable soft-delete.
Writing markdown content requires edit access (owner, or a participant with the admin or member role, including team-shared editors). Viewers and comment-only participants receive 403 Forbidden. Callers who cannot see the document at all get the same "Document not found" error as other read endpoints.
Browsing the tree
The tree endpoint walks the hierarchy breadth-first:
- Call it without a
parentIdfor root-level documents, or pass a document id to fetch that subtree. - Control how deep it descends with
depth(1–10, default 3).
Each node carries a hasChildren flag, which makes lazy-loading explorers easy — only fetch a deeper level when a user expands a node. The tree is capped at 500 documents per response; when it truncates, the response includes nextParentIds so you can continue fetching the remaining branches.
Reading and writing content
Content is always markdown. Read it from the document's …/md endpoint, which returns { "content": "…" } (an empty string when the document has no body).
To write content, POST to the same path with an operation and content:
replace— overwrite the entire body.append— add to the end.prepend— add to the beginning.
Content updates are asynchronous: the API queues the change and returns HTTP 202 Accepted. The new content appears a moment later. Creating a document with initial content follows the same async path — the page is created immediately and its body is filled in shortly after.
Managing documents
- Create —
POSTatitle, with an optionalparent(for nesting) and optional initialcontent. - Update metadata —
PATCHto change thetitle,icon, orcover. - Delete — soft-deletes the document (owner only; recoverable).
Searching
The search endpoint runs full-text search across every document you can access. Pass q for the query and optionally control sortBy (createdAt / updatedAt, default updatedAt), sortOrder (asc / desc, default desc), and limit (1–50, default 20).
Results come back with highlighted matches in the title and body, plus each hit's ancestor chain (parents), so you can show where a result lives in the tree.
For search that spans documents and other entity types (channels, todos, drive files, and more) in one call, use the global Search API instead.
Authentication & scope
Docs endpoints require a Personal Access Token (cp_pat_) with the access_docs scope. A token missing the scope gets a 403. See Authentication.
Reference
- Docs introduction — orientation, Quick Start, and parity.
- Docs in the API Reference — tree, search, get/create/update/delete, and markdown content endpoints with full schemas.
- Error Handling — note that "not found" cases surface as a
400with aNOT_FOUNDcode. - Copera CLI and MCP server.