Skip to content

Selective compare

Compare a range of paragraphs in one document with a range in another, and get the result at once as JSON, Markdown and a PDF.

View as Markdown

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 URLhttps://api.versionstory.com
AuthAuthorization: Bearer vs_.... See Authentication

The flow

  1. Upload each document with POST /v1/files, or use the file_id of a file you already have: an upload, an edit's files.clean, or a comparison's redline.
  2. Find the ranges. Read each file's listing with GET /v1/files/{file_id}?format=md (or json). It prints every paragraph's index and id; note the first and last paragraph of each range.
  3. 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.

FieldRequiredMeaning
file_idYesThe file holding the range. Both sides may name the same file
fromYesThe range's first paragraph, as a paragraph locator
throughYesThe 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:

LocatorNames
{"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 ..."
}
FieldMeaning
comparison.original, comparison.modifiedEach side's file and how many paragraphs its range holds
comparison.summaryHow many lined-up paragraphs have each status
comparison.paragraphsThe 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
markdownThe same result with changes marked by <ins> and <del>, each paragraph opened by <p original-unid="..." modified-unid="..."/>
fileA new file holding the same result as a PDF. See The PDF file
messageWhat 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

StatusCodeCause
400INVALID_REQUESTA 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
400PARAGRAPH_NOT_FOUNDA paragraph index or id is not in its file
400RANGE_TOO_LARGEThe 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
400UNSUPPORTED_FILE_TYPEA workbook or presentation, which has no paragraphs
400INVALID_FILE_IDA file_id is malformed
401INVALID_API_KEYMissing, malformed, unknown, revoked, or expired key
404FILE_NOT_FOUNDNo such file, or it belongs to another organization
409CONFLICTA file is still being prepared (retry after Retry-After), or its preparation failed (upload it again)
410CONTENT_DELETEDA file's content was deleted under zero-day retention
  • 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