Skip to main content

Documentation Index

Fetch the complete documentation index at: https://help.draftable.com/llms.txt

Use this file to discover all available pages before exploring further.

The change-details endpoint returns a detailed JSON breakdown of every change detected between two compared documents. This includes the type of change, the affected text, positional coordinates on each page, font and style information, and a statistical summary.

Endpoint

GET https://<your-self-hosted-domain>/api/v1/comparisons/<Comparison_ID>/change-details
  • your-self-hosted-domain: The base URL for your self-hosted Draftable API installation.
  • Comparison_ID: The unique identifier for the comparison (returned when you create a comparison via the API).

Authentication

Include your API token in the Authorization header:
Authorization: Token your_access_token

Example request

curl -H "Authorization: Token your_access_token" \
  "https://your-server.example.com/api/v1/comparisons/LxBGpPTO/change-details"

Response structure

The JSON response contains three top-level keys:
KeyDescription
changesArray of change objects representing every matched, inserted, deleted, or replaced segment
summaryStatistical summary of the comparison including change counts and document metadata
s3_linksPresigned URLs for downloading highlighted PDFs, original documents, and viewer data files
Depending on your self-hosted storage configuration, the s3_links key may contain URLs pointing to your local storage rather than AWS S3.

Change objects

Each object in the changes array represents a segment of text that was either matched, inserted, deleted, or replaced between the left (older) and right (newer) documents.

Change kinds

KindDescriptionText fieldsRegion fields
matchText is identical in both documentstextleftRegion, rightRegion
insertText exists only in the right documentrightTextrightRegion
deleteText exists only in the left documentleftTextleftRegion
replaceText in the left document was replaced with different text in the right documentleftText, rightTextleftRegion, rightRegion

Common fields on all change objects

FieldTypeDescription
kindstringThe type of change: match, insert, delete, or replace
moveIdstring or nullIf present, indicates this change is part of a move operation (see Move detection)
stylesInfoobjectFont, color, and emphasis information for the text (see Styles info)
viewerPropertiesobjectProperties used by the Draftable viewer (typically an empty object)

Additional fields by kind

FieldPresent onTypeDescription
textmatchstringThe matched text content
leftTextdelete, replacestringText from the left (older) document
rightTextinsert, replacestringText from the right (newer) document
leftRegionmatch, delete, replaceobjectPosition of the text in the left document (see Region objects)
rightRegionmatch, insert, replaceobjectPosition of the text in the right document (see Region objects)
deletionMarkdelete, replaceobjectMarker position in the left document margin (see Marker objects)
insertionMarkinsert, replaceobjectMarker position in the right document margin (see Marker objects)

Examples by change kind

Match

A match change represents text that is identical in both documents.
{
  "kind": "match",
  "text": "ANNUAL REPORT",
  "leftRegion": {
    "pageIndex": 0,
    "rectangles": [
      { "left": 270.0, "top": 297.85, "right": 373.53, "bottom": 311.17 }
    ],
    "regionOutlines": [
      {
        "points": [
          [373.53, 297.85],
          [270.0, 297.85],
          [270.0, 311.17],
          [373.53, 311.17]
        ]
      }
    ]
  },
  "rightRegion": {
    "pageIndex": 0,
    "rectangles": [
      { "left": 270.0, "top": 297.85, "right": 373.53, "bottom": 311.17 }
    ],
    "regionOutlines": [
      {
        "points": [
          [373.53, 297.85],
          [270.0, 297.85],
          [270.0, 311.17],
          [373.53, 311.17]
        ]
      }
    ]
  },
  "stylesInfo": {
    "leftStyles": [
      { "color": "#000000", "font": "Arial-BoldMT", "emphasis": "bold", "size": 12.0 }
    ],
    "rightStyles": [
      { "color": "#000000", "font": "Arial-BoldMT", "emphasis": "bold", "size": 12.0 }
    ],
    "leftStyleMap": ["0-12"],
    "rightStyleMap": ["0-12"],
    "styleChanges": []
  },
  "viewerProperties": {}
}

Insert

An insert change represents text that exists only in the right (newer) document.
{
  "kind": "insert",
  "rightText": "See comment above",
  "rightRegion": {
    "pageIndex": 3,
    "rectangles": [
      { "left": 12.8, "top": 449.05, "right": 121.79, "bottom": 462.54 }
    ],
    "regionOutlines": [
      {
        "points": [
          [121.79, 449.05],
          [12.8, 449.05],
          [12.8, 462.54],
          [121.79, 462.54]
        ]
      }
    ]
  },
  "stylesInfo": {
    "rightStyles": [
      { "color": "#FF0000", "font": "Chrom Sans OTF", "emphasis": "", "size": 12.0 }
    ],
    "rightStyleMap": ["0-16"]
  },
  "insertionMark": {
    "pageIndex": 3,
    "point": [66.68, 477.6]
  },
  "viewerProperties": {}
}

Delete

A delete change represents text that exists only in the left (older) document.
{
  "kind": "delete",
  "leftText": "$9,500",
  "leftRegion": {
    "pageIndex": 3,
    "rectangles": [
      { "left": 71.13, "top": 533.38, "right": 98.62, "bottom": 543.63 }
    ],
    "regionOutlines": [
      {
        "points": [
          [98.62, 533.38],
          [71.13, 533.38],
          [71.13, 543.63],
          [98.62, 543.63]
        ]
      }
    ]
  },
  "stylesInfo": {
    "leftStyles": [
      { "color": "#000000", "font": "ArialMT", "emphasis": "", "size": 9.24 }
    ],
    "leftStyleMap": ["0-5"]
  },
  "deletionMark": {
    "pageIndex": 3,
    "point": [98.62, 521.62]
  },
  "viewerProperties": {}
}

Replace

A replace change represents text that was changed between the left and right documents.
{
  "kind": "replace",
  "leftText": "21",
  "rightText": "20",
  "leftRegion": {
    "pageIndex": 1,
    "rectangles": [
      { "left": 555.07, "top": 371.1, "right": 564.97, "bottom": 383.31 }
    ],
    "regionOutlines": [
      {
        "points": [
          [564.97, 371.1],
          [555.07, 371.1],
          [555.07, 383.31],
          [564.97, 383.31]
        ]
      }
    ]
  },
  "rightRegion": {
    "pageIndex": 1,
    "rectangles": [
      { "left": 555.07, "top": 371.1, "right": 566.45, "bottom": 383.31 }
    ],
    "regionOutlines": [
      {
        "points": [
          [566.45, 371.1],
          [555.07, 371.1],
          [555.07, 383.31],
          [566.45, 383.31]
        ]
      }
    ]
  },
  "stylesInfo": {
    "leftStyles": [
      { "color": "#000000", "font": "ArialMT", "emphasis": "", "size": 11.0 }
    ],
    "rightStyles": [
      { "color": "#000000", "font": "ArialMT", "emphasis": "", "size": 11.0 }
    ],
    "leftStyleMap": ["0-1"],
    "rightStyleMap": ["0-1"]
  },
  "deletionMark": {
    "pageIndex": 1,
    "point": [35.99, 409.32]
  },
  "insertionMark": {
    "pageIndex": 1,
    "point": [35.99, 409.32]
  },
  "viewerProperties": {}
}

Move detection

When Draftable detects that a block of text was moved (cut from one location and pasted to another), the individual insert and delete changes that make up the move are linked together using the moveId field. This allows you to distinguish a true move from unrelated insertions and deletions.

The moveId format

The moveId is a string in the format "{moveGroup}-{subIndex}":
ComponentDescription
moveGroupIdentifies which move operation this change belongs to. All changes sharing the same moveGroup number are part of the same move.
subIndexWhen a moved block is split across paragraphs or pages, each chunk receives its own sub-index (e.g., 0-0, 0-1, 0-2).
Changes that are not part of a move operation will not have a moveId field.

How moves appear in the changes array

A move is represented as paired delete and insert entries:
  • A delete change with moveId — the text was removed from this location in the left document
  • An insert change with the same moveId — the text was placed at this location in the right document
// Text moved from page 2 to page 5
{
  "kind": "delete",
  "moveId": "0-0",
  "leftText": "This paragraph was moved to a different section.",
  "leftRegion": {
    "pageIndex": 1,
    "rectangles": [
      { "left": 72.0, "top": 200.0, "right": 540.0, "bottom": 214.0 }
    ],
    "regionOutlines": [...]
  },
  "stylesInfo": { ... },
  "deletionMark": { "pageIndex": 1, "point": [36.0, 207.0] },
  "viewerProperties": {}
}
{
  "kind": "insert",
  "moveId": "0-0",
  "rightText": "This paragraph was moved to a different section.",
  "rightRegion": {
    "pageIndex": 4,
    "rectangles": [
      { "left": 72.0, "top": 350.0, "right": 540.0, "bottom": 364.0 }
    ],
    "regionOutlines": [...]
  },
  "stylesInfo": { ... },
  "insertionMark": { "pageIndex": 4, "point": [36.0, 357.0] },
  "viewerProperties": {}
}

Gaps in the sub-index sequence

When a moved block also contains edits (text that was changed within the moved content), the sub-index sequence will have gaps. For example, you might see moveId values of "0-0" and "0-2" but no "0-1". The gap positions represent portions of the moved text that were edited — those edits appear as regular replace, insert, or delete changes without a moveId. Example sequence for a move with internal edits:
moveIdKindDescription
"0-0"delete / insert pairFirst chunk of moved text (unchanged)
(no moveId)replaceAn edit within the moved text (the “gap” at sub-index 1)
"0-2"delete / insert pairNext chunk of moved text (unchanged)

Reconstructing move regions

Each individual change that is part of a move still carries its own leftRegion or rightRegion with full bounding box coordinates. To get the complete regions for an entire move operation, group changes by their moveGroup and collect the regions:
from collections import defaultdict

def group_moves(changes):
    """Group move-related changes by their move group."""
    moves = defaultdict(lambda: {"deletes": [], "inserts": []})

    for change in changes:
        move_id = change.get("moveId")
        if not move_id:
            continue

        move_group = move_id.split("-")[0]

        if change["kind"] == "delete":
            moves[move_group]["deletes"].append(change)
        elif change["kind"] == "insert":
            moves[move_group]["inserts"].append(change)

    return dict(moves)

# Reconstruct left/right regions for each move
for group_id, move_data in group_moves(changes).items():
    left_regions = [c["leftRegion"] for c in move_data["deletes"]]
    right_regions = [c["rightRegion"] for c in move_data["inserts"]]
    print(f"Move {group_id}: {len(left_regions)} source region(s), "
          f"{len(right_regions)} destination region(s)")
The moveId field is only present on delete and insert changes that Draftable has identified as part of a move operation. Not all deletions and insertions are moves — only those where the comparison engine has matched the removed text to inserted text elsewhere in the document.

Region objects

Region objects describe the physical location of text on a page. They appear as leftRegion and rightRegion on change objects. These regions correspond directly to the colored highlight boxes visible in the Draftable viewer and in the highlighted PDF exports available via s3_links.
FieldTypeDescription
pageIndexintegerZero-based page number where the text appears
rectanglesarrayBounding rectangles for the text region. Each rectangle has left, top, right, and bottom coordinates (in points from the top-left corner of the page). Text spanning multiple lines or non-contiguous areas (such as chart labels) will have multiple rectangles.
regionOutlinesarrayOutline polygons for the text region. Each outline has a points array of [x, y] coordinate pairs defining the exact highlight shape. For simple rectangular regions, this will be a four-point polygon matching the rectangle. For complex or multi-line regions, this defines the precise shape of the highlight overlay.
Coordinates are measured in points (1 point = 1/72 of an inch) from the top-left corner of the page. A standard A4 page is approximately 595 x 842 points. A US Letter page is approximately 612 x 792 points.

Marker objects

Marker objects (deletionMark and insertionMark) indicate where change markers appear in the document margin. These correspond to the small indicators shown alongside changes in the Draftable viewer and highlighted PDF exports — they allow you to identify which margin marker corresponds to which change.
FieldTypeDescription
pageIndexintegerZero-based page number where the marker appears
pointarrayAn [x, y] coordinate pair indicating the marker position on the page (in points)

Styles info

The stylesInfo object provides font, color, and emphasis data for the text in a change. It allows you to understand the formatting of the text and detect style-only changes.
FieldTypeDescription
leftStylesarrayArray of style objects applied to the left document text (absent on insert changes)
rightStylesarrayArray of style objects applied to the right document text (absent on delete changes)
leftStyleMaparrayMaps character ranges to styles in leftStyles (see below)
rightStyleMaparrayMaps character ranges to styles in rightStyles (see below)
styleChangesarrayList of style-only changes detected (present on match changes where text is identical but formatting differs)

Style objects

Each style object in leftStyles or rightStyles describes the formatting for a range of characters in the original source document:
FieldTypeDescription
colorstringHex color code of the text in the original document (e.g., "#000000" for black, "#FF0000" for red)
fontstringFont name (e.g., "ArialMT", "Arial-BoldMT")
emphasisstringText emphasis: "" (none), "bold", "italics", or "bold italics"
sizenumberFont size in points
The color field represents the text color from the original document, not the comparison highlight color. For example, if a PDF contains a red annotation comment, its color will be "#FF0000" because that is the original text color — not because Draftable highlighted it.

Style map format

The leftStyleMap and rightStyleMap arrays map character ranges in the text to entries in the corresponding styles array. Each entry is a string in the format "start-end", where start and end are zero-based character indices (inclusive). When a text segment has a single style, the style map contains one entry:
"leftStyleMap": ["0-12"]
When a text segment has multiple styles (e.g., mixed font sizes), the style map contains multiple comma-separated ranges per entry or multiple entries:
"leftStyleMap": ["0-25", "26-26"]
This means characters 0 through 25 use the first style in leftStyles, and character 26 uses the second style.

Summary object

The summary object provides an overview of the comparison results.
{
  "anyChanges": true,
  "anyMatches": true,
  "changeSummary": {
    "matches": 998,
    "deletions": 24,
    "insertions": 19,
    "replacements": 67,
    "matchingWords": 16738,
    "deletedLeftWords": 163,
    "replacedLeftWords": 70,
    "insertedRightWords": 139,
    "replacedRightWords": 69,
    "styleChanges": 0
  },
  "leftDocumentSummary": {
    "pageCount": 32,
    "characterCount": 75721,
    "wordCount": 16862
  },
  "rightDocumentSummary": {
    "pageCount": 32,
    "characterCount": 75092,
    "wordCount": 16836
  }
}

Summary fields

FieldTypeDescription
anyChangesbooleantrue if any differences were detected between the documents
anyMatchesbooleantrue if any matching text was found between the documents

Change summary fields

FieldTypeDescription
matchesintegerNumber of matched (unchanged) text segments
deletionsintegerNumber of deleted text segments
insertionsintegerNumber of inserted text segments
replacementsintegerNumber of replaced text segments
matchingWordsintegerTotal word count across all matched segments
deletedLeftWordsintegerTotal word count of deleted text from the left document
replacedLeftWordsintegerTotal word count of replaced text from the left document
insertedRightWordsintegerTotal word count of inserted text in the right document
replacedRightWordsintegerTotal word count of replacement text in the right document
styleChangesintegerNumber of style-only changes (text is identical but formatting differs)

Document summary fields

Both leftDocumentSummary and rightDocumentSummary contain:
FieldTypeDescription
pageCountintegerTotal number of pages in the document
characterCountintegerTotal character count
wordCountintegerTotal word count

How the JSON maps to the visual comparison

The change-details JSON provides the same underlying data that powers the Draftable viewer and highlighted PDF exports. Here is how the key fields relate to what you see visually:
JSON fieldVisual representation
kindDetermines the highlight color: deletions and the left side of replacements appear as red strikethrough in the left document; insertions and the right side of replacements appear as colored highlight boxes in the right document
rectanglesDefine the exact bounding boxes of each highlight region on the page
regionOutlinesDefine the precise polygon shape of each highlight overlay (important for multi-line or irregularly shaped regions)
deletionMark / insertionMarkCorrespond to the small margin indicators shown next to each change in the viewer
stylesInfoCaptures the original document formatting — useful for detecting style-only changes, but does not represent comparison highlight colors
You can use the rectangles coordinates to programmatically draw your own highlight overlays on the original PDFs, or to build custom change navigation (e.g., “jump to change on page 5”).
The s3_links object contains presigned URLs for downloading documents and viewer data files associated with the comparison. These URLs are temporary and will expire.
KeyDescription
leftOriginal left (older) document
rightOriginal right (newer) document
left_highlightedLeft document with comparison highlights applied (PDF) — deletions shown with red strikethrough, left side of replacements highlighted
right_highlightedRight document with comparison highlights applied (PDF) — insertions and right side of replacements shown with colored highlight boxes
scrollmapScroll synchronization data used by the Draftable viewer
changelistChange list data used by the Draftable viewer
The left_highlighted and right_highlighted PDFs are the same highlighted documents used in the Draftable combined comparison export. The combined export interleaves them page-by-page (left page 1, right page 1, left page 2, right page 2, etc.).

Fetching change-details via code (Python)

You can access the change-details endpoint programmatically. Below is an example using Python.
The change-details endpoint is not part of any existing Draftable client library. You will need to call it directly using an HTTP client.
import requests
import json

# Replace with your self-hosted domain, API access token, and comparison ID
base_url = "https://your-server.example.com"
access_token = "your_access_token"
comparison_id = "your_comparison_id"

url = f"{base_url}/api/v1/comparisons/{comparison_id}/change-details"

headers = {
    "Authorization": f"Token {access_token}",
}

response = requests.get(url, headers=headers)

if response.status_code == 200:
    data = response.json()

    # Access the summary
    summary = data["summary"]
    print(f"Changes found: {summary['anyChanges']}")
    print(f"Deletions: {summary['changeSummary']['deletions']}")
    print(f"Insertions: {summary['changeSummary']['insertions']}")
    print(f"Replacements: {summary['changeSummary']['replacements']}")

    # Iterate through changes
    for change in data["changes"]:
        if change["kind"] == "replace":
            print(f"Replaced '{change['leftText']}' with '{change['rightText']}'")
        elif change["kind"] == "insert":
            print(f"Inserted: '{change['rightText']}'")
        elif change["kind"] == "delete":
            print(f"Deleted: '{change['leftText']}'")
else:
    print(f"Request failed with status code: {response.status_code}")

Important considerations

  1. Authentication: You must include a valid API token in the Authorization header. Ensure your token has access to the comparison you are querying.
  2. Comparison must be complete: The change-details endpoint is only available after a comparison has finished processing. Check the comparison status via the main comparisons endpoint before requesting change details.
  3. Error handling: Handle common HTTP status codes in your code:
    • 401 Unauthorized — Invalid or missing API token
    • 404 Not Found — Comparison ID does not exist or has expired
  4. Data privacy: The response contains the full text content of both compared documents. Ensure appropriate security measures are in place when storing or transmitting this data.
  5. Presigned URL expiry: The URLs in s3_links are temporary presigned URLs. Retrieve fresh URLs by calling the endpoint again if they have expired.
For more information on other API endpoints, refer to your self-hosted Draftable API documentation. If you encounter any issues accessing the endpoint or need assistance, reach out to our support team.