RememberStackremember.dev/docs

Section history and references

Two reads work at the level of a document's sections rather than its passages. GET /documents/{doc_id}/sections/{section_key}/history lists what one keyed section held in each version of a document, and whether it changed. POST /documents/references lists the references a passage, section or document makes and receives, each resolved against the versions of the target that were in force at the time.

Both take the same time scope as the other text reads, and both are easier to follow with a worked example: see Read a document as of a date. The writes they depend on (declaring effective periods and supplying references) are on Ingest, readiness and documents.

Base URL, authentication and error shapes are described in HTTP API conventions.

The time scope

ModeSelects
currentVersions in force now.
atVersions in force at the instant at.
overlapVersions in force at any point from from to to, both included.
historyVersions in force at any point up to now.

For a document with declared effective periods, a version is selected by its periods. For any other document, every mode selects its served version, the newest processed one. Instants must carry a time zone. A cursor keeps the instant the first page was read at, and reads the periods as they were known then, so a correction made while you page cannot move a row from one page to another.

GET /documents/{doc_id}/sections/{section_key}/history

List, for each version the time scope selects, what that version holds under one section key.

Scope: read.

Parameters

NameInTypeRequiredDefaultConstraints
doc_idpathUUIDyesA live document.
section_keypathstringyes1 to 200 characters from A–Z a–z 0–9 _ . : / -, without the leading #. A key may contain /.
modequerycurrent | at | overlap | historynohistoryThe time scope.
atquerydate-timewith mode=at
from, toquerydate-timewith mode=overlapto not before from.
kqueryintegerno501 to 200 rows.
cursorquerystringnoThe previous page's cursor.

Response

200 with a SectionHistoryPage:

{
  "doc_id": "a1f3e0b4-1c2d-5e6f-8a9b-0c1d2e3f4a5b",
  "section_key": "per-diem",
  "periodised": true,
  "rows": [
    {
      "version_id": "6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d",
      "version_no": 1,
      "version_key": "edition-1",
      "effective": [{"from": "2024-01-01T00:00:00Z", "until": "2026-01-01T00:00:00Z", "until_declared": false}],
      "status": "present",
      "section": {
        "section_id": "…",
        "node_path": "0.4",
        "title": "4. Per-diem allowance",
        "own_content_hash": "…",
        "subtree_content_hash": "…",
        "changed": null,
        "own_changed": null,
        "first_chunk_ids": ["…"]
      }
    },
    {
      "version_id": "0f9e8d7c-6b5a-4c3d-2e1f-0a9b8c7d6e5f",
      "version_no": 2,
      "version_key": "edition-2",
      "effective": [{"from": "2026-01-01T00:00:00Z", "until": "2027-06-30T00:00:00Z", "until_declared": true}],
      "status": "present",
      "section": {"title": "4. Per-diem allowance", "changed": true, "own_changed": true, "…": "…"}
    }
  ],
  "amendments": [],
  "cursor": null,
  "evaluated_at": "2027-07-01T08:00:00Z",
  "believed_at": "2027-07-01T08:00:00Z"
}

Rows are ordered by the start of each version's period for a document with declared periods (periodised: true), and by version_no otherwise. For a document without declared periods, history lists every live version (the served one with an open interval, the others with none), and every other mode lists the served version only.

Each row's status:

StatusMeaning
presentThe version has a section with this key; section describes it.
absentThe version has been indexed for section keys and has no such section: it was removed or never existed there.
not_indexedThe version's sections were stored before section keys existed and have not been indexed yet, so whether it has the key is unknown.
processingThe version is selected but not fully processed yet.

In section, changed compares the whole section, subsections included, with the previous row that held the key; own_changed compares only the section's own text. Both are null on the first row that holds the key. first_chunk_ids are the passages holding the section's first block: pass one to adjacent_chunks to read the section.

amendments, on the first page only, lists up to 200 references of kind amends that live documents make to this section, each with change_effective_from and change_date_known. See ReferenceInput.

Errors

StatusdetailCause
400the cursor problemThe cursor is malformed, or belongs to another call.
404document_not_foundNo such document, or it is deleted.
422validation listA malformed key, a missing at, from or to for its mode, to before from, a timestamp without a time zone, or k out of range.

Example

curl -s "$REMEMBER_API_URL/documents/$DOC_ID/sections/per-diem/history" \
  -H "Authorization: Bearer $REMEMBER_API_KEY"
from remember import Client
 
memory = Client()
page = memory.section_history(doc_id=doc_id, section_key="per-diem")
for row in page.rows:
    changed = row.section.changed if row.section else None
    print(row.version_key, row.status, changed)

POST /documents/references

List what a passage, a section or a whole document references, and what references it, across versions and time.

Scope: read.

Request body

A DocumentReferencesRequest:

FieldTypeRequiredDefaultConstraints
chunk_idUUIDone of chunk_id, doc_idA passage. Its version and its section are the source.
doc_idUUIDone of chunk_id, doc_idA document. The versions time selects are the source.
section_keystringnoOnly with doc_id: the section and its subsections. Omitted: the whole document.
directionoutgoing | incoming | bothnoboth
kindsarray of stringsnoall1 to 7 of the reference kinds.
timeobjectnosee belowThe time scope of the source versions.
kintegerno501 to 200 rows.
cursorstringnoThe previous page's cursor.

Unknown fields are rejected.

time defaults to current. With chunk_id and no time, the window is the chunk's own version's in-force time up to now: "what did this passage point to while it was in force". For a document without declared periods, that is the instant now.

What it returns

The source side. Outgoing rows are the references the selected source versions make from the section and its subsections. Incoming rows are references other documents make to this document, or to the section or one of its subsections, from versions that their own time scope selects. A section with more than 1,000 keyed subsections is too broad for one call: the result has no rows and a too_broad block asking for a narrower section.

Each source version contributes its source window: its in-force time inside the query window (the instant for current and at, from–to for overlap, everything up to now for history). A document without declared periods counts as in force throughout.

The target side. Each reference is resolved against the target versions for its source window:

  • a floating reference to a document with declared periods gives one row per target version in force during the window, with applies_during, the part of the window it covers. Two versions in force at the same time (overlapping periods, or a point in time where both are in force) are both returned, marked concurrent: true; none is chosen for you;
  • a floating reference to any other document gives one row for its served version;
  • a pinned reference gives one row for the version with the named version_key, whether or not it is in force, with that version's own periods in target.effective.

The target version is chosen first, then checked. If it cannot be read, the row says so; it never falls back to another version.

statusMeaning
resolvedThe target version is readable, and the named section is in it. target has the version, its representation_id, the section's title and first_chunk_ids (empty when the reference names the whole document).
target_processingThe target version is not fully processed yet. target has the version and applies_during, not the section.
target_unavailableNo live document has the named source_kind and source_ref: it was never ingested, or it is deleted or forgotten. One status for all three, so a deletion is not revealed. target is null.
target_not_in_forceFloating reference: the target exists, but none of its versions is in force during the window.
section_not_in_versionThe target version has no section with the named key.
section_not_indexedThe target version's sections have not been indexed for keys yet, so whether it has the section is unknown.
pinned_version_unavailablePinned reference: the target has no live version with that version_key.

Every row carries named_target: the target exactly as the source names it (source_kind, source_ref, version_key, section_key), whatever its status. That is part of the source document's content, like the passage that contains the reference.

Order and paging. Rows are ordered by direction (outgoing first), source document, source version, reference, then target version. A page reads at most ten times k references; it can come back shorter than k with a cursor. Keep paging until cursor is null.

Response

200 with a DocumentReferencesPage:

{
  "rows": [
    {
      "direction": "outgoing",
      "crossref_id": "…",
      "kind": "refers_to",
      "origin": "supplied",
      "binding": "floating",
      "source_label": null,
      "context": null,
      "change_effective_from": null,
      "change_date_known": null,
      "source": {
        "doc_id": "a1f3e0b4-1c2d-5e6f-8a9b-0c1d2e3f4a5b",
        "version_id": "0f9e8d7c-6b5a-4c3d-2e1f-0a9b8c7d6e5f",
        "version_key": "edition-2",
        "section_key": "approvals",
        "section_title": "7. Approvals",
        "window": {"from": "2026-01-01T00:00:00Z", "until": "2026-12-31T00:00:00Z", "until_inclusive": true}
      },
      "named_target": {"source_kind": "intranet", "source_ref": "policy/expense", "version_key": null, "section_key": "approvals-process"},
      "status": "resolved",
      "target": {
        "doc_id": "…",
        "version_id": "…",
        "version_key": "2026",
        "representation_id": "…",
        "section_key": "approvals-process",
        "section_title": "3. Approvals process",
        "first_chunk_ids": ["…"],
        "effective": [{"from": "2025-07-01T00:00:00Z", "until": null, "until_declared": false}],
        "applies_during": {"from": "2026-01-01T00:00:00Z", "until": "2026-12-31T00:00:00Z", "until_inclusive": true},
        "concurrent": false
      }
    }
  ],
  "cursor": null,
  "evaluated_at": "2026-03-02T09:00:00Z",
  "believed_at": "2026-03-02T09:00:00Z",
  "too_broad": null
}

A window runs from from (included; null is unbounded) to until (excluded, unless until_inclusive is true; null is unbounded). An instant window has from equal to until and until_inclusive: true.

Errors

StatusdetailCause
400the cursor problemThe cursor is malformed, or belongs to another call.
404document_not_foundNo such document, or it is deleted.
404chunk_not_foundNo such passage, or its document is deleted.
422validation listBoth or neither of chunk_id and doc_id, section_key without doc_id, an unknown kind, a bad time, k out of range, or an unknown field.

Example

curl -s -X POST "$REMEMBER_API_URL/documents/references" \
  -H "Authorization: Bearer $REMEMBER_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"doc_id\": \"$DOC_ID\", \"section_key\": \"approvals\", \"direction\": \"outgoing\",
       \"time\": {\"mode\": \"overlap\", \"from\": \"2026-01-01T00:00:00Z\", \"to\": \"2026-12-31T00:00:00Z\"}}"
page = memory.document_references(
    doc_id=doc_id,
    section_key="approvals",
    direction="outgoing",
    time={"mode": "overlap", "from": "2026-01-01T00:00:00Z", "to": "2026-12-31T00:00:00Z"},
)
for row in page.rows:
    print(row.status, row.named_target.source_ref, row.target and row.target.applies_during)

References found in the text itself (links, citations) are not extracted yet; today every reference is one you supplied. See What is not built yet.