POST /v1/document inspects one canonical document and returns selected content as clean markdown. The usual flow is search → pick a doc_id → read, but you can also read any URL directly — unknown URLs are fetched on demand.
doc_id or canonical_url
One of the two lookup keys is required (missing both is a 400validation_error); if you send both, doc_id takes precedence and canonical_url is ignored:
doc_id— the UUID identifier from a search result. It is deterministic per canonical URL and can be reused across searches and recrawls.canonical_url— any URL. It is normalized before lookup (tracking params stripped, fragment removed, host lowercased), and unknown URLs are fetched on demand.
doc_id lookups resolve only documents the account has been granted: the document appeared in one of its search results, or a prior canonical_url lookup granted it. An ungranted doc_id returns 404 document_not_found. A successful canonical_url read grants that doc_id for future calls — so URL-first, then doc_id, always works.
If content cannot be fetched, the response still returns doc metadata plus warning content_unavailable.
Choosing sections: include
include selects which response sections to compute. Omitted, you get all available sections.
Any other value is a 400
validation_error. include: ["metadata"] alone skips the content fetch entirely — the cheapest way to check freshness fields. provenance is always returned when a capture exists, regardless of include — unlike search results, where it is verbosity-gated (provenance).
The official SDKs and CLI default to
include: ["metadata", "content"]; the raw API defaults to all sections. Ask for passages or capture_history explicitly when using a client.Content controls
The response
content object is selection, format, text, truncated, char_count, plus start_char on range reads.
Continuation reads
A truncated read is resumable. Do not retry with a bigger cap; continue fromstart_char + char_count using content.range.
content.range fields:
Rules that make the loop safe:
- The next offset is always
start_char + char_countfrom the previous response (start_charis omitted when 0). The CLI flag is--start-char; the SDKs takestartChar(TypeScript) /start_char(Python). - Continuation reads must use
selection: "full_document"— offsets address the raw document text, and selection-based excerpting would make them non-contiguous between calls. The raw API does not change your selection; set it yourself on range reads (as in the cURL example above). The official clients do this automatically whenever a start offset is set. - A
start_charpast the end of the document returns HTTP 200 with emptytextandchar_count: 0— your loop terminates cleanly. stale_range: emitted only whenrange.start_charis greater than 0 andrange.capture_idis set but no longer matches the latest capture. The read still succeeds and is served from the latest capture, but offsets may not line up with your earlier chunks — restart fromstart_char: 0if exact continuity matters. Details carryrequested_capture_idandlatest_capture_id.
Capture history
capture_history entries are capture_id, capture_time, content_digest, and optionally content_format (text for search-snippet captures, markdown for full fetches).
Today
capture_history returns the latest capture entry only. It is metadata for change detection — compare content_digest across reads — not a surface for reading old captures.full_document was requested against a snippet-only capture. The provenance block (capture_id, capture_time) always identifies the capture the returned content came from.
Passages
passages are structured spans for citation: passage_id, doc_id, ordinal, text, plus optional section_heading, section_path, and char_start/char_end (offsets only with content.include_offsets: true on a full-document capture).
To re-fetch specific passages from a search result, use content.selection: "passage_ids" with content.passage_ids. Passage IDs identify passages in the latest capture, so they change when the document content changes. Stale or unknown IDs never fail the request: available passages are returned plus warning stale_passage_id listing the missing IDs in details.passage_ids. Treat that warning as “the document changed” and re-search or re-read rather than retrying the same IDs.
Full request and response schemas are in the API reference; error envelopes and codes in errors.