Document Index
This workflow shows how to navigate a document's structure and retrieve verbatim text from specific provisions — without pulling the entire document.
Overview
1. Get a document → POST /documents/list (or upload one first)
2. Find relevant nodes → POST /clauses/search
3. Inspect the index → GET /documents/{id}/index?mode=structure
4. Pull verbatim text → POST /clauses/batch
All requests require x-user-api-key, x-workspace-id, and x-app-name headers. See Authentication.
Step 1 — Get a document
If you haven't uploaded a document yet, follow the Document Management workflow first.
To list documents already in your workspace:
API Reference:
POST /documents/list
POST /documents/list
Content-Type: application/json
{
"limit": 25,
"offset": 0,
"orderBy": [{ "field": "created", "direction": "DESC" }]
}
Pick the documentId of the document you want to index.
Step 2 — Find relevant nodes with clause search
Use clause search to semantically search for clauses across your workspace. This is useful for identifying the clauseId values — which correspond to node IDs in the index — before fetching full text.
API Reference:
POST /clauses/search
POST /clauses/search
Content-Type: application/json
Request body:
This example searches for clauses about "funds", restricted to the top level of a document (depth ≤ 1):
{
"filter": {
"conditions": {
"conjunction": "AND",
"nodes": [
{ "rule": { "field": "primaryText", "operator": "EQUAL", "value": ["funds"] } },
{ "rule": { "field": "depth", "operator": "LESS_THAN_OR_EQUAL", "value": 1 } }
]
},
"disableExternalTextSearch": false,
"limit": 25,
"offset": 0,
"returnDepth": true
}
}
| Field | Required | Description |
|---|---|---|
filter.conditions | No | Rule tree used to filter clauses. Each node is { "rule": { field, operator, value } }, combined with conjunction: "AND" | "OR". Omit to search across all clauses. |
filter.disableExternalTextSearch | No | When false (default), primaryText rules run a semantic search via an external search service; when true, matching falls back to Postgres text similarity. |
filter.limit | No | Max clauses to return. Defaults to 10. |
filter.offset | No | Number of clauses to skip, for pagination. Defaults to 0. |
filter.returnDepth | No | When true, includes each clause's nesting depth in the response. |
primaryText is special: the operator is ignored — it always runs a text/semantic search rather than a literal comparison — and value accepts a single string or an array of strings. See the API reference for the full list of filterable fields and operators.
Response (200):
{
"count": 3,
"clauses": [
{
"clauseId": "98001",
"documentId": "10567",
"title": "Use of Funds",
"text": "The borrower shall apply the funds solely for...",
"depth": 1,
"similarity": 0.87,
"textBlockId": "70318",
"date": "2024-03-01",
"endorsed": false
}
]
}
Key fields to note:
| Field | Description |
|---|---|
clauseId | The node ID used in document index requests |
documentId | Which document this clause belongs to |
title | Clause heading |
text | Clause text as extracted |
depth | Nesting depth (1 = top-level section); only present when filter.returnDepth is true |
similarity | Relevance score for the primaryText search query |
Step 3 — Inspect the document index
The document index endpoint has two modes. Use structure first to get a full navigation map of a document without fetching any text.
API Reference:
GET /documents/{id}/index
structure mode — navigation map
Returns the section tree: node IDs, titles, clause references, snippets, keywords, and cross-references. No full verbatim text is included.
GET /documents/{id}/index?mode=structure
Optional query parameters:
| Parameter | Description |
|---|---|
depth | Limit results to nodes at this depth or shallower (integer > 0) |
includeDocumentMetadata | When true, adds a documentTags object (tags grouped by category) |
Response (200):
{
"spec": "syntheia/v1-draft",
"documentId": "10567",
"documentTitle": "Acme NDA 2024",
"extraction_mode": "acceptedOnly",
"documentIndex": [
{
"nodeId": "98001",
"title": "Payment Terms",
"clauseReference": "4",
"snippet": "Payment shall be made within 30 days...",
"crossReferencedIds": ["98045"],
"keywords": ["Payment"],
"children": [
{
"nodeId": "98002",
"title": "Late Payment",
"clauseReference": "4.1",
"snippet": "Any payment not received within the due date...",
"crossReferencedIds": [],
"keywords": [],
"children": []
}
]
}
]
}
Use the nodeId values from documentIndex to target specific nodes in the next step.
xrefs mode — cross-references
Returns cross-reference IDs for specified nodes — useful for tracing how provisions refer to each other — without fetching their text.
GET /documents/{id}/index?mode=xrefs&clauseIds=98001,98002
Response (200):
{
"spec": "syntheia/v1-draft",
"documentId": "10567",
"documentTitle": "Acme NDA 2024",
"extraction_mode": "acceptedOnly",
"nodes": [
{ "nodeId": "98001", "crossReferencedIds": ["98045", "98067"] }
]
}
Step 4 — Pull verbatim text
Once you have the clauseId values you need, call POST /clauses/batch to fetch verbatim text for up to 500 clauses at once — across any documents in the workspace.
API Reference:
POST /clauses/batch
POST /clauses/batch
Content-Type: application/json
Request body:
{
"clauseIds": ["98001", "98002"]
}
| Field | Required | Description |
|---|---|---|
clauseIds | Yes | Array of clause IDs to fetch text for (max 500) |
Response (200):
{
"clauses": [
{
"clauseId": "98001",
"text": "Payment shall be made within 30 days of the invoice date..."
},
{
"clauseId": "98002",
"text": "Any payment not received within the due date shall incur..."
}
]
}
Returns one entry per requested clauseId. When a clause has no direct text, its descendants' text is joined together. text is null when neither the clause nor any descendant has text.
End-to-end example (TypeScript)
Map a document with structure, then pull verbatim text for the top-level nodes with POST /clauses/batch.
const BASE = 'https://api.your-domain.com';
const HEADERS = {
'x-user-api-key': process.env.SYNTHEIA_API_KEY!,
'x-workspace-id': process.env.SYNTHEIA_WORKSPACE_ID!,
'x-app-name': 'my-app',
'Content-Type': 'application/json',
};
type IndexNode = { nodeId: string; title: string | null; children: IndexNode[] };
async function getStructure(docId: string, depth = 3): Promise<{ documentIndex: IndexNode[] }> {
const res = await fetch(`${BASE}/documents/${docId}/index?mode=structure&depth=${depth}`, {
headers: HEADERS,
});
return res.json();
}
async function batchFetch(clauseIds: string[]): Promise<{ clauses: { clauseId: string; text: string | null }[] }> {
const res = await fetch(`${BASE}/clauses/batch`, {
method: 'POST',
headers: HEADERS,
body: JSON.stringify({ clauseIds }),
});
return res.json();
}
// Map the document, then fetch text for the first 5 top-level nodes
const { documentIndex } = await getStructure('10567');
const topClauseIds = documentIndex.slice(0, 5).map((n) => n.nodeId);
const { clauses } = await batchFetch(topClauseIds);
console.log(clauses);
Next steps
- API Reference — Document Index — full parameter reference for structure and xrefs modes
- API Reference — Batch fetch clause text — batch text retrieval