Does Diffy store pages server-side?
No. The service is stateless. You keep the state object from a snapshot and send it back as previous_state on POST /diff.
Snapshot a URL into Markdown, blocks, or a tree, then diff against client-held state. Agents get only what changed.
This demo posts POST /snapshot with render=static and max_chars=2000. Only example.com and datpaq.com are allowed. Chromium is not used here.
Click run to snapshot the allowlisted URL with render=static.
Why Diffy API
Snapshot then diff
POST /snapshot returns Markdown, blocks, or a tree plus compact state. POST /diff returns only what changed.
Token-cheap Markdown
Readability strips chrome. max_chars truncates at block boundaries so agents stay inside a budget.
Semantic block diffs
Added, removed, and modified blocks with a change ratio. Pure reorders are not reported as changes.
SSRF on every hop
Private, loopback, and metadata destinations are blocked, including redirect hops.
Static first
render=auto tries a fast static fetch, then Chromium only when the page is script-heavy.
You hold the state
Nothing is stored server-side. Keep state and state_etag, then send previous_state on the next check.
/api/v1/diffy
Snapshot
Fetch a URL as markdown, blocks, or tree, plus compact client-held state.
Diff
Compare the live page against previous_state and return added, removed, and modified blocks.
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | yes | http or https URL, max 2048 chars, SSRF-guarded on every hop. |
| format | string | no | markdown, blocks, or tree. Default markdown. Snapshot only. |
| render | string | no | auto, static, or browser. Default auto. Demo uses static. |
| max_chars | integer | no | Content cap 1000 to 100000, default 20000. Truncates at block boundaries. |
| include_state | boolean | no | Include the diffable state object. Default true. Snapshot only. |
| previous_state | object | diff | State previously returned by Diffy (schema 1). Required on POST /diff. |
Error reference
Invalid body, or the destination failed SSRF policy including a blocked redirect hop.
Missing or invalid gateway API key. Send x-api-key or api_key with every request.
previous_state.schema is not supported. Re-snapshot to obtain the current schema.
The page could not be processed (unsupported type, too many redirects, size exceeded).
More than 30 requests in a 15-minute window.
Chromium pool is at capacity. Retry-After is 5 seconds.
Example error response
{
"success": false,
"code": "BLOCKED_URL",
"error": "Destination is not allowed.",
"correlationId": "req_error123"
}FAQ
No. The service is stateless. You keep the state object from a snapshot and send it back as previous_state on POST /diff.
render=auto tries a fast static fetch first and falls back to headless Chromium for script-heavy pages. You can force static or browser. The live demo uses static only.
If state_etag matches, nothing changed. Pure reorders of identical blocks are not reported as changes.
Set max_chars between 1,000 and 100,000 (default 20,000). Content truncates at block boundaries. Tree output also caps links, buttons, and forms.
Send your Datpaq API key as an x-api-key header or an api_key query parameter. Keys are created in the Datpaq dashboard and a free tier is included.
Snapshot once, store compact state on the client, and receive only what changed on the next check.