> ## 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.

# Getting Comparison Results as a JSON via the Change-Details Endpoint (API Self Hosted)

> The change-details endpoint allows you to retrieve comparison results as a structured JSON response, including individual changes, style information, positional data, and a summary of all differences found.

<Warning>
  **This documentation is for API Self-Hosted Version 2 (Legacy)**

  Version 2 is a legacy deployment model. All new customers **must** use Version 3, which features a modern multi-container architecture with improved security and performance.

  👉 [Get started with API Self-Hosted v3](/hc/en-us/articles/51133475373465-API-Self-Hosted-v3-Quick-Start-Guide)
</Warning>

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

```bash theme={null}
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:

```bash theme={null}
Authorization: Token your_access_token
```

### Example request

```bash theme={null}
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:

| Key        | Description                                                                                |
| ---------- | ------------------------------------------------------------------------------------------ |
| `changes`  | Array of change objects representing every matched, inserted, deleted, or replaced segment |
| `summary`  | Statistical summary of the comparison including change counts and document metadata        |
| `s3_links` | Presigned URLs for downloading highlighted PDFs, original documents, and viewer data files |

<Note>
  Depending on your self-hosted storage configuration, the `s3_links` key may contain URLs pointing to your local storage rather than AWS S3.
</Note>

## 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

| Kind      | Description                                                                      | Text fields             | Region fields               |
| --------- | -------------------------------------------------------------------------------- | ----------------------- | --------------------------- |
| `match`   | Text is identical in both documents                                              | `text`                  | `leftRegion`, `rightRegion` |
| `insert`  | Text exists only in the right document                                           | `rightText`             | `rightRegion`               |
| `delete`  | Text exists only in the left document                                            | `leftText`              | `leftRegion`                |
| `replace` | Text in the left document was replaced with different text in the right document | `leftText`, `rightText` | `leftRegion`, `rightRegion` |

### Common fields on all change objects

| Field              | Type           | Description                                                                                           |
| ------------------ | -------------- | ----------------------------------------------------------------------------------------------------- |
| `kind`             | string         | The type of change: `match`, `insert`, `delete`, or `replace`                                         |
| `moveId`           | string or null | If present, indicates this change is part of a move operation (see [Move detection](#move-detection)) |
| `stylesInfo`       | object         | Font, color, and emphasis information for the text (see [Styles info](#styles-info))                  |
| `viewerProperties` | object         | Properties used by the Draftable viewer (typically an empty object)                                   |

### Additional fields by kind

| Field           | Present on                   | Type   | Description                                                                          |
| --------------- | ---------------------------- | ------ | ------------------------------------------------------------------------------------ |
| `text`          | `match`                      | string | The matched text content                                                             |
| `leftText`      | `delete`, `replace`          | string | Text from the left (older) document                                                  |
| `rightText`     | `insert`, `replace`          | string | Text from the right (newer) document                                                 |
| `leftRegion`    | `match`, `delete`, `replace` | object | Position of the text in the left document (see [Region objects](#region-objects))    |
| `rightRegion`   | `match`, `insert`, `replace` | object | Position of the text in the right document (see [Region objects](#region-objects))   |
| `deletionMark`  | `delete`, `replace`          | object | Marker position in the left document margin (see [Marker objects](#marker-objects))  |
| `insertionMark` | `insert`, `replace`          | object | Marker position in the right document margin (see [Marker objects](#marker-objects)) |

## Examples by change kind

### Match

A `match` change represents text that is identical in both documents.

```json theme={null}
{
  "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.

```json theme={null}
{
  "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.

```json theme={null}
{
  "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.

```json theme={null}
{
  "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}"`:

| Component   | Description                                                                                                                        |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `moveGroup` | Identifies which move operation this change belongs to. All changes sharing the same `moveGroup` number are part of the same move. |
| `subIndex`  | When 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

```json theme={null}
// 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": {}
}
```

```json theme={null}
{
  "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:

| moveId        | Kind                     | Description                                              |
| ------------- | ------------------------ | -------------------------------------------------------- |
| `"0-0"`       | `delete` / `insert` pair | First chunk of moved text (unchanged)                    |
| *(no moveId)* | `replace`                | An edit within the moved text (the "gap" at sub-index 1) |
| `"0-2"`       | `delete` / `insert` pair | Next 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:

```python theme={null}
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)")
```

<Note>
  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.
</Note>

## 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`.

| Field            | Type    | Description                                                                                                                                                                                                                                                                                                                    |
| ---------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `pageIndex`      | integer | Zero-based page number where the text appears                                                                                                                                                                                                                                                                                  |
| `rectangles`     | array   | Bounding 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.                                                      |
| `regionOutlines` | array   | Outline 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. |

<Note>
  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.
</Note>

## 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.

| Field       | Type    | Description                                                                        |
| ----------- | ------- | ---------------------------------------------------------------------------------- |
| `pageIndex` | integer | Zero-based page number where the marker appears                                    |
| `point`     | array   | An `[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.

| Field           | Type  | Description                                                                                                     |
| --------------- | ----- | --------------------------------------------------------------------------------------------------------------- |
| `leftStyles`    | array | Array of style objects applied to the left document text (absent on `insert` changes)                           |
| `rightStyles`   | array | Array of style objects applied to the right document text (absent on `delete` changes)                          |
| `leftStyleMap`  | array | Maps character ranges to styles in `leftStyles` (see below)                                                     |
| `rightStyleMap` | array | Maps character ranges to styles in `rightStyles` (see below)                                                    |
| `styleChanges`  | array | List 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**:

| Field      | Type   | Description                                                                                            |
| ---------- | ------ | ------------------------------------------------------------------------------------------------------ |
| `color`    | string | Hex color code of the text in the original document (e.g., `"#000000"` for black, `"#FF0000"` for red) |
| `font`     | string | Font name (e.g., `"ArialMT"`, `"Arial-BoldMT"`)                                                        |
| `emphasis` | string | Text emphasis: `""` (none), `"bold"`, `"italics"`, or `"bold italics"`                                 |
| `size`     | number | Font size in points                                                                                    |

<Warning>
  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.
</Warning>

### 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:

```json theme={null}
"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:

```json theme={null}
"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.

```json theme={null}
{
  "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

| Field        | Type    | Description                                                   |
| ------------ | ------- | ------------------------------------------------------------- |
| `anyChanges` | boolean | `true` if any differences were detected between the documents |
| `anyMatches` | boolean | `true` if any matching text was found between the documents   |

### Change summary fields

| Field                | Type    | Description                                                             |
| -------------------- | ------- | ----------------------------------------------------------------------- |
| `matches`            | integer | Number of matched (unchanged) text segments                             |
| `deletions`          | integer | Number of deleted text segments                                         |
| `insertions`         | integer | Number of inserted text segments                                        |
| `replacements`       | integer | Number of replaced text segments                                        |
| `matchingWords`      | integer | Total word count across all matched segments                            |
| `deletedLeftWords`   | integer | Total word count of deleted text from the left document                 |
| `replacedLeftWords`  | integer | Total word count of replaced text from the left document                |
| `insertedRightWords` | integer | Total word count of inserted text in the right document                 |
| `replacedRightWords` | integer | Total word count of replacement text in the right document              |
| `styleChanges`       | integer | Number of style-only changes (text is identical but formatting differs) |

### Document summary fields

Both `leftDocumentSummary` and `rightDocumentSummary` contain:

| Field            | Type    | Description                           |
| ---------------- | ------- | ------------------------------------- |
| `pageCount`      | integer | Total number of pages in the document |
| `characterCount` | integer | Total character count                 |
| `wordCount`      | integer | Total 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 field                       | Visual representation                                                                                                                                                                                                               |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `kind`                           | Determines 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 |
| `rectangles`                     | Define the exact bounding boxes of each highlight region on the page                                                                                                                                                                |
| `regionOutlines`                 | Define the precise polygon shape of each highlight overlay (important for multi-line or irregularly shaped regions)                                                                                                                 |
| `deletionMark` / `insertionMark` | Correspond to the small margin indicators shown next to each change in the viewer                                                                                                                                                   |
| `stylesInfo`                     | Captures the original document formatting — useful for detecting style-only changes, but does **not** represent comparison highlight colors                                                                                         |

<Tip>
  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").
</Tip>

## S3 links

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.

| Key                 | Description                                                                                                                            |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `left`              | Original left (older) document                                                                                                         |
| `right`             | Original right (newer) document                                                                                                        |
| `left_highlighted`  | Left document with comparison highlights applied (PDF) — deletions shown with red strikethrough, left side of replacements highlighted |
| `right_highlighted` | Right document with comparison highlights applied (PDF) — insertions and right side of replacements shown with colored highlight boxes |
| `scrollmap`         | Scroll synchronization data used by the Draftable viewer                                                                               |
| `changelist`        | Change list data used by the Draftable viewer                                                                                          |

<Note>
  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.).
</Note>

## Fetching change-details via code (Python)

You can access the change-details endpoint programmatically. Below is an example using Python.

<Note>
  The change-details endpoint is **not** part of any existing Draftable client library. You will need to call it directly using an HTTP client.
</Note>

```python theme={null}
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.
