---
title: "Start a comparison job. Free up to 40 pages a day per client address when the request carries no payment credential and no prepaid key: the response is the normal 201 with sample: true, pagesCharged: 0 and allowanceRemaining, and a job above the remaining allowance, or any request once it is used up, is challenged as usual with nextSteps in the 402 body. A sample report over three pages, produced by this API, is at https://difficat.com/agents/sample/report.json. Then 0.02 USD per compared pair (2 captured pages at 0.01 USD each) for the first 1000 pairs, 0.01 USD per pair beyond; a Pro account’s key draws 2 captured pages per pair from its monthly allowance instead. Compares Site A against Site B on page existence, content and visual regression. Visual captures cover the first 10,000 px of each page; a page cut there carries checks.visual.truncatedAt in the report. The job is priced on the pages it compares: the urls pairs, or the pages discovery finds, at most maxPages when given (an optional ceiling, not a reservation); there is no page limit, and the 402 challenge names the count and the exact amount. A single-page comparison is a urls list with one pair (preferred), or maxPages: 1 with the page URLs as sourceUrl and targetUrl; either is priced as one pair and runs no discovery. The discovery that prices a job without urls is not charged; POST /discover is a separate, optional purchase for callers who want the page list itself. Send dryRun: true to get the page count and exact amount without charging, using the free allowance or starting a job; without a credential or key the quote also carries allowanceRemaining and freeCovers, true when the same body would run free. Set maxPriceUsd to refuse a job priced above your budget before any challenge; a job the free allowance covers costs nothing and is not refused by it. A settled charge is final: cancelling, a failed job or partial results do not reduce it. POST /jobs/recover returns a job or screenshot whose create response was lost, given the same payment credential (a /discover response is not recoverable). A body names at most 20 distinct hosts, and a caller runs at most 3 jobs at once. Then GET /jobs/{id}?wait=<seconds> holds the response until the job ends (at most 300 s), or give callbackUrl to receive one JSON POST when it does; a terminal status read carries summary.issuesBySeverity, the verdict. Read /jobs/{id}/report?failedOnly=true first for the pages that failed, then the rest in pages."
method: POST
path: "/jobs"
---

# Start a comparison job. Free up to 40 pages a day per client address when the request carries no payment credential and no prepaid key: the response is the normal 201 with sample: true, pagesCharged: 0 and allowanceRemaining, and a job above the remaining allowance, or any request once it is used up, is challenged as usual with nextSteps in the 402 body. A sample report over three pages, produced by this API, is at https://difficat.com/agents/sample/report.json. Then 0.02 USD per compared pair (2 captured pages at 0.01 USD each) for the first 1000 pairs, 0.01 USD per pair beyond; a Pro account’s key draws 2 captured pages per pair from its monthly allowance instead. Compares Site A against Site B on page existence, content and visual regression. Visual captures cover the first 10,000 px of each page; a page cut there carries checks.visual.truncatedAt in the report. The job is priced on the pages it compares: the urls pairs, or the pages discovery finds, at most maxPages when given (an optional ceiling, not a reservation); there is no page limit, and the 402 challenge names the count and the exact amount. A single-page comparison is a urls list with one pair (preferred), or maxPages: 1 with the page URLs as sourceUrl and targetUrl; either is priced as one pair and runs no discovery. The discovery that prices a job without urls is not charged; POST /discover is a separate, optional purchase for callers who want the page list itself. Send dryRun: true to get the page count and exact amount without charging, using the free allowance or starting a job; without a credential or key the quote also carries allowanceRemaining and freeCovers, true when the same body would run free. Set maxPriceUsd to refuse a job priced above your budget before any challenge; a job the free allowance covers costs nothing and is not refused by it. A settled charge is final: cancelling, a failed job or partial results do not reduce it. POST /jobs/recover returns a job or screenshot whose create response was lost, given the same payment credential (a /discover response is not recoverable). A body names at most 20 distinct hosts, and a caller runs at most 3 jobs at once. Then GET /jobs/{id}?wait=<seconds> holds the response until the job ends (at most 300 s), or give callbackUrl to receive one JSON POST when it does; a terminal status read carries summary.issuesBySeverity, the verdict. Read /jobs/{id}/report?failedOnly=true first for the pages that failed, then the rest in pages.

`POST /jobs`

## Request

- Base URL: `https://app.difficat.com/api/v1`
- URL: `https://app.difficat.com/api/v1/jobs`
- Auth: one of:
  - HTTP payment
  - HTTP bearer

## Headers

- `Idempotency-Key` string

## Request body

- union
  - object
    - `sourceUrl` string, uri, required: Site A, the reference site (full URL). Required unless urls is given.
    - `targetUrl` string, uri
    - `sourceAuth` BasicAuth
      - `username` string, required: Username for HTTP basic auth.
      - `password` string, required: Password for HTTP basic auth.
    - `targetAuth` BasicAuth
      - `username` string, required: Username for HTTP basic auth.
      - `password` string, required: Password for HTTP basic auth.
    - `urls` object[]: Explicit page pairs to compare (at most 50000); skips discovery and is priced per pair. One pair is a single-page comparison and needs no sourceUrl.
      - `source` string, uri, required: Page URL on Site A.
      - `target` string, uri, required: The same page's URL on Site B.
    - `maxPages` integer: Optional ceiling on the pages compared and charged for. The job is priced on the pages it compares: the urls list, or the pages discovery finds, at most this many. There is no page limit. maxPages: 1 without sitemapUrl compares exactly the given sourceUrl and targetUrl, with no discovery.
    - `threshold` number: Visual diff tolerance as a fraction from 0 (exact) to 1: 0.05 means 5%. The web app takes a percentage; the API does not.
    - `sitemapUrl` string, uri
    - `viewportWidth` integer
    - `checkMode` 'full' | 'text-only' | 'existence-only': What each page is checked on: full (existence, content and visual, the default), text-only (existence and content, no screenshots) or existence-only (only that every page exists and answers, as for a site translated into another language; content and visual are reported as skipped with reason not-requested).
    - `maxPriceUsd` number: Optional spend cap in USD. A job priced above it is refused before any challenge, so an auto-paying client is never asked for more.
    - `dryRun` boolean: Quote only: runs every check and the pricing, then answers the page count and exact amount. Nothing is charged, the free allowance is not used and no job starts; the same body without dryRun is priced from the retained result.
    - `callbackUrl` string, uri: Optional https URL (port 443) that receives one JSON POST when the job ends (jobId, status, summary, links; never the jobToken), a nudge to read the job. Public hosts only, no redirects followed, retried twice; delivery never affects the job.
    - `visual` VisualRules: Capture rules applied to both sides.
      - `masks` string[]: CSS selectors of elements hidden on both sites before the screenshot (at most 50, each up to 200 characters). The layout keeps its place.
      - `freeze` object
        - `animations` boolean: Stop CSS animations and transitions before the screenshot.
        - `clock` boolean: Pause timers and the clock after load; a page that needs a timer to render may stay empty.
        - `clockSeconds` integer
  - object
    - `sourceUrl` string, uri: Site A, the reference site (full URL). Required unless urls is given.
    - `targetUrl` string, uri
    - `sourceAuth` BasicAuth
      - `username` string, required: Username for HTTP basic auth.
      - `password` string, required: Password for HTTP basic auth.
    - `targetAuth` BasicAuth
      - `username` string, required: Username for HTTP basic auth.
      - `password` string, required: Password for HTTP basic auth.
    - `urls` object[], required: Explicit page pairs to compare (at most 50000); skips discovery and is priced per pair. One pair is a single-page comparison and needs no sourceUrl.
      - `source` string, uri, required: Page URL on Site A.
      - `target` string, uri, required: The same page's URL on Site B.
    - `maxPages` integer: Optional ceiling on the pages compared and charged for. The job is priced on the pages it compares: the urls list, or the pages discovery finds, at most this many. There is no page limit. maxPages: 1 without sitemapUrl compares exactly the given sourceUrl and targetUrl, with no discovery.
    - `threshold` number: Visual diff tolerance as a fraction from 0 (exact) to 1: 0.05 means 5%. The web app takes a percentage; the API does not.
    - `sitemapUrl` string, uri
    - `viewportWidth` integer
    - `checkMode` 'full' | 'text-only' | 'existence-only': What each page is checked on: full (existence, content and visual, the default), text-only (existence and content, no screenshots) or existence-only (only that every page exists and answers, as for a site translated into another language; content and visual are reported as skipped with reason not-requested).
    - `maxPriceUsd` number: Optional spend cap in USD. A job priced above it is refused before any challenge, so an auto-paying client is never asked for more.
    - `dryRun` boolean: Quote only: runs every check and the pricing, then answers the page count and exact amount. Nothing is charged, the free allowance is not used and no job starts; the same body without dryRun is priced from the retained result.
    - `callbackUrl` string, uri: Optional https URL (port 443) that receives one JSON POST when the job ends (jobId, status, summary, links; never the jobToken), a nudge to read the job. Public hosts only, no redirects followed, retried twice; delivery never affects the job.
    - `visual` VisualRules: Capture rules applied to both sides.
      - `masks` string[]: CSS selectors of elements hidden on both sites before the screenshot (at most 50, each up to 200 characters). The layout keeps its place.
      - `freeze` object
        - `animations` boolean: Stop CSS animations and transitions before the screenshot.
        - `clock` boolean: Pause timers and the clock after load; a page that needs a timer to render may stay empty.
        - `clockSeconds` integer

## Response `200`

Dry run: the quote, with nothing charged and no job started

- DryRunResponse
  - `dryRun` true, required
  - `pagesPriced` integer, required: The page count the job would be charged for.
  - `amount` string, required: The exact amount the same body without dryRun is charged.
  - `currency` 'USD', required
  - `allowanceRemaining` integer: For a request with no credential and no prepaid key: the comparison pages the client address may still run free today, read without consuming any.
  - `freeCovers` boolean: For a request with no credential and no prepaid key: true when the same body sent now without dryRun would run free (the priced pages are within allowanceRemaining and the shared daily pool).

## Other responses

- `201`: OK
- `400`: Invalid input or blocked URL
- `401`: Unknown, expired or blocked prepaid key; nextSteps names the ways to pay
- `402`: Payment required: the free daily allowance for this kind of call is used up, or the request carried a credential. The WWW-Authenticate header carries the Machine Payments Protocol challenge; pay and retry the identical request with the credential in the Authorization header. The body is the one failure that is not `{ error }`: an RFC 9457 problem document (type, title, status, detail, challengeId) plus nextSteps, every way a human can pay. A Link header (rel="payment") points at the prepaid top-up page. A request carrying a prepaid key whose balance is short receives the application/json body instead, with no challenge.
- `409`: A prepaid request repeated its Idempotency-Key; jobId names the job already created. Without jobId the first request was charged but recorded no job (still running, or failed): list the key’s jobs with GET /jobs, or retry under a new Idempotency-Key
- `429`: The limit of 3 running jobs for this caller, or the hourly limit of unpaid job requests for this address, is reached (Retry-After)
- `500`: The work failed after the charge settled; the charge is final. A job or screenshot is still recorded under the credential (status failed), so POST /jobs/recover finds it
- `502`: Discovery for an unpriced job failed
- `503`: The balance could not be read or debited; nothing was charged
- `504`: Discovery for an unpriced job timed out

---

[API](https://skmtc.dev/difficat/apis/difficat-agent-api.md) · [All operations](https://skmtc.dev/difficat/apis/difficat-agent-api/llms.txt) · [OpenAPI document](https://skmtc.dev/difficat/apis/difficat-agent-api/revisions/49c291e77659?raw)
