POST /v1/compare/selective compares a range of paragraphs in one document with a range in
another, or two ranges of the same document, such as one clause in two drafts. It answers in the
same response with the comparison as JSON and Markdown, and stores it as a new PDF file. It makes
no Word redline; to redline whole documents, use Compare.
| Base URL | https://api.versionstory.com |
| Auth | Authorization: Bearer vs_.... See Authentication |
The flow
- Upload each document with
POST /v1/files, or use thefile_idof a file you already have: an upload, an edit'sfiles.clean, or a comparison's redline. - Find the ranges. Read each file's listing with
GET /v1/files/{file_id}?format=md(orjson). It prints every paragraph'sindexandid; note the first and last paragraph of each range. - Compare with
POST /v1/compare/selective. The response carries the comparison and its PDF file, and nothing is left to poll.
POST /v1/compare/selective
application/json, with an original and a modified range. Each names its file and runs from
its first paragraph through its last, as an edit's
range of paragraphs does.
| Field | Required | Meaning |
|---|---|---|
file_id | Yes | The file holding the range. Both sides may name the same file |
from | Yes | The range's first paragraph, as a paragraph locator |
through | Yes | The range's last paragraph, as a paragraph locator: from itself or a paragraph after it |
Naming a paragraph
from and through name a paragraph as an edit does, with the same
paragraph locator, so a locator copied from
the listing is sent as it is printed:
| Locator | Names |
|---|---|
{"index": 12} | Body paragraph 12 |
{"id": "8532bf246ab64e8788c26d27cfd105b4"} | The paragraph with this id |
{"index": 12, "id": "8532bf246ab64e8788c26d27cfd105b4"} | Both, which must name the same paragraph |
A range is in the body: an index counts the body's paragraphs from 0 in document order, paragraphs
in tables included, as the listing numbers them. A locator with a header, footer or note part, or a
ref, is refused. The two ends of a side can be named differently, one by index and the other by id.
curl -sS https://api.versionstory.com/v1/compare/selective \
-H "Authorization: Bearer vs_live_..." \
-H "Content-Type: application/json" \
-d '{
"original": {"file_id": "3f2a9c1e-...", "from": {"index": 12}, "through": {"index": 18}},
"modified": {"file_id": "8c41d7b2-...", "from": {"id": "8532bf246ab64e8788c26d27cfd105b4"}, "through": {"index": 19}}
}'Ranges are inclusive. Each paragraph is read with its tracked changes accepted, and Word's list numbering is compared apart from the text. Paragraphs are lined up first, with identical paragraphs as anchors and the rest paired by how similar their words are, and then each pair is compared word by word.
Response: 200 OK
{
"comparison": {
"schema_version": 1,
"original": { "file_id": "3f2a9c1e-...", "file_name": "v1 NDA.docx", "paragraph_count": 7 },
"modified": { "file_id": "8c41d7b2-...", "file_name": "v2 NDA.docx", "paragraph_count": 8 },
"summary": { "unchanged": 5, "modified": 2, "inserted": 1, "deleted": 0 },
"paragraphs": [
{
"status": "modified",
"original_id": "...",
"modified_id": "...",
"original_number": "2.",
"modified_number": "2.",
"content": [
{ "text": "through no fault" },
{ "type": "insertion", "text": ", action, or omission" },
{ "text": " of the Recipient;" }
]
}
]
},
"markdown": "...",
"file": {
"file_id": "b4056e05-...",
"file_name": "Selective Compare - v1 NDA to v2 NDA.pdf",
"status": "ready",
"downloads": [
{
"format": "pdf",
"url": "https://documents.versionstory.com/...",
"file_name": "Selective Compare - v1 NDA to v2 NDA.pdf",
"expires_at": "2026-11-03T19:30:00Z"
}
]
},
"message": "The comparison is complete. Tell the user what changed between the two ranges ..."
}| Field | Meaning |
|---|---|
comparison.original, comparison.modified | Each side's file and how many paragraphs its range holds |
comparison.summary | How many lined-up paragraphs have each status |
comparison.paragraphs | The lined-up paragraphs in reading order. status is unchanged, modified, inserted (with only modified_id) or deleted (with only original_id). A content segment without type is unchanged text: skip the insertions to read the original, and the deletions to read the modified |
markdown | The same result with changes marked by <ins> and <del>, each paragraph opened by <p original-unid="..." modified-unid="..."/> |
file | A new file holding the same result as a PDF. See The PDF file |
message | What an agent should do next with the result, as in other responses |
The PDF file
The PDF is the one the Selective Compare panel exports in the Version Story app: inserted text blue
and underlined, deleted text red and struck through, each paragraph on its own line after its list
number. It is written before the response is sent, so file is always ready. Its download URL is
valid for one hour; GET /v1/files/{file_id} with its
file_id mints a fresh one. It is served as pdf only, and it cannot be compared, edited or read
as a listing. For an organization with
zero-day retention, it is deleted an hour after it
is made, or as soon as you call
POST /v1/files/{file_id}/acknowledge.
Each request makes a new file, so send it once and keep the file_id. It counts toward your usage
as a document with the words of the compared text; downloading its PDF, then or later, does not
count again.
Errors
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_REQUEST | A missing from or through, a locator that names no paragraph or names one outside the body, an index and id that name different paragraphs, a range that runs backwards, or a file with no listing to compare, such as another selective comparison's PDF |
| 400 | PARAGRAPH_NOT_FOUND | A paragraph index or id is not in its file |
| 400 | RANGE_TOO_LARGE | The ranges are too large to compare in one request: more than 40,000 pairs of paragraphs between identical ones, or a comparison of more than 300,000 characters. Compare smaller ranges |
| 400 | UNSUPPORTED_FILE_TYPE | A workbook or presentation, which has no paragraphs |
| 400 | INVALID_FILE_ID | A file_id is malformed |
| 401 | INVALID_API_KEY | Missing, malformed, unknown, revoked, or expired key |
| 404 | FILE_NOT_FOUND | No such file, or it belongs to another organization |
| 409 | CONFLICT | A file is still being prepared (retry after Retry-After), or its preparation failed (upload it again) |
| 410 | CONTENT_DELETED | A file's content was deleted under zero-day retention |
Related
- Compare: a Word redline of two whole documents
- Edit operations: every form of a paragraph locator
- Files: upload documents and download the comparison's PDF again
- Data handling: what is kept, and for how long