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. EachPOST /sourcesis 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/changeswithsince=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 -
qorsearch- 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) orcsv
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 theidreturned by/sources(e.g.abc123). An unknown id returns[]. -
type- Filter by change type:link(new links only) orchange(page content changes only) -
qorsearch- 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, orrss
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.markdowncontains the full content of the newly discovered URL. -
change- A page content change detected.descriptionis an AI-generated markdown summary of what changed;markdownis 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 includesmarkdownwith the full page content. -
type=change- Only page content changes. Each includesdescriptionwith 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 ofrunning,initialising,error(or an error variant likeerror_blocked). Newly-created sources spend a minute or two asinitialisingbefore they flip torunning. -
paused- boolean. Only returned byGET /sources/{id}. A source can bestatus: runningandpaused: trueat 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_changesreturns link items withmarkdown. -
new_content- The page content itself is the value; Changeflow flags edits, inserts and deletes.recent_changesreturns 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. unparseablesince) -
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:
- Email: [email protected]
- Webhook integration: See Webhooks documentation for push notifications
- Feeds: See Feeds documentation for RSS, JSON, and CSV feeds