Bundles
A master agreement and its four amendments are one contract, and a table asking about it should have one row. A bundle is that: several files that answer as a single subject.
What a bundle changes
A row points at either a file or a bundle. When it points at a bundle, the question is put to every document in it at once and the answer resolves across them — so "what is the notice period" reads the amendment that changed it rather than the original that did not.
Order and effect are yours to set. sortOrder is the sequence, effectiveAt is when a document took effect, and isPrimary marks the master. The role is a free-text label — "Master agreement", "Amendment 3" — that shows up alongside the document.
Bundles cost more to run
Answering a bundle means reading several documents for one cell, so a bundled row is charged the column's rate plus 4 credits per member past the third. A five-document bundle answering a 4-credit column costs 4 + 8 = 12 credits per cell.
A compositional column is charged on a gentler curve — 4 credits per six members rather than per member past three — because it spreads one retrieval budget across the bundle instead of reading each document separately. Five members cost it nothing extra.
Price it before you commit with the free run preview, which accounts for this.
Changing a bundle makes its answers stale
Every bundle carries documentsVersion, which increments when its membership changes. Cells record the version they were answered against, and any cell whose recorded version is behind comes back with stale: true.
Nothing re-runs automatically. Adding an amendment marks the row's answers as questionable and leaves them readable; running it again is a deliberate act that costs credits. That is the same rule editing a column's prompt follows.
Fifteen files
A bundle holds at most 15 files, and POST …/files accepts between 1 and 15 ids per call. If a subject genuinely has more documents than that, it is usually two subjects.
List bundles
GET/v2/workspaces/:workspaceId/bundles
Requires Read on the workspace
Bundles in the workspace, without their members — fetch one to see what is in it.
curl https://api.ragextract.com/v2/workspaces/wks_7Kq2mB4nR8vXpL3d/bundles \
-H "x-api-key: $RAGEXTRACT_API_KEY"{
"success": true,
"total": 1,
"data": [ {
"id": "bndl_C6yT4gN8kM2vB5xQ",
"workspaceId": "wks_7Kq2mB4nR8vXpL3d",
"name": "Acme MSA (as amended)",
"color": "#404040",
"documentsVersion": 3,
"archivedAt": null,
"createdAt": 1756108800000,
"updatedAt": 1756195200000
}]
}Get a bundle
GET/v2/workspaces/:workspaceId/bundles/:bundleId
Requires Read on the workspace
The bundle and its membership. This is the only place the file list appears — a bundle's own object never carries it.
curl https://api.ragextract.com/v2/workspaces/wks_7Kq2mB4nR8vXpL3d/bundles/bndl_C6yT4gN8kM2vB5xQ \
-H "x-api-key: $RAGEXTRACT_API_KEY"{
"success": true,
"total": 1,
"data": {
"bundle": {
"id": "bndl_C6yT4gN8kM2vB5xQ",
"workspaceId": "wks_7Kq2mB4nR8vXpL3d",
"name": "Acme MSA (as amended)",
"color": "#404040",
"documentsVersion": 3,
"archivedAt": null,
"createdAt": 1756108800000,
"updatedAt": 1756195200000
},
"files": [
{
"fileId": "ds_M3xJ8pQ1vK5nB7wT",
"isPrimary": true,
"role": "Master agreement",
"effectiveAt": 1704067200000,
"sortOrder": 0,
"createdAt": 1756108800000,
"updatedAt": 1756108800000
},
{
"fileId": "ds_K2nV7cX4jL9bR1sD",
"isPrimary": false,
"role": "Amendment 1",
"effectiveAt": 1727740800000,
"sortOrder": 1,
"createdAt": 1756195200000,
"updatedAt": 1756195200000
}
]
}
}Create a bundle
POST/v2/workspaces/:workspaceId/bundles
Requires Read & write on the workspace
Creates the bundle and, optionally, populates it in the same call. The response is the bundle alone — fetch it to see the members back.
Parameters
name | string · required1–120 characters. |
|---|---|
fileIds | string[]Up to 15 ids, each starting ds_. They take sortOrder in the order given, and the first becomes isPrimary. |
color | string | nullUp to 16 characters. Presentation only; nothing reads it. |
curl -X POST https://api.ragextract.com/v2/workspaces/wks_7Kq2mB4nR8vXpL3d/bundles \
-H "x-api-key: $RAGEXTRACT_API_KEY" \
-H "content-type: application/json" \
-d '{
"name": "Acme MSA (as amended)",
"fileIds": ["ds_M3xJ8pQ1vK5nB7wT", "ds_K2nV7cX4jL9bR1sD"]
}'{
"success": true,
"total": 1,
"data": {
"id": "bndl_C6yT4gN8kM2vB5xQ",
"workspaceId": "wks_7Kq2mB4nR8vXpL3d",
"name": "Acme MSA (as amended)",
"color": "#404040",
"documentsVersion": 1,
"archivedAt": null,
"createdAt": 1756108800000,
"updatedAt": 1756108800000
}
}Add files to a bundle
POST/v2/workspaces/:workspaceId/bundles/:bundleId/files
Requires Read & write on the workspace
Adds one or more files and returns the bundle with its full membership. documentsVersion increments, which marks every cell on rows pointing at this bundle as stale.
role and effectiveAt apply to every id in the call, so send one request per distinct role. isPrimary is different — it applies to the first id only, because a bundle has at most one anchor.
The call is all-or-nothing on validation: an id that is not a file in this workspace, or a batch that would take the bundle past 15, is refused with 400 before anything is written. Re-adding a file that is already a member is a no-op rather than an error, so retrying a half-failed call is safe.
Parameters
fileIds | string[] · required1 to 15 ids, each starting ds_. |
|---|---|
isPrimary | booleanMarks these as the master document of the bundle. |
role | string | nullUp to 120 characters. Free text — "Master agreement", "Amendment 3". |
effectiveAt | number | nullEpoch milliseconds. When this document took effect, which is what lets a later amendment supersede an earlier one. |
curl -X POST https://api.ragextract.com/v2/workspaces/wks_7Kq2mB4nR8vXpL3d/bundles/bndl_C6yT4gN8kM2vB5xQ/files \
-H "x-api-key: $RAGEXTRACT_API_KEY" \
-H "content-type: application/json" \
-d '{
"fileIds": ["ds_W9fH3rY6tP1nA8mE"],
"role": "Amendment 2",
"effectiveAt": 1735689600000
}'{
"success": true,
"total": 1,
"data": {
"bundle": { "id": "bndl_C6yT4gN8kM2vB5xQ", "documentsVersion": 4, ... },
"files": [ ... ]
}
}Change a file's role in a bundle
PATCH/v2/workspaces/:workspaceId/bundles/:bundleId/files/:fileId
Requires Read & write on the workspace
Changes how a document sits inside the bundle without changing which documents are in it. Every field is optional; omitted ones are left alone.
Parameters
isPrimary | booleanMake this the master document. |
|---|---|
role | string | nullUp to 120 characters. |
effectiveAt | number | nullEpoch milliseconds. |
sortOrder | numberInteger, 0 or above. |
curl -X PATCH https://api.ragextract.com/v2/workspaces/wks_7Kq2mB4nR8vXpL3d/bundles/bndl_C6yT4gN8kM2vB5xQ/files/ds_K2nV7cX4jL9bR1sD \
-H "x-api-key: $RAGEXTRACT_API_KEY" \
-H "content-type: application/json" \
-d '{"role": "Amendment 1 (superseded)", "sortOrder": 2}'{
"success": true,
"total": 1,
"data": {
"fileId": "ds_K2nV7cX4jL9bR1sD",
"isPrimary": false,
"role": "Amendment 1 (superseded)",
"effectiveAt": 1727740800000,
"sortOrder": 2,
"createdAt": 1756195200000,
"updatedAt": 1756281600000
}
}Remove a file from a bundle
DELETE/v2/workspaces/:workspaceId/bundles/:bundleId/files/:fileId
Requires Read & write on the workspace
Takes the file out of the bundle. The file itself is untouched — it stays in the workspace and stays indexed. documentsVersion increments, so the bundle's answers go stale.
curl -X DELETE https://api.ragextract.com/v2/workspaces/wks_7Kq2mB4nR8vXpL3d/bundles/bndl_C6yT4gN8kM2vB5xQ/files/ds_K2nV7cX4jL9bR1sD \
-H "x-api-key: $RAGEXTRACT_API_KEY"{
"success": true,
"total": 1
}Rename a bundle
PATCH/v2/workspaces/:workspaceId/bundles/:bundleId
Requires Read & write on the workspace
Name and colour only. Neither is read by extraction, so this does not make anything stale.
Parameters
name | string1–120 characters. |
|---|---|
color | string | nullUp to 16 characters. |
curl -X PATCH https://api.ragextract.com/v2/workspaces/wks_7Kq2mB4nR8vXpL3d/bundles/bndl_C6yT4gN8kM2vB5xQ \
-H "x-api-key: $RAGEXTRACT_API_KEY" \
-H "content-type: application/json" \
-d '{"name": "Acme MSA (as amended to Dec 2026)"}'{
"success": true,
"total": 1,
"data": {
"id": "bndl_C6yT4gN8kM2vB5xQ",
"workspaceId": "wks_7Kq2mB4nR8vXpL3d",
"name": "Acme MSA (as amended to Dec 2026)",
"color": "#404040",
"documentsVersion": 3,
"archivedAt": null,
"createdAt": 1756108800000,
"updatedAt": 1756195200000
}
}Archive a bundle
DELETE/v2/workspaces/:workspaceId/bundles/:bundleId
Requires Read & write on the workspace
Archives rather than deletes — the response carries the archivedAt stamp it set. The files stay in the workspace.
curl -X DELETE https://api.ragextract.com/v2/workspaces/wks_7Kq2mB4nR8vXpL3d/bundles/bndl_C6yT4gN8kM2vB5xQ \
-H "x-api-key: $RAGEXTRACT_API_KEY"{
"success": true,
"total": 1,
"data": {
"id": "bndl_C6yT4gN8kM2vB5xQ",
"workspaceId": "wks_7Kq2mB4nR8vXpL3d",
"name": "Acme MSA (as amended)",
"color": "#404040",
"documentsVersion": 3,
"archivedAt": 1756281600000,
"createdAt": 1756108800000,
"updatedAt": 1756195200000
}
}