> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.datalab.to/llms.txt
> Use this file to discover all available pages before exploring further.

# Track Changes

> Pull tracked changes and comments from Word documents, PDFs, and images for review workflows

If you're working with legal documents, contracts, or any collaborative review process, you know how painful it is to manually track all the changes, comments, and revisions in documents.

This guide shows you how to extract all that markup programmatically using the Track Changes API.

# Overview

The Track Changes API extracts:

* Tracked changes: insertions and deletions
* Comments: margin comments and their note text

For DOCX input, every change and comment also carries the author name and timestamp. See [Output markup](#output-markup) for the exact tags each input type returns.

This allows you get a full revision history from your documents into clean HTML and Markdown.

`track_changes` is perfect for legal workflows where you need to:

* Generate redline summaries for clients
* Identify all changes made by specific parties
* Extract action items from comments
* Analyze negotiation patterns across contract versions
* Create audit trails of document revisions

The API accepts **DOCX, PDF, and image files**:

* **DOCX** — tracked changes are extracted exactly from the document XML. Author names, timestamps, and comments are fully preserved.
* **PDF, images (PNG, JPEG, TIFF, etc.), and other formats** — each page is rendered and read by a visual redline model that detects underlined or colored insertions, struck-through or bracketed deletions, "Inserted:" and "Deleted:" balloons, and margin comments. Scanned documents are handled the same way as digital PDFs. A page with no visible markup is returned as a plain parse, so you can submit a whole document without pre-filtering pages.

Submit your document to the dedicated Track Changes endpoint. The output will be provided in Markdown, HTML, and chunks format by default, with all tracked changes and comments preserved in the markup.

<Warning>
  The extraction path is chosen from the file extension. When you pass a `file_url` whose path has no recognizable extension (for example `https://example.com/download?id=123`), the request is treated as DOCX. For PDFs and images behind opaque URLs, download the file and upload it with `file` instead.
</Warning>

# Quick Start (SDK)

The simplest way to extract tracked changes is with the Python SDK:

```python theme={null}
from datalab_sdk import DatalabClient, TrackChangesOptions

client = DatalabClient()
options = TrackChangesOptions(output_format="markdown,html,chunks")

# Works with DOCX, PDF, and images
result = client.track_changes("contract.docx", options=options)
print(result.markdown)
```

# Making the API Request

Here's how to submit a document and extract its tracked changes using the REST API:

```python theme={null}
import requests
import mimetypes
import time
import os

API_URL = "https://www.datalab.to/api/v1/track-changes"
API_KEY = os.getenv("DATALAB_API_KEY")

# Not every Python install knows the DOCX MIME type; register it explicitly
mimetypes.add_type("application/vnd.openxmlformats-officedocument.wordprocessingml.document", ".docx")

def extract_tracked_changes(file_path, output_format='html,markdown'):
    """
    Extract tracked changes and comments from a document.

    Accepts DOCX (exact XML extraction), PDF, images, and other formats
    (visual redline model).

    Args:
        file_path: Path to the file (.docx, .pdf, .png, etc.)
        output_format: 'html' or 'markdown' or 'html,markdown'

    Returns:
        Dictionary with the converted content including tracked changes
    """
    mime_type, _ = mimetypes.guess_type(file_path)
    mime_type = mime_type or "application/octet-stream"

    with open(file_path, 'rb') as f:
        form_data = {
            'file': (os.path.basename(file_path), f, mime_type),
            'output_format': (None, output_format),
            'paginate': (None, False)  # Set to True if you want page breaks
        }

        headers = {"X-API-Key": API_KEY}
        response = requests.post(API_URL, files=form_data, headers=headers)
        data = response.json()

    # Poll for completion
    check_url = data["request_check_url"]
    max_polls = 300 # Set longer if needed

    for i in range(max_polls):
        time.sleep(2)
        response = requests.get(check_url, headers=headers)
        result = response.json()

        if result["status"] == "complete":
            return result
        elif result["status"] == "failed":
            raise Exception(f"Conversion failed: {result.get('error')}")

    raise TimeoutError("Conversion did not complete in time")
```

# Output markup

The response contains your document with all tracked changes preserved inline. For DOCX input, each tag carries revision metadata:

* Insertions: `<ins data-revision-author="Sandy Kwon" data-revision-datetime="2025-11-11T11:24:00">new text</ins>`
* Deletions: `<del data-revision-author="Vikram Oberoi" data-revision-datetime="2025-11-11T10:34:00">old text</del>`
* Comments: `<comment data-comment-author="Vikram Oberoi" data-comment-datetime="2025-11-11T10:32:00" data-comment-initial="VO" text="comment text">marked text</comment>`

For PDF and image input, the same tags are used but carry no author or timestamp attributes, because that metadata is not printed on the page:

* Insertions: `<ins>new text</ins>`
* Deletions: `<del>old text</del>`
* Comments: `<comment text="comment text">marked text</comment>`

<Warning>
  Only rely on `data-revision-author`, `data-revision-datetime`, and `data-comment-author` when the input is DOCX. Parse PDF and image output by tag name only.
</Warning>

Here is real Markdown output for the header of a [public regulatory redline PDF](https://illinoisshines.com/wp-content/uploads/2024/04/IL-Shines-DG-Contract-Requirements-redline-4.18.2024.pdf), where the dates were bumped from 2023 to 2024. Edits are tagged at the character level:

```markdown theme={null}
**Published April 1<ins>8</ins><del>7</del>, 202<ins>4</ins><del>3</del>**
**Compliance required by June <ins>3</ins><del>1</del>, 202<ins>4</ins><del>3</del>\***
```

This markup appears in HTML, Markdown, and chunks output. Markdown has no native syntax for insertions or comments, so the tags are kept as literal inline HTML.

# Analyzing Changes with LLMs

Once you have the extracted markup, you can use an LLM to analyze the changes.

Here's an example using OpenRouter to generate a redline summary:

```python theme={null}
import requests
import os

OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
OPENROUTER_MODEL = os.getenv("OPENROUTER_MODEL")


def analyze_changes_with_llm(marked_up_content, analysis_type='summary'):
    """
    Use an LLM via OpenRouter to analyze tracked changes.
    
    Args:
        marked_up_content: The HTML or Markdown with tracked changes
        analysis_type: Type of analysis ('summary', 'risks', 'by_author', etc.)
    
    Returns:
        LLM analysis of the changes
    """
    
    prompts = {
        'summary': """Analyze this contract with tracked changes and provide:
1. A concise summary of all changes made
2. Key changes that materially affect the agreement
3. Any changes that shift risk or obligations between parties
4. Recommended action items for legal review

Document with tracked changes:
{content}""",
        
        'by_author': """Review this document with tracked changes and create a report organized by author:
- List each author's changes
- Categorize changes as substantive vs. stylistic
- Highlight any conflicting changes between authors

Document:
{content}""",
        
        'risks': """Analyze this contract's tracked changes for potential legal risks:
- Identify changes that increase liability or obligations
- Flag any deletions of protective language
- Note additions that could be problematic
- Assess the overall risk profile of the revisions

Document:
{content}"""
    }
    
    prompt = prompts.get(analysis_type, prompts['summary']).format(content=marked_up_content)
    
    response = requests.post(
        url="https://openrouter.ai/api/v1/chat/completions",
        headers={
            "Authorization": f"Bearer {OPENROUTER_API_KEY}",
            "Content-Type": "application/json"
        },
        json={
            "model": OPENROUTER_MODEL,
            "messages": [
                {
                    "role": "user",
                    "content": prompt
                }
            ]
        }
    )
    
    return response.json()['choices'][0]['message']['content']

# Example usage
result = extract_tracked_changes('nda_draft_v3.docx', output_format='html')
marked_up_doc = result['html']

# Generate different types of analysis
summary = analyze_changes_with_llm(marked_up_doc, 'summary')
risk_analysis = analyze_changes_with_llm(marked_up_doc, 'risks')
# Per-author analysis needs DOCX input; PDF and image output has no author metadata
by_author = analyze_changes_with_llm(marked_up_doc, 'by_author')

print("Change Summary:")
print(summary)
print("\n" + "="*80 + "\n")
print("Risk Analysis:")
print(risk_analysis)
```

# Pagination

For longer documents, you may want to preserve page breaks in the output so you can split them. Set paginate to True in your request:

```python theme={null}
form_data = {
    'file': (os.path.basename(file_path), f, mime_type),
    'output_format': (None, 'html'),
    'paginate': (None, True)  # Enable pagination
}
```

**For Markdown output**, each page will preceded by a horizontal rule containing the page number:

```
{0}------------------------------------------------

<Page 0 Content>

{1}------------------------------------------------

<Page 1 Content>
```

**For HTML output**, each page will be wrapped in a div with the page number:

```html theme={null}
<div class="page" data-page-id="0">
  <!-- Page 1 content -->
</div>
<div class="page" data-page-id="1">
  <!-- Page 2 content -->
</div>
```

This makes it easy to process documents page-by-page or display them with proper pagination in your UI.

# Full Code Sample

Here's a complete example that extracts tracked changes and generates a legal review summary:

```python theme={null}
import os
import mimetypes
import requests
import time
import json

API_URL = "https://www.datalab.to/api/v1/track-changes"
DATALAB_API_KEY = os.getenv("DATALAB_API_KEY")
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")

# Not every Python install knows the DOCX MIME type; register it explicitly
mimetypes.add_type("application/vnd.openxmlformats-officedocument.wordprocessingml.document", ".docx")

def extract_tracked_changes(file_path, output_format='html', paginate=False):
    """Extract tracked changes from a DOCX, PDF, or image file."""
    mime_type, _ = mimetypes.guess_type(file_path)
    mime_type = mime_type or "application/octet-stream"

    with open(file_path, 'rb') as f:
        form_data = {
            'file': (os.path.basename(file_path), f, mime_type),
            'output_format': (None, output_format),
            'paginate': (None, paginate)
        }
        
        headers = {"X-API-Key": DATALAB_API_KEY}
        response = requests.post(API_URL, files=form_data, headers=headers)
        data = response.json()
    
    # Poll for completion
    check_url = data["request_check_url"]
    max_polls = 300
    
    for i in range(max_polls):
        time.sleep(2)
        response = requests.get(check_url, headers=headers)
        result = response.json()
        
        if result["status"] == "complete":
            return result
        elif result["status"] == "failed":
            raise Exception(f"Conversion failed: {result.get('error')}")
    
    raise TimeoutError("Conversion did not complete in time")


def analyze_with_llm(content, prompt_template):
    """Send content to LLM for analysis via OpenRouter."""
    response = requests.post(
        url="https://openrouter.ai/api/v1/chat/completions",
        headers={
            "Authorization": f"Bearer {OPENROUTER_API_KEY}",
            "Content-Type": "application/json"
        },
        json={
            "model": "anthropic/claude-3.5-sonnet",
            "messages": [
                {
                    "role": "user",
                    "content": prompt_template.format(content=content)
                }
            ]
        }
    )
    
    return response.json()['choices'][0]['message']['content']


def generate_legal_review(file_path):
    """
    Complete workflow: extract tracked changes and generate legal review.
    """
    print(f"Processing {file_path}...")
    
    # Extract tracked changes
    result = extract_tracked_changes(file_path, output_format='html', paginate=True)
    marked_up_doc = result['html']
    
    print("Document converted with tracked changes preserved.")
    
    # Generate comprehensive legal review
    review_prompt = """You are a legal reviewer analyzing a contract with tracked changes.

Please provide:

1. **Executive Summary**: Brief overview of the document and key changes
2. **Material Changes**: List substantive changes that affect rights, obligations, or liabilities
3. **Risk Assessment**: Identify any changes that increase risk exposure
4. **Comments Analysis**: Summarize unresolved comments and action items
5. **Recommendations**: Specific next steps for legal review

Document with tracked changes:
{content}"""

    print("\nGenerating legal review with LLM...")
    review = analyze_with_llm(marked_up_doc, review_prompt)
    
    # Also generate author-specific analysis (DOCX input only: PDF and image
    # output carries no author metadata)
    author_prompt = """Analyze this document's tracked changes by author.

For each author who made changes:
- Total number of insertions and deletions
- Types of changes (substantive vs. editorial)
- Key themes in their revisions
- Any patterns in their negotiation strategy

Document:
{content}"""
    
    print("Generating per-author analysis...")
    author_analysis = analyze_with_llm(marked_up_doc, author_prompt)
    
    return {
        'marked_up_document': marked_up_doc,
        'legal_review': review,
        'author_analysis': author_analysis
    }


if __name__ == "__main__":
    # Process a contract with tracked changes
    results = generate_legal_review('contract_redline_v3.docx')
    
    # Save results
    with open('legal_review.txt', 'w') as f:
        f.write("LEGAL REVIEW\n")
        f.write("="*80 + "\n\n")
        f.write(results['legal_review'])
        f.write("\n\n" + "="*80 + "\n\n")
        f.write("AUTHOR ANALYSIS\n")
        f.write("="*80 + "\n\n")
        f.write(results['author_analysis'])
    
    with open('marked_up_document.html', 'w') as f:
        f.write(results['marked_up_document'])
    
    print("\nReview complete! Results saved to:")
    print("  - legal_review.txt")
    print("  - marked_up_document.html")
```

# Pricing

Track Changes is billed as a conversion plus a \$6 per 1,000 pages add-on, for every input type. See [pricing](https://www.datalab.to/pricing) for current rates.

## Next Steps

<CardGroup cols={2}>
  <Card title="Document Conversion" icon="file-lines" href="/docs/recipes/conversion/conversion-api-overview">
    Explore the full conversion API and output format options.
  </Card>

  <Card title="Structured Extraction" icon="table" href="/docs/recipes/structured-extraction/api-overview">
    Extract structured data from documents using JSON schemas.
  </Card>

  <Card title="Pipelines" icon="workflow" href="/docs/recipes/pipelines/pipeline-overview">
    Chain processors into versioned, reusable pipelines.
  </Card>

  <Card title="SDK Conversion" icon="code" href="/docs/welcome/sdk/conversion">
    Use the Python SDK for simpler document conversion workflows.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.