The Changeflow API lets you programmatically manage your sources and retrieve change data. You can list sources, create new ones, update settings, and fetch recent changes - all using simple HTTP requests.

The API is available on all plans. You can find your API token at changeflow.com/account/api.

Authentication

All API requests require your API token. You can find your token on the Integrations page.

Header authentication (recommended)

Pass your token in the Authorization header, prefixed with Bearer :

Authorization: Bearer YOUR_TOKEN

This is the standard method for scripts, chatbots, MCP servers, and any programmatic use.

URL token authentication

For RSS readers, feed URLs, and other tools that can't set headers, you can include the token in the URL path:

https://changeflow.com/api/v3/{YOUR_TOKEN}/sources

Both methods work on all endpoints. Header auth is preferred because it keeps your token out of server logs and browser history.

Base URL

https://changeflow.com/api/v3

Rate limits

Two separate per-account hourly budgets:

  • Writes (POST, PUT, DELETE): 100 per hour. Each POST /sources is synchronous and kicks off a browser fetch through our scraping pipeline plus the initial AI page classification and baseline diff. The cap is there to keep that work paced — please don't burst-create tracks in a tight loop. For batches over ~50 sources, chunk in groups of 10–20 with a pause between chunks. For a few hundred or more, email us and we'll onboard the list on our side without touching your limit.
  • Reads (GET): 1,000 per hour. Plenty of headroom for polling /changes with since= every minute or two.

Exceeding either limit returns 429 Too Many Requests with a Retry-After: 3600 header. The error message tells you which bucket you tripped.

Endpoints

List sources

Retrieve all your monitored sources.

GET /api/v3/sources

Optional parameters:

  • tag - Filter by tag name
  • q or search - Search sources by title or URL
  • filter - Filter by status. One of: running, paused, error, initialising. Unknown values return the unfiltered list.
  • format - Response format: json (default) or csv

Response:

[
  {
    "id": "abc123",
    "url": "https://competitor.com/pricing",
    "title": "Competitor Pricing Page",
    "looking_for": "Track pricing changes and new plan announcements",
    "frequency": "Daily at 9am",
    "tags": ["competitors", "pricing"],
    "notification_addresses": ["[email protected]"],
    "last_checked": "2026-05-19T10:30:00Z",
    "next_check": "2026-05-20T09:00:00Z",
    "last_change": "2026-05-14T15:22:00Z",
    "status": "running",
    "created_at": "2026-04-01T09:00:00Z"
  }
]

Get source

Retrieve details for a single source, including its 25 most recent changes.

GET /api/v3/sources/{id}

Response:

{
  "id": "abc123",
  "url": "https://competitor.com/pricing",
  "title": "Competitor Pricing Page",
  "looking_for": "Track pricing changes and new plan announcements",
  "frequency": "Daily at 9am",
  "tags": ["competitors", "pricing"],
  "notification_addresses": ["[email protected]"],
  "last_checked": "2026-05-19T10:30:00Z",
  "next_check": "2026-05-20T09:00:00Z",
  "last_change": "2026-05-14T15:22:00Z",
  "status": "running",
  "paused": false,
  "created_at": "2026-04-01T09:00:00Z",
  "change_type": "new_content",
  "recent_changes": [
    {
      "type": "change",
      "title": "Pricing Update",
      "description": "Enterprise plan price increased from $99 to $149/month. The new pricing includes advanced analytics and AI-powered reporting.",
      "track_id": "abc123",
      "change_id": "v_456",
      "source_name": "Competitor Pricing Page",
      "source_url": "https://competitor.com/pricing",
      "timestamp": "2026-05-14T15:22:00Z"
    }
  ]
}

The recent_changes array contains link items (for sources with change_type: new_links) or version items (for everything else). See Response fields below.

List changes

Retrieve recent changes across all your sources.

GET /api/v3/changes

Optional parameters:

  • limit - Maximum number of results (default: 100)
  • tag - Filter by tag name
  • source_id - Filter to a single source. Pass the id returned by /sources (e.g. abc123). An unknown id returns [].
  • type - Filter by change type: link (new links only) or change (page content changes only)
  • q or search - Search across change titles, descriptions, source names, URLs, and content
  • since - ISO8601 timestamp; only return changes after this time (e.g. 2026-01-15T00:00:00Z). Returns 400 if the value can't be parsed.
  • format - Response format: json (default), csv, or rss

Response:

[
  {
    "type": "link",
    "title": "New Product Launch Announced",
    "description": "Competitor announces new enterprise product line",
    "thumbnail": "https://cdn.changeflow.com/images/thumb123.jpg",
    "diff_image": "https://cdn.changeflow.com/diffs/diff123.png",
    "diff_url": "https://changeflow.com/tracks/abc123/versions/789",
    "url": "https://competitor.com/blog/new-product-launch",
    "track_id": "abc123",
    "version_id": 789,
    "change_id": "l_456",
    "source_name": "Competitor Blog",
    "source_url": "https://competitor.com/blog",
    "timestamp": "2026-05-19T14:30:00Z",
    "markdown": "# New Product Launch\n\nFull article content in markdown...",
    "changes": [
      {
        "description": "New blog post: Product Launch Announced",
        "importance": 9,
        "confidence": 9,
        "change_type": "inserts",
        "link": "https://competitor.com/blog/new-product-launch"
      }
    ]
  },
  {
    "type": "change",
    "title": "Pricing Page Updated",
    "description": "Enterprise plan price increased from $99 to $149/month. The new pricing includes advanced analytics and AI-powered reporting features.",
    "thumbnail": "https://cdn.changeflow.com/screenshots/screen456.png",
    "diff_image": "https://cdn.changeflow.com/diffs/diff456.png",
    "diff_url": "https://changeflow.com/tracks/def456/versions/123",
    "url": "https://changeflow.com/tracks/def456/versions/123",
    "track_id": "def456",
    "version_id": 123,
    "change_id": "v_123",
    "source_name": "Competitor Pricing",
    "source_url": "https://competitor.com/pricing",
    "timestamp": "2026-05-19T12:00:00Z",
    "markdown": "# Pricing\n\n## Enterprise\n\n$149/month — advanced analytics, AI-powered reporting...",
    "changes": [
      {
        "description": "Enterprise plan price changed from $99 to $149/month",
        "importance": 9,
        "confidence": 8,
        "change_type": "edits"
      },
      {
        "description": "New AI-powered reporting feature added",
        "importance": 8,
        "confidence": 9,
        "change_type": "inserts"
      }
    ]
  }
]

Change types:

  • link - A new link discovered on a monitored page. markdown contains the full content of the newly discovered URL.
  • change - A page content change detected. description is an AI-generated markdown summary of what changed; markdown is the full content of the monitored page after the change.

Both types ship markdown with the cleaned full-page content. Both also include a changes array when available, containing the individual AI-filtered changes with description, importance (1-10), confidence (1-10), and change_type (inserts/deletes/edits). Only changes the AI deemed relevant are included.

This endpoint returns the same payload Changeflow sends to your webhook URL, so you can use it to backfill or replay deliveries. See Webhooks.

Filtering changes

The list-changes endpoint supports several filters that can be combined:

Keyword search (q or search) searches across:

  • Change titles and descriptions
  • Version summaries
  • Source names and URLs
  • Link titles, descriptions, and URLs

So searching for pricing finds changes where "pricing" appears in the change summary, the source name, or the linked page description - not just the source URL.

Type filter (type) returns one kind of change:

  • type=link - Only new links discovered on monitored pages. Each includes markdown with the full page content.
  • type=change - Only page content changes. Each includes description with an AI-generated summary.

Source filter (source_id) scopes the response to a single source. Pass the source id from /sources.

Since filter (since) returns only changes detected after the given timestamp. Use ISO8601 format (e.g. 2026-01-15T00:00:00Z). Useful for polling - store the timestamp of your last request and only fetch new changes. Invalid values return HTTP 400.

Combine filters for targeted queries:

GET /api/v3/changes?type=link&tag=competitors&q=pricing&since=2026-01-15T00:00:00Z&limit=20

Response fields

Fields on all changes:

Field Type Description
type string "link" for new links, "change" for page changes
title string Short title of the change
description string AI-generated summary of what changed (markdown)
thumbnail string Thumbnail image URL
diff_image string Screenshot of the change
diff_url string URL to view the change in Changeflow
url string For links: the discovered URL. For changes: the diff viewer URL
track_id string Source ID (use with GET /sources/{id})
version_id integer Internal version ID
change_id string Change ID (use with GET /changes/{id}). Format: v_123 for changes, l_456 for links
source_name string Display name for the source. For link changes this falls back to the linked page's site name (Open Graph metadata) when present, otherwise the monitored source's title.
source_url string URL of the monitored source
timestamp string ISO8601 timestamp of when the change was detected
changes array AI-filtered changeset (when available). Each item has description, importance, confidence, and change_type
markdown string Cleaned full-page content as markdown. For link changes this is the newly discovered URL's content. For change changes this is the monitored page's content after the diff.

Fields in the changes array:

Field Type Description
description string What this specific change is
importance integer 1-10 relevance score based on your source's monitoring prompt
confidence integer 1-10 confidence that this is a real, meaningful change
change_type string "inserts" (new content), "deletes" (removed content), or "edits" (modified content)
link string For link-type changes: the URL of the discovered link

Get change

Retrieve a single change by its ID with full content.

GET /api/v3/changes/{change_id}

The change_id is returned in list responses (e.g. v_456 for content changes, l_789 for link changes).

Response (link change):

{
  "type": "link",
  "title": "New Product Launch Announced",
  "description": "Competitor announces new enterprise product line",
  "url": "https://competitor.com/blog/new-product-launch",
  "track_id": "abc123",
  "change_id": "l_456",
  "source_name": "Competitor Blog",
  "source_url": "https://competitor.com/blog",
  "timestamp": "2026-05-19T14:30:00Z",
  "markdown": "# New Product Launch\n\nFull article content in markdown...",
  "changes": [
    {
      "description": "New blog post: Product Launch Announced",
      "importance": 9,
      "confidence": 9,
      "change_type": "inserts",
      "link": "https://competitor.com/blog/new-product-launch"
    }
  ]
}

Response (version change):

{
  "type": "change",
  "title": "Pricing Page Updated",
  "description": "Enterprise plan price increased from $99 to $149/month. The new pricing includes advanced analytics and AI-powered reporting features.",
  "url": "https://changeflow.com/tracks/def456/versions/123",
  "track_id": "def456",
  "change_id": "v_123",
  "source_name": "Competitor Pricing",
  "source_url": "https://competitor.com/pricing",
  "timestamp": "2026-05-19T12:00:00Z",
  "markdown": "# Pricing\n\n## Enterprise\n\n$149/month — advanced analytics, AI-powered reporting...",
  "changes": [
    {
      "description": "Enterprise plan price changed from $99 to $149/month",
      "importance": 9,
      "confidence": 8,
      "change_type": "edits"
    }
  ]
}

PDF tracks are supported: markdown is derived from the extracted page text, so output reads as a continuous flow rather than richly-structured headings.

Create source

Create a new monitored source.

POST /api/v3/sources
Content-Type: application/json

Required fields:

  • url - The URL to monitor
  • looking_for - What you want to track (the monitoring prompt)
  • frequency - How often to check. See Frequency below.

Optional fields:

  • title - Custom name for the source (auto-generated from the page title if not provided)
  • tags - Array of tag names, or a comma-separated string
  • notification_addresses - Array of email addresses to notify (or comma-separated string). Addresses that aren't already on your account are auto-created and confirmed.

Request:

{
  "url": "https://competitor.com/pricing",
  "looking_for": "Track any pricing changes, new plans, or promotional offers",
  "frequency": "Daily at 9am",
  "title": "Competitor Pricing",
  "tags": ["competitors", "pricing"],
  "notification_addresses": ["[email protected]"]
}

Response:

{
  "status": "success",
  "id": "abc123"
}

Update source

Update an existing source's settings.

PUT /api/v3/sources/{id}
Content-Type: application/json

Fields (all optional):

  • title - Update the source name
  • looking_for - Update the monitoring prompt
  • frequency - Update check frequency. See Frequency below.
  • tags - Replace all tags (array or comma-separated string)
  • notification_addresses - Replace all notification addresses (array or comma-separated string). Unknown addresses are auto-created. Pass [] to clear.

Request:

{
  "frequency": "Hourly",
  "looking_for": "Track pricing changes, especially enterprise tier",
  "tags": ["competitors", "pricing", "high-priority"]
}

Response:

{
  "status": "success",
  "id": "abc123"
}

Delete source

Permanently delete a source and all its history.

DELETE /api/v3/sources/{id}

Response:

{
  "status": "success",
  "id": "abc123"
}

Pause / Resume / Check source

Control source monitoring status.

Pause a source:

PUT /api/v3/sources/{id}/pause

Resume a source:

PUT /api/v3/sources/{id}/resume

Trigger an immediate check:

PUT /api/v3/sources/{id}/check

Response:

{
  "status": "success",
  "id": "abc123"
}

Frequency

The frequency field is a natural-language description of how often Changeflow should check the source. We parse it into a schedule at setup time. The minimum effective cadence is once per hour - anything more frequent will still only fire hourly.

Examples that work well:

  • "Hourly"
  • "Daily" or "Daily at 9am" or "Daily at 6pm UTC"
  • "Weekly" or "Weekly on Monday at 9am"
  • "Every 2 hours", "Every 3 hours", "Every 6 hours"
  • "9-5 weekdays only"

If you need to verify how a string was interpreted, call GET /sources/{id} after creation and check next_check.

Source status fields

GET /sources and GET /sources/{id} return two independent fields that together describe a source's state:

  • status - one of running, initialising, error (or an error variant like error_blocked). Newly-created sources spend a minute or two as initialising before they flip to running.
  • paused - boolean. Only returned by GET /sources/{id}. A source can be status: running and paused: true at the same time - that means it's set up and healthy, but Changeflow isn't currently checking it.

change_type on GET /sources/{id} describes how Changeflow interprets the page:

  • new_links - The page is a directory/feed of links and Changeflow surfaces newly discovered links. recent_changes returns link items with markdown.
  • new_content - The page content itself is the value; Changeflow flags edits, inserts and deletes. recent_changes returns version items.
  • all_changes - Treat every diff as a change, including small ones. Returns version items.
  • legacy - Pre-AI tracks created before change-type classification.

Error responses

When an error occurs, the API returns a JSON response with an error message:

{
  "status": "error",
  "error": "url is required"
}

Common HTTP status codes:

  • 200 - Success
  • 400 - Bad request (missing or invalid parameters, e.g. unparseable since)
  • 403 - Forbidden (invalid or missing API token)
  • 404 - Not found (source or change doesn't exist)
  • 429 - Too many requests (rate limit exceeded)

Code examples

Python

import requests

TOKEN = "your_api_token"
BASE = "https://changeflow.com/api/v3"
HEADERS = {"Authorization": f"Bearer {TOKEN}"}

# List all sources
sources = requests.get(f"{BASE}/sources", headers=HEADERS).json()

# Create a new source
new_source = {
    "url": "https://example.com/pricing",
    "looking_for": "Track pricing changes",
    "frequency": "Daily at 9am"
}
result = requests.post(f"{BASE}/sources", json=new_source, headers=HEADERS).json()
print(f"Created source: {result['id']}")

# Get recent changes (links only, with keyword search)
changes = requests.get(f"{BASE}/changes", params={
    "type": "link",
    "q": "pricing",
    "limit": 50
}, headers=HEADERS).json()
for change in changes:
    print(change["title"])
    if change.get("markdown"):
        print(f"  Content: {change['markdown'][:200]}...")

# Poll for new changes since last check
changes = requests.get(f"{BASE}/changes", params={
    "since": "2026-01-15T00:00:00Z"
}, headers=HEADERS).json()

# Get a single change with full content
change = requests.get(f"{BASE}/changes/l_456", headers=HEADERS).json()
print(change["markdown"])

JavaScript (Node.js)

const TOKEN = 'your_api_token';
const BASE = 'https://changeflow.com/api/v3';
const headers = { 'Authorization': `Bearer ${TOKEN}` };

// List all sources
const sources = await fetch(`${BASE}/sources`, { headers }).then(r => r.json());

// Get only new links (great for AI processing)
const links = await fetch(`${BASE}/changes?type=link&limit=20`, { headers })
  .then(r => r.json());
links.forEach(link => {
  console.log(`${link.title}: ${link.url}`);
  console.log(`Markdown: ${link.markdown?.substring(0, 200)}...`);
});

// Poll for new changes since last check
const since = new Date(Date.now() - 3600000).toISOString(); // last hour
const newChanges = await fetch(`${BASE}/changes?since=${since}`, { headers })
  .then(r => r.json());

// Get full details for a specific change
const change = await fetch(`${BASE}/changes/v_123`, { headers }).then(r => r.json());
console.log(change.description);

cURL

TOKEN="your_api_token"

# List sources
curl -H "Authorization: Bearer $TOKEN" \
  "https://changeflow.com/api/v3/sources"

# Get only link changes with keyword search
curl -H "Authorization: Bearer $TOKEN" \
  "https://changeflow.com/api/v3/changes?type=link&q=pricing"

# Get changes since a timestamp
curl -H "Authorization: Bearer $TOKEN" \
  "https://changeflow.com/api/v3/changes?since=2026-01-15T00:00:00Z"

# Get a single change
curl -H "Authorization: Bearer $TOKEN" \
  "https://changeflow.com/api/v3/changes/l_456"

# Get changes as RSS (URL token - for feed readers)
curl "https://changeflow.com/api/v3/$TOKEN/changes?format=rss"

Getting help

If you need assistance with the API: