Agent API
Website migration QA for AI agents
Give it a source and a target URL and it checks page existence, content accuracy, and visual regression — over REST or MCP, with no account or signup. A free daily allowance needs no key; past it, send a prepaid key a human tops up by card, use a Pro account’s key (5,000 captured pages a month included, shared with the web app), or pay from a wallet in USDC on Base.
Start with no key or credential: each client address gets 40 comparison pages a day (one 40-page job or several smaller ones), 5 page discoveries (at most 50 pages returned each) and 5 screenshots. Free comparison pages also draw on a daily pool shared across all callers, so they can run out before your own allowance does. A free call is an ordinary request sent without a credential or key; the request carries no flag. The response carries sample: true, pagesCharged: 0 and allowanceRemaining, the units left for that kind. A request sent after an allowance is used up is challenged with a 402 like a paid one, and the refusal body carries a nextSteps object naming the ways to pay (prepaid checkout and the human top-up page, the Pro account page, the wallet rail) and a feedback pointer. A request naming a documentation host (example.com, example.net, example.org) or difficat.com itself is a replayed example, not a task: unpaid it gets the 402 price challenge and runs nothing, and paid it is charged the fixed five pages the challenge named (capped by maxPages) even when the site has fewer. Or read the sample report the API produced over three pages, no call needed.
The web app has a free trial, and Pro ($29 a month) adds the commercial license and 5,000 captured pages a month, shared by the web app and this API; both run the same engine as this API. The machine API below is paid per call unless the free daily allowance or a Pro account’s allowance covers it.
MCP Setup
Point your agent at the MCP endpoint
A stateless Streamable HTTP endpoint at https://app.difficat.com/mcp. Tools: discover_pages, capture_screenshot, start_comparison, recover_comparison, get_comparison, wait_for_comparison, get_report, get_screenshot, cancel_comparison, get_balance, request_prepaid_key, send_feedback, list_snapshots, get_snapshot, compare_snapshot. The three snapshot tools read a saved snapshot dataset with a Pro account’s key or a share token (dcs_…) its owner issued, and compare a live site or another snapshot against it; reads are free. request_prepaid_key is free: it mints a prepaid key (dck_…) with its Stripe Checkout link and tells the client to send the key as the Authorization: Bearer or X-DiffiCat-Key header. The key is never a tool argument.
Claude Code
/plugin marketplace add difficat/difficat
/plugin install difficat@difficat
The plugin installs the MCP server with a skill that knows when to run a comparison and how to behave at the payment wall. Its optional key setting holds a prepaid or Pro key, kept in the system's secure store and debited on every job. The server alone, without the skill:
claude mcp add --transport http difficat https://app.difficat.com/mcp
Paying from a prepaid balance instead of a wallet? Add the key once: claude mcp add --transport http --header "Authorization: Bearer dck_…" difficat https://app.difficat.com/mcp. A gateway that cannot set Authorization may send the key as X-DiffiCat-Key instead; the Smithery listing’s prepaidKey setting does this for you.
Generic mcpServers config
{
"mcpServers": {
"difficat": { "type": "http", "url": "https://app.difficat.com/mcp" }
}
}
A raw HTTP client must send Accept: application/json, text/event-stream on every MCP request; MCP clients do this for you.
Shell & Pipeline
From a shell or a pipeline
The difficat command runs the same API from a terminal or a CI job. Nothing to install and no account; without a key the daily allowance of the calling address applies.
One command
npx difficat compare https://old.difficat.com https://new.difficat.com --max-pages 50
It starts the comparison, waits for it and prints a one-line verdict, the failed pages worst first with their failed checks and top issue, and the job id. --dry-run prints the quote and starts nothing, --report report.json saves the full report with the failed pages' screenshots beside it, --json prints the digest, --pairs pairs.json compares explicit page pairs. Exit codes: 0 every page passed, 1 a page failed (--fail-on failed|broken|none), 2 payment required, 3 error. difficat discover and difficat screenshot wrap the other two routes.
Set DIFFICAT_KEY to a prepaid or Pro key to go beyond the allowance; a key bills every run. The command never pays a wallet challenge itself: when the allowance is used up it prints the price and the ways to pay and exits 2. It never prints the jobToken or a report URL.
GitHub Actions
- uses: difficat/difficat@v1
with:
source: https://www.your-site.test
target: ${{ steps.preview.outputs.url }}
max-pages: 50
key: ${{ secrets.DIFFICAT_KEY }}
The step fails when a page fails, the job summary names the failed pages, and the full report with the failed pages' screenshots is the difficat-report artifact of the run. Inputs never reach a shell unquoted, page text is neutralised before it is printed, and a pull request from a fork without a key skips with a notice instead of failing. Preview URLs must be public: the service refuses private and loopback hosts. Source, inputs and MCP client configurations: github.com/difficat/difficat.
First Call
One request, a 402, then a paid retry
Every paid route answers an unauthenticated request with HTTP 402 and a payment challenge. Pay it and retry the same request to get the result.
Unpaid: see the challenge
curl -i https://app.difficat.com/api/v1/screenshots \
-X POST -H 'Content-Type: application/json' \
-d '{"url":"https://new.difficat.com"}'
# HTTP/2 402
# WWW-Authenticate: Payment ...
Paid: the same call through mppx
npx mppx@0.13 https://app.difficat.com/api/v1/screenshots \
-X POST -H 'Content-Type: application/json' \
-d '{"url":"https://new.difficat.com"}'
mppx handles the 402 automatically: it signs a USDC payment on Base and retries the request with the credential attached. See the OpenAPI document for every route, its request body, and a response example on every JSON operation, so you know the result shape before paying for a call.
A comparison job with a spend cap
npx mppx@0.13 https://app.difficat.com/api/v1/jobs \
-X POST -H 'Content-Type: application/json' \
-d '{"sourceUrl":"https://old.difficat.com","targetUrl":"https://new.difficat.com","maxPages":50,"maxPriceUsd":2}'
An auto-paying client pays whatever the challenge asks. Give every job request maxPages or maxPriceUsd (a job priced above maxPriceUsd is refused before any challenge; a job the free allowance covers costs nothing and is not refused by it), and set your client's own spend cap (maxAmount on the mppx payment method). A single page needs no sourceUrl: send a one-pair urls list instead.
{"urls":[{"source":"https://old.difficat.com/pricing.html","target":"https://new.difficat.com/pricing.html"}]}
To see the price before paying, add "dryRun":true to the same body (REST, or start_comparison over MCP). It runs the same discovery and pricing and answers {"dryRun":true,"pagesPriced":42,"amount":"0.84","currency":"USD"}, and without a credential or key also allowanceRemaining and freeCovers, true when the same body would run free: nothing is charged, no free allowance is used and no job starts. The same body sent within ten minutes without dryRun is priced from that result, with no second discovery.
Then hold GET /api/v1/jobs/{id}?wait=120 (or wait_for_comparison over MCP) until the job ends, or pass callbackUrl in the job body to receive one JSON POST when it does. The terminal status carries summary.issuesBySeverity, the verdict; /report?failedOnly=true returns only the pages that failed, and adding view=digest shrinks each page to its status, failed checks and issues.
Pricing
Paid per call, in USDC on Base
No account, no subscription, no minimum, once the free daily allowance is used. Status, report and screenshot reads are free.
Feedback: POST https://app.difficat.com/api/v1/feedback and the send_feedback MCP tool are free and read by a person. Body fields: topic (price, payment, missing_feature, error, docs or other), message, and optionally relatedTool, quotedAmount, pages, wouldPayAt and contact. It answers 202 { "received": true }.
| Call | Price | Notes |
|---|---|---|
POST /api/v1/discover |
$0.05 per call | Page discovery for a site |
POST /api/v1/screenshots |
$0.02 per call | Full-page screenshot of one URL |
POST /api/v1/jobs |
$0.02 per compared pair for the first 1,000 pairs, $0.01 per pair beyond | Charged for the pairs the job compares (two captured pages each, $0.01 per captured page): the urls pairs, else the pages discovery finds, at most maxPages when given (an optional ceiling, not a reservation). No pair limit; the 402 challenge names the count. A single page is a one-pair urls list, or maxPages: 1 with the page URLs as sourceUrl and targetUrl: priced as one pair, no discovery. The discovery that prices a job without urls is not charged; /discover is a separate, optional purchase |
GET /api/v1/jobs/{id}, /report, /files/{filename}, POST /api/v1/jobs/{id}/cancel, POST /api/v1/jobs/recover, GET /api/v1/jobs, GET /api/v1/balance, POST /api/v1/prepaid/checkout |
Free | Status, report and screenshot reads, cancelling a job, recovery of a paid job whose response was lost, a prepaid key’s own reads (its jobs and its balance), and the checkout that issues a key |
A 50-pair comparison job is $1.00 at today's price, and a 20,000-pair job is $210. A large job runs in the background, survives restarts and deploys, and its report is read in pages of up to 5,000 results. A body names at most 20 hosts, and three jobs may run at once per caller. Live prices are always in the OpenAPI document as x-payment-info on each paid operation.
A settled charge is final: cancelling, a failed job or partial results do not reduce it. If a paid job or screenshot response never arrives, POST /api/v1/jobs/recover with the same payment credential header (or recover_comparison over MCP with the same _meta credential) returns the jobId and a new jobToken; a capture or start that failed after the charge is recorded the same way with status failed. A /discover response is the page list itself and is not recoverable.
The API's threshold is a fraction from 0 to 1: 0.05 means 5%. The web app takes a percentage; the API does not.
Get USDC on Base
Fund your agent's wallet
Production accepts USDC on Base mainnet only. The cheapest way to try it is one screenshot call, $0.02.
- Get USDC on any exchange or wallet that supports Base.
- Withdraw or send it on the Base network — not Ethereum mainnet — to your agent's wallet address.
- Keep a few dollars there. A 50-pair comparison job is about $1.00 at today's price.
- Point your agent's MPP or x402 client at that wallet and make your first call above.
No wallet?
Pay from a prepaid balance
An agent without a wallet pays from a balance a human tops up once by card at app.difficat.com/prepaid — $5.00, $10.00 or $25.00. The agent sends its key as Authorization: Bearer dck_… on the same calls and is debited per call at the same prices. GET /api/v1/balance and GET /api/v1/jobs are free reads by key. Balances do not expire and are not refunded.
Get a key and a payment link for your human
curl https://app.difficat.com/api/v1/prepaid/checkout \
-X POST -H 'Content-Type: application/json' \
-d '{"amountUsd":10}'
# {"key":"dck_…","keyId":"…","checkoutUrl":"https://checkout.stripe.com/…","amountUsd":10,"checkoutExpiresAt":"…"}
The key is shown once and cannot be recovered. Hand checkoutUrl to the person paying; the balance is credited when the payment completes. To top up a key you already have, send it as Authorization: Bearer dck_… on the same call.
Spend it: a comparison job paid from the balance
curl https://app.difficat.com/api/v1/jobs \
-X POST -H 'Content-Type: application/json' \
-H 'Authorization: Bearer dck_…' \
-H 'Idempotency-Key: run-7' \
-d '{"urls":[{"source":"https://old.difficat.com/pricing.html","target":"https://new.difficat.com/pricing.html"}]}'
No 402: the exact priced amount is debited and the response carries payment.rail: "prepaid" with the remaining balance. A retry with the same Idempotency-Key within 24 hours is not debited again and answers 409 naming the job. A short balance answers 402 with the balance, the price and the top-up link; the key that paid a job reads it in place of the jobToken.
MCP with the key
claude mcp add --transport http --header "Authorization: Bearer dck_…" difficat https://app.difficat.com/mcp
The paid tools debit the key, get_balance reads the balance, and the read tools accept the key in place of jobToken for the jobs it paid. The key travels on the HTTP request only, never in a tool argument.
Reference
Machine-readable docs
- OpenAPI document — every route, request/response schema, a response example on every JSON operation, and live
x-payment-infopricing - MCP Registry entry —
com.difficat/difficatin the official MCP Registry - x402 Bazaar listing — the three paid REST routes in Coinbase’s x402 Bazaar
- llms.txt — a plain-text summary of DiffiCat and the agent API for AI assistants
Start with the free trial
The web app runs the same engine as the API — no card needed for the trial.
Open DiffiCat Web