# domscout API > Version 1.1.0. Generated from `api/openapi.json`, which is the canonical > wire contract. Do not edit this file by hand. domscout is a browser API: screenshots, PDFs, clean Markdown extraction, structured data extraction, DOM/accessibility snapshots, page automation, durable jobs, batches, and bounded crawls — over HTTP, with no browser to run. - **Base URL:** `https://api.domscout.io` (current production gateway) - **Authentication:** `x-api-key: YOUR_API_KEY` on every request - **OpenAPI:** `https://www.domscout.io/openapi.json` - **Dashboard and keys:** `https://www.domscout.io/dashboard/api-keys` ## READ THIS FIRST: who may run a browser capture Browser work runs only inside the isolated renderer, and whether YOUR account may use it depends on two independent gates. Neither is a plan limit and neither is cleared by upgrading. - **Renderer isolation** — `ISOLATED` (assessed 2026-08-08). Captures run in a separate Lambda with no database credentials and a TCP-only VPC egress boundary. While this says anything else, every capture is refused with **HTTP 403** `code: "BROWSER_ISOLATION_REQUIRED"`. - **Arbitrary public capture** — off unless the deployment sets `PUBLIC_CAPTURE_ENABLED=true`. While it is off, capturing an arbitrary public URL is refused with **HTTP 403**; accounts with the internal entitlement are unaffected. UDP egress containment is `CONTAINED` (measured 2026-07-30), which is a precondition for that switch to be honoured at all. A 403 from either gate is a platform state, not your mistake and not something a retry fixes. It carries no `upgrade_url` precisely because no plan resolves it. Endpoints that never start a browser are unaffected by both: `POST /map`, `POST /extract`, `POST /search`, `POST /index`, `POST /semantic-search`, `GET /credits`, `POST /feedback`, job status and cancellation, and webhook replay. ## Quickstart With a key whose plan and account entitlements allow the requested feature, a request looks like this: ```bash curl -X POST https://api.domscout.io/screenshot \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com","format":"png"}' ``` Check your balance first — this one always works, and costs nothing: ```bash curl https://api.domscout.io/credits -H "x-api-key: YOUR_API_KEY" ``` Durable submission POSTs (`/screenshot`, `/batch`, `/crawl`, and webhook replay) accept an optional `Idempotency-Key` header (1–255 chars). Replaying the same key with the same body returns the original result; with a different body it returns 409 `IDEMPOTENCY_CONFLICT`. The synchronous provider-backed routes do not promise replay deduplication. ## What a request costs Requests are weighted by what they make the browser do. A viewport PNG costs **1 credit**; options that force a second render, an extra pass, or customer JavaScript add to it. No single request can cost more than **10 credits**, whatever it combines — so you can always bound your worst case before sending. | Request feature | Credits | | --- | --- | | base capture (png/jpeg/webp, viewport), or `probe: true` | 1 | | `fullPage` or `lazyScroll` | +1 | | `format: "pdf"` | +1 | | `captureMode: "skeleton"` (two renders) | +1 | | `captureMode: "grid"` (three renders) | +2 | | `semanticSnapshot` | +1 | | `diagnostics` | +1 | | `extract` | +1 | | `cleanup` | +1 | | `injectJS` | +2 | | `captureTimeline` | +2 | | more than 10 `actions` | +1 | | `POST /map` | +1 | | `POST /extract` | +3 | | `POST /search` | +1 | | `POST /index` | +2 | | `POST /semantic-search` | +1 | | **maximum for any single request** | **10** | `extractMarkdown`, `semanticNodes`, `simplifyDom` and `render` add nothing — they are a single DOM pass or a pre-navigation setting. A crawl costs the per-page capture cost for each page it fetches. A batch costs each item priced individually. Submitting either is free; the children are what is charged. ### These requests are always free They are rate limited but never consume credits or quota: - every `GET` (job status, job metadata, batch/crawl status, `GET /credits`) - every `DELETE` (cancellation) - `POST /batch` and `POST /crawl` submission (the children pay) - `POST /webhooks/deliveries/{deliveryId}/replay` - **`POST /feedback`** — telling us something is never sold The provider-backed discovery, extraction, search, and semantic-index routes are synchronous and billed by their route surcharge. They do not provide the durable idempotency guarantee used by batch, crawl, and capture submission. ### What pays for a request In this order, and a single request may straddle two of them: 1. **Included quota** — the allowance your plan includes each calendar month. 2. **Prepaid credits** — your purchased balance. Always spent before overage, so holding a balance can never produce a surprise invoice. 3. **Metered overage** — postpaid, on paid plans only, capped at 2× the included quota. Hobby has none and hard-stops instead. Every response carries `X-Domscout-Credits-Cost`, `X-Domscout-Credits-Remaining`, `X-Domscout-Quota-Remaining` and `X-RateLimit-Limit`. JSON capture responses also carry `metadata.credits`. A 429 adds `Retry-After`. ### Buying credits Any amount from $3 to $2000, at a rate that improves with size: | Spend | Credits | Per credit | | --- | --- | --- | | $3 | 1,000 | $0.003 | | $12 | 5,000 | $0.0024 | | $50 | 25,000 | $0.002 | | $175 | 100,000 | $0.00175 | Custom amounts use the same curve — $10 buys 3,333 credits. Credits **never** **expire**, and they **survive a cancelled subscription**: the balance is yours, and after a plan ends it is simply spent at the free tier's limits. ### What credits do not buy Credits buy captures. Plans buy capabilities. A credit balance never changes your rate limit, retention, monitor allowance, or feature access. A Hobby account holding 50,000 credits still gets 1 request/second, 7-day retention, and a 403 on `pdf`, `fullPage` and `async`. ## Plans and limits | Plan | Monthly | Yearly | Included/month | Rate | Retention | Monitors | | --- | --- | --- | --- | --- | --- | --- | | Hobby | $0 | $0 | 100 | 1/s | 7 days | 0 | | Pro | $9 | $90 | 5,000 | 5/s | 30 days | 10 | | Business | $29 | $290 | 25,000 | 20/s | 90 days | 100 | | Enterprise | $99 | $990 | 100,000 | 100/s | 365 days | 500 | **An annual plan does not change the quota.** It buys the same monthly allowance twelve times over, not twelve months of allowance to spend at once. The quota resets on the first of each calendar month (UTC) on every interval. ### Feature gates | Feature | Minimum plan | | --- | --- | | PNG / JPEG / WebP viewport capture, `extractMarkdown`, `debugOverlay` | Hobby | | `format: "pdf"`, `fullPage`, `async`, `semanticSnapshot`, `diagnostics`, `render`, `cleanup`, `extract`, extended actions, `/map`, `/extract`, `/search`, `/index`, `/semantic-search` | Pro | | `injectJS`, `captureTimeline`, `waitForFunction`, `POST /batch`, `POST /crawl` | Business | | 365-day retention | Enterprise | A refused feature returns **403** with `code: "PLAN_FEATURE_UNAVAILABLE"` and an `upgrade_url`. Some capabilities additionally require a per-account entitlement that a plan alone does not grant; those return `code: "FEATURE_NOT_ENABLED"`. ## Endpoints ## Capture Screenshot, PDF, DOM-inspection, automation, and extraction requests. ### POST /screenshot Capture a page, inspect its DOM, and optionally automate it Browser execution is CONDITIONAL, not held outright. Production captures run only inside the isolated renderer, which was deployed and verified on 2026-07-30 (api/browser-isolation.js records ISOLATED; api/udp-containment.js records CONTAINED). Two gates decide whether a given request may start a browser, and neither is a plan gate: renderer isolation must be proven, and capturing an ARBITRARY PUBLIC URL additionally requires the deployment switch PUBLIC_CAPTURE_ENABLED=true — accounts holding the internal entitlement are unaffected by the second. A request refused by either gate returns 403 and carries no upgrade_url: code BROWSER_ISOLATION_REQUIRED when isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled when the public-capture switch is off. Returns a direct result when it fits within the synchronous response budget; explicit async requests and rich results that exceed it return 202 with a durable job ID instead. Set responseType to json whenever semantic analysis, diagnostics, extraction, cleanup reporting, or action results are needed. A binary response is raw image/PDF bytes; the deployment template configures API Gateway binary handling independently of the ordering of values in Accept. **Path and header parameters** | Name | In | Required | Type | Notes | | --- | --- | --- | --- | --- | | `Idempotency-Key` | header | no | string | Optional caller-generated key for POST submission. Reusing a key with an identical body for the same owner returns the original job; reusing it with a different body returns 409. | **Request body** | Parameter | Type | Required | Constraints | Notes | | --- | --- | --- | --- | --- | | `url` | string | no | length 0–4096, format `uri` | Public HTTP(S) page URL. Unsafe addresses and redirects are rejected. Credentials in the authority (https://user:pass@host/) are refused with 400 — send authentication in the headers field instead, so it is never written to a capture log. | | `html` | string | no | length 0–1000000 | Raw HTML to render instead of navigating to a URL. | | `format` | "png" \| "jpeg" \| "webp" \| "pdf" | no | default `"png"` | | | `responseType` | "json" \| "binary" | no | default `"json"` | How the capture comes back. "json" (default) returns a JSON envelope with base64 image data plus metadata and any analysis you requested. "binary" returns the raw file bytes with a matching Content-Type, and therefore has nowhere to carry metadata — so a binary request combined with an analysis option (cleanup, semanticSnapshot, diagnostics, or extract) is AUTO-PROMOTED to a durable job: it answers 202 with a jobId and metadata.autoPromotedAsync, and you poll GET /job/{jobId} for the result. A capture that fails after promotion reports its failure on the job rather than as a synchronous 4xx, carrying the same error code it would have returned inline. Binary responses also cannot carry metadata.ignoredFields. | | `async` | boolean | no | default `false` | Force durable asynchronous execution. The service also auto-promotes captures expected to be large (rich JSON output) or slow (a URL capture stacking two or more whole-DOM passes, or one combined with fullPage/lazyScroll). Slow-capture promotion applies to Pro and above; Hobby keeps inline results. | | `probe` | boolean | no | default `false` | Load and measure the page without a screenshot. | | `width` | integer | no | default `1280`, range 100–3840 | | | `height` | integer | no | default `800`, range 100–2160 | | | `isMobile` | boolean | no | default `false` | | | `hasTouch` | boolean | no | default `false` | | | `deviceScaleFactor` | number | no | default `2`, range 1–3 | | | `quality` | integer | no | default `80`, range 1–100 | | | `fullPage` | boolean | no | default `false` | Capture the whole scrollable page rather than the viewport. Adds a credit surcharge. The rendered page may be at most 12000px tall; beyond that the request is refused with 400 and code CAPTURE_TOO_TALL rather than a truncated image. The same ceiling applies to captureSelector. | | `selector` | string | no | length 0–2000 | Legacy alias for captureSelector. | | `captureSelector` | string | no | length 0–2000 | The matched element must be visible within 5000ms, or the request is refused with 400 and code CAPTURE_SELECTOR_NOT_FOUND. It is also subject to the same 12000px ceiling as fullPage (400, CAPTURE_TOO_TALL). | | `hideSelectors` | array of string | no | items 0–50 | | | `captureMode` | "default" \| "grid" \| "skeleton" | no | default `"default"` | default uses the requested viewport. grid produces responsive desktop, tablet, and mobile frames without reloading raw inline HTML. skeleton preserves a DOMContentLoaded frame before waiting for bounded hydration, so an inline HTML skeleton is still useful even if later network-idle settling times out. grid and skeleton also honour extractMarkdown; the Markdown is taken from the DOM after the mode has finished capturing, so for grid that is the final (desktop) viewport. | | `waitForSelector` | string | no | length 0–2000 | A wait that expires is a client-side outcome, not a server fault: the request is refused with 400 and code WAIT_FOR_SELECTOR_TIMEOUT. | | `delay` | integer | no | default `0`, range 0–5000 | | | `headers` | object | no | | Scoped only to requests to the target origin. Do not expect sensitive header values to appear in diagnostics or persisted artifacts. | | `cookies` | array of object | no | items 0–50 | | | `cookies[].selector` | string | no | length 0–2000 | | | `cookies[].name` | string | yes | length 0–256 | | | `cookies[].value` | string | yes | length 0–4096 | | | `cookies[].domain` | string | no | length 0–255 | | | `cookies[].path` | string | no | length 0–1024 | | | `cookies[].httpOnly` | boolean | no | | | | `cookies[].secure` | boolean | no | | | | `cookies[].sameSite` | "Strict" \| "Lax" \| "None" | no | | | | `injectCSS` | string | no | length 0–100000 | | | `injectJS` | string | no | length 0–50000 | Trusted-JavaScript feature. It requires an eligible plan, an operator-controlled account entitlement, and the platform-wide emergency switch. | | `aiMode` | boolean | no | default `false` | | | `debugOverlay` | boolean | no | default `false` | Render console errors, page exceptions, failed requests and oversized assets into an overlay on the captured image. At most 200 entries are collected and each is truncated to 2048 characters; past that the overlay ends with a [TRUNCATED] marker. For structured, machine-readable output use `diagnostics` instead. | | `performanceOverlay` | boolean | no | default `false` | | | `forceState` | array of object | no | | | | `forceState[].selector` | string | yes | length 0–2000 | A CSS selector. If it matches several elements the state is applied to the first, and the result says so. | | `forceState[].state` | "hover" \| "focus" | yes | | | | `actions` | array of object | no | items 0–25 | | | `actions[].action` | "click" \| "type" \| "scroll" \| "wait" \| "navigate" \| "back" \| "forward" \| "reload" \| "select" \| "check" \| "uncheck" \| "hover" \| "focus" \| "pressKey" \| "scrollIntoView" \| "waitForText" \| "waitForUrl" \| "waitForFunction" \| "waitForRequest" \| "waitForResponse" \| "waitForNetworkIdle" \| "openTab" \| "switchTab" \| "closeTab" \| "assert" \| "upload" | yes | | | | `actions[].target` | object | no | | A selector-based action target. Semantic snapshot node IDs are inspection evidence only and are intentionally not accepted as durable action targets. | | `actions[].selector` | string | no | length 0–2000 | Legacy CSS target. Do not combine with target. | | `actions[].frame` | object | no | | A same-origin iframe target. Cross-origin frames are reported as metadata only. | | `actions[].frame.name` | string | no | length 0–512 | | | `actions[].frame.urlIncludes` | string | no | length 0–4096 | | | `actions[].tabId` | string | no | length 0–128 | openTab: the id for the new tab, refused with 400 VALIDATION_FAILED if a tab with that id is open; omitted, a free tab-N id is generated. switchTab and closeTab: the tab to act on. closeTab refuses to close the last open tab, whatever its id (400 TAB_CLOSE_FORBIDDEN). | | `actions[].url` | string | no | length 0–4096 | | | `actions[].text` | string | no | length 0–2000 | | | `actions[].exact` | boolean | no | default `true` | | | `actions[].key` | string | no | length 0–128 | Puppeteer key name, for example Enter, Tab, or Control+A. | | `actions[].amount` | number | no | default `500`, range -5000–5000 | | | `actions[].time` | integer | no | default `1000`, range 0–5000 | | | `actions[].value` | string \| array of string | no | | | | `actions[].expression` | string | no | length 0–4096 | Trusted-JavaScript only; waitForFunction requires the same plan, account entitlement, and platform-wide emergency switch as injectJS. | | `actions[].files` | array of object | no | items 1–20 | | | `actions[].expected` | string | no | length 0–2048 | Assertion expression: exists, visible, checked, unchecked, disabled, enabled, text:VALUE, or equals:VALUE. | | `actions[].continueOnError` | boolean | no | default `false` | | | `typeSpeed` | integer | no | default `40`, range 0–40 | | | `fastType` | boolean | no | default `false` | | | `captureTimeline` | boolean | no | default `false` | | | `extractMarkdown` | boolean | no | default `false` | Extract agent-readable Markdown. Empty main content falls back to the document body and then the accessibility tree when available. | | `semanticNodes` | boolean | no | default `false` | Legacy interactive-node summary. Prefer semanticSnapshot for rich DOM data. | | `simplifyDom` | boolean | no | default `false` | | | `lazyScroll` | boolean | no | default `false` | | | `pdfFormat` | string | no | default `"A4"`, length 0–32 | Paper size for a PDF capture. Matched case-insensitively against LETTER, LEGAL, TABLOID, LEDGER, A0, A1, A2, A3, A4, A5 and A6; anything else is refused with 400 and code PDF_FORMAT_UNSUPPORTED, which names the accepted values. An empty or omitted value takes the A4 default. | | `landscape` | boolean | no | default `false` | | | `printBackground` | boolean | no | default `true` | | | `save` | boolean | no | default `false` | Persist capture artifacts using the configured finite, plan-bounded retention policy. JSON responses include metadata.captureId, metadata.retentionDays, and metadata.captureExpiresAt. A synchronous binary response exposes X-Domscout-Capture-Id and X-Domscout-Capture-Expires-At. Retrieve owned retained artifacts with GET /capture/{captureId}; that route has no separate S3-key field, but its signed downloadUrl can contain storage-path information and is a secret capability URL. Historic rows with expiresAt null are legacy permanent retention. | | `captureName` | string | no | length 0–255 | | | `retentionDays` | 7 \| 30 \| 90 \| 365 \| "permanent" | no | | Plan-gated artifact retention selection. 365 is Enterprise-only. permanent is deprecated: it is still accepted but now resolves to the plan maximum rather than unlimited retention. | | `callbackUrl` | string | no | length 0–4096, format `uri`, pattern `^https://` | Legacy per-job callback endpoint. Must be public HTTPS without URL credentials and must pass DNS/IP safety checks before acceptance and at every redirect hop. | | `callbackSecret` | string | no | length 0–256 | Legacy per-job signing secret. It is never returned. | | `semanticSnapshot` | boolean \| object | no | | | | `diagnostics` | boolean \| object | no | | | | `render` | object | no | | | | `render.disableAnimations` | boolean | no | default `false` | | | `render.colorScheme` | "light" \| "dark" \| "no-preference" | no | | | | `render.locale` | string | no | | BCP 47 locale such as en-US. | | `render.timezone` | string | no | | IANA timezone such as Asia/Kolkata. | | `render.geolocation` | object | no | | | | `render.geolocation.latitude` | number | yes | range -90–90 | | | `render.geolocation.longitude` | number | yes | range -180–180 | | | `render.geolocation.accuracy` | number | no | range 0–∞ | | | `render.userAgent` | string | no | length 0–1024 | Overrides the browser User-Agent string. The API enforces 1024 characters; a longer value is rejected with 400. | | `render.transparentBackground` | boolean | no | default `false` | | | `render.blockResourceTypes` | array of "document" \| "stylesheet" \| "image" \| "media" \| "font" \| "script" \| "xhr" \| "fetch" \| "websocket" \| "other" | no | | | | `render.blockDomains` | array of string | no | items 0–100 | | | `render.capturePadding` | integer | no | default `0`, range 0–1000 | | | `render.devicePreset` | "desktop" \| "iphone13" \| "pixel7" \| "ipad" | no | | Named viewport and device-emulation preset. | | `render.reducedMotion` | boolean | no | default `false` | | | `render.media` | "screen" \| "print" | no | default `"screen"` | | | `cleanup` | boolean \| object | no | | | | `extract` | object | no | | | | `extract.fields` | object | yes | | | | `extract.strict` | boolean | no | default `false` | Fail the request when a required field is missing. | | `renderJs` | boolean | no | default `true` | Set false to answer from one guarded HTTP GET instead of a browser. Roughly an order of magnitude faster and priced identically, but a page that renders its content with JavaScript returns whatever its server-side HTML says, which is frequently an empty shell. The response always reports metadata.renderJs and metadata.contentQuality so a caller can detect that case and retry with the browser. Refused with 400 VALIDATION_FAILED, naming each field and refunding the reservation, when combined with html, screenshot:true, a field that needs a live page (actions, injectJS, injectCSS, waitForSelector, selector, captureSelector, hideSelectors, forceState, cookies, headers, a non-zero delay, semanticNodes), save:true, or any option that carries a credit surcharge. | | `fast` | boolean | no | default `false` | Alias for renderJs:false. Identical behaviour; both spellings are accepted. | | `screenshot` | boolean | no | default `false` | POST /scrape only. Put the base64 image back in the response, which /scrape drops by default. Ignored by POST /screenshot, which always returns one. Requires a browser, so it cannot be combined with renderJs:false. | **Responses** | Status | Meaning | | --- | --- | | 200 | Synchronous capture result. | | 202 | The request was explicitly asynchronous or automatically promoted to a durable job. | | 206 | The action budget ended after some action/timeline work completed. metadata.budgetWarning names the budget that applied: 24s synchronous, 52s for async and other worker-executed captures. | | 400 | The request failed validation. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 409 | The Idempotency-Key was already used with a different request body. | | 413 | The requested binary response cannot be served synchronously. | | 429 | The plan quota or rate limit has been reached. | | 500 | An unexpected service error occurred. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | | 504 | The browser session timed out during navigation or while executing actions, or did not finish before the synchronous capture deadline (`RENDER_DEADLINE_EXCEEDED`). When frames were captured before an action timeout the body includes partialResult and timelineBase64. Retry with async:true to run outside the synchronous window. | ### POST /scrape Read a page as Markdown, with or without a browser The same capture engine POST /screenshot runs, addressed as a content endpoint. It defaults extractMarkdown:true and forces responseType:json, and it drops the base64 image from the response unless screenshot:true asks for it — which is what keeps a text answer inside the synchronous response budget on pages whose PNG would not fit. Every field, plan gate, entitlement, credit weight and error contract is POST /screenshot's. The only differences are the three defaults above and the response shape. Send renderJs:false (or fast:true) to answer from one guarded HTTP GET instead of a browser. That path is roughly an order of magnitude faster, costs the same credits, and returns the identical document shape — but it does not execute JavaScript, so check metadata.renderJs and metadata.contentQuality before concluding a page is empty. renderJs:false is also accepted on POST /screenshot, where it returns this document shape because there is no image to return. **Path and header parameters** | Name | In | Required | Type | Notes | | --- | --- | --- | --- | --- | | `Idempotency-Key` | header | no | string | Optional caller-generated key for POST submission. Reusing a key with an identical body for the same owner returns the original job; reusing it with a different body returns 409. | **Request body** | Parameter | Type | Required | Constraints | Notes | | --- | --- | --- | --- | --- | | `url` | string | no | length 0–4096, format `uri` | Public HTTP(S) page URL. Unsafe addresses and redirects are rejected. Credentials in the authority (https://user:pass@host/) are refused with 400 — send authentication in the headers field instead, so it is never written to a capture log. | | `html` | string | no | length 0–1000000 | Raw HTML to render instead of navigating to a URL. | | `format` | "png" \| "jpeg" \| "webp" \| "pdf" | no | default `"png"` | | | `responseType` | "json" \| "binary" | no | default `"json"` | How the capture comes back. "json" (default) returns a JSON envelope with base64 image data plus metadata and any analysis you requested. "binary" returns the raw file bytes with a matching Content-Type, and therefore has nowhere to carry metadata — so a binary request combined with an analysis option (cleanup, semanticSnapshot, diagnostics, or extract) is AUTO-PROMOTED to a durable job: it answers 202 with a jobId and metadata.autoPromotedAsync, and you poll GET /job/{jobId} for the result. A capture that fails after promotion reports its failure on the job rather than as a synchronous 4xx, carrying the same error code it would have returned inline. Binary responses also cannot carry metadata.ignoredFields. | | `async` | boolean | no | default `false` | Force durable asynchronous execution. The service also auto-promotes captures expected to be large (rich JSON output) or slow (a URL capture stacking two or more whole-DOM passes, or one combined with fullPage/lazyScroll). Slow-capture promotion applies to Pro and above; Hobby keeps inline results. | | `probe` | boolean | no | default `false` | Load and measure the page without a screenshot. | | `width` | integer | no | default `1280`, range 100–3840 | | | `height` | integer | no | default `800`, range 100–2160 | | | `isMobile` | boolean | no | default `false` | | | `hasTouch` | boolean | no | default `false` | | | `deviceScaleFactor` | number | no | default `2`, range 1–3 | | | `quality` | integer | no | default `80`, range 1–100 | | | `fullPage` | boolean | no | default `false` | Capture the whole scrollable page rather than the viewport. Adds a credit surcharge. The rendered page may be at most 12000px tall; beyond that the request is refused with 400 and code CAPTURE_TOO_TALL rather than a truncated image. The same ceiling applies to captureSelector. | | `selector` | string | no | length 0–2000 | Legacy alias for captureSelector. | | `captureSelector` | string | no | length 0–2000 | The matched element must be visible within 5000ms, or the request is refused with 400 and code CAPTURE_SELECTOR_NOT_FOUND. It is also subject to the same 12000px ceiling as fullPage (400, CAPTURE_TOO_TALL). | | `hideSelectors` | array of string | no | items 0–50 | | | `captureMode` | "default" \| "grid" \| "skeleton" | no | default `"default"` | default uses the requested viewport. grid produces responsive desktop, tablet, and mobile frames without reloading raw inline HTML. skeleton preserves a DOMContentLoaded frame before waiting for bounded hydration, so an inline HTML skeleton is still useful even if later network-idle settling times out. grid and skeleton also honour extractMarkdown; the Markdown is taken from the DOM after the mode has finished capturing, so for grid that is the final (desktop) viewport. | | `waitForSelector` | string | no | length 0–2000 | A wait that expires is a client-side outcome, not a server fault: the request is refused with 400 and code WAIT_FOR_SELECTOR_TIMEOUT. | | `delay` | integer | no | default `0`, range 0–5000 | | | `headers` | object | no | | Scoped only to requests to the target origin. Do not expect sensitive header values to appear in diagnostics or persisted artifacts. | | `cookies` | array of object | no | items 0–50 | | | `cookies[].selector` | string | no | length 0–2000 | | | `cookies[].name` | string | yes | length 0–256 | | | `cookies[].value` | string | yes | length 0–4096 | | | `cookies[].domain` | string | no | length 0–255 | | | `cookies[].path` | string | no | length 0–1024 | | | `cookies[].httpOnly` | boolean | no | | | | `cookies[].secure` | boolean | no | | | | `cookies[].sameSite` | "Strict" \| "Lax" \| "None" | no | | | | `injectCSS` | string | no | length 0–100000 | | | `injectJS` | string | no | length 0–50000 | Trusted-JavaScript feature. It requires an eligible plan, an operator-controlled account entitlement, and the platform-wide emergency switch. | | `aiMode` | boolean | no | default `false` | | | `debugOverlay` | boolean | no | default `false` | Render console errors, page exceptions, failed requests and oversized assets into an overlay on the captured image. At most 200 entries are collected and each is truncated to 2048 characters; past that the overlay ends with a [TRUNCATED] marker. For structured, machine-readable output use `diagnostics` instead. | | `performanceOverlay` | boolean | no | default `false` | | | `forceState` | array of object | no | | | | `forceState[].selector` | string | yes | length 0–2000 | A CSS selector. If it matches several elements the state is applied to the first, and the result says so. | | `forceState[].state` | "hover" \| "focus" | yes | | | | `actions` | array of object | no | items 0–25 | | | `actions[].action` | "click" \| "type" \| "scroll" \| "wait" \| "navigate" \| "back" \| "forward" \| "reload" \| "select" \| "check" \| "uncheck" \| "hover" \| "focus" \| "pressKey" \| "scrollIntoView" \| "waitForText" \| "waitForUrl" \| "waitForFunction" \| "waitForRequest" \| "waitForResponse" \| "waitForNetworkIdle" \| "openTab" \| "switchTab" \| "closeTab" \| "assert" \| "upload" | yes | | | | `actions[].target` | object | no | | A selector-based action target. Semantic snapshot node IDs are inspection evidence only and are intentionally not accepted as durable action targets. | | `actions[].selector` | string | no | length 0–2000 | Legacy CSS target. Do not combine with target. | | `actions[].frame` | object | no | | A same-origin iframe target. Cross-origin frames are reported as metadata only. | | `actions[].frame.name` | string | no | length 0–512 | | | `actions[].frame.urlIncludes` | string | no | length 0–4096 | | | `actions[].tabId` | string | no | length 0–128 | openTab: the id for the new tab, refused with 400 VALIDATION_FAILED if a tab with that id is open; omitted, a free tab-N id is generated. switchTab and closeTab: the tab to act on. closeTab refuses to close the last open tab, whatever its id (400 TAB_CLOSE_FORBIDDEN). | | `actions[].url` | string | no | length 0–4096 | | | `actions[].text` | string | no | length 0–2000 | | | `actions[].exact` | boolean | no | default `true` | | | `actions[].key` | string | no | length 0–128 | Puppeteer key name, for example Enter, Tab, or Control+A. | | `actions[].amount` | number | no | default `500`, range -5000–5000 | | | `actions[].time` | integer | no | default `1000`, range 0–5000 | | | `actions[].value` | string \| array of string | no | | | | `actions[].expression` | string | no | length 0–4096 | Trusted-JavaScript only; waitForFunction requires the same plan, account entitlement, and platform-wide emergency switch as injectJS. | | `actions[].files` | array of object | no | items 1–20 | | | `actions[].expected` | string | no | length 0–2048 | Assertion expression: exists, visible, checked, unchecked, disabled, enabled, text:VALUE, or equals:VALUE. | | `actions[].continueOnError` | boolean | no | default `false` | | | `typeSpeed` | integer | no | default `40`, range 0–40 | | | `fastType` | boolean | no | default `false` | | | `captureTimeline` | boolean | no | default `false` | | | `extractMarkdown` | boolean | no | default `false` | Extract agent-readable Markdown. Empty main content falls back to the document body and then the accessibility tree when available. | | `semanticNodes` | boolean | no | default `false` | Legacy interactive-node summary. Prefer semanticSnapshot for rich DOM data. | | `simplifyDom` | boolean | no | default `false` | | | `lazyScroll` | boolean | no | default `false` | | | `pdfFormat` | string | no | default `"A4"`, length 0–32 | Paper size for a PDF capture. Matched case-insensitively against LETTER, LEGAL, TABLOID, LEDGER, A0, A1, A2, A3, A4, A5 and A6; anything else is refused with 400 and code PDF_FORMAT_UNSUPPORTED, which names the accepted values. An empty or omitted value takes the A4 default. | | `landscape` | boolean | no | default `false` | | | `printBackground` | boolean | no | default `true` | | | `save` | boolean | no | default `false` | Persist capture artifacts using the configured finite, plan-bounded retention policy. JSON responses include metadata.captureId, metadata.retentionDays, and metadata.captureExpiresAt. A synchronous binary response exposes X-Domscout-Capture-Id and X-Domscout-Capture-Expires-At. Retrieve owned retained artifacts with GET /capture/{captureId}; that route has no separate S3-key field, but its signed downloadUrl can contain storage-path information and is a secret capability URL. Historic rows with expiresAt null are legacy permanent retention. | | `captureName` | string | no | length 0–255 | | | `retentionDays` | 7 \| 30 \| 90 \| 365 \| "permanent" | no | | Plan-gated artifact retention selection. 365 is Enterprise-only. permanent is deprecated: it is still accepted but now resolves to the plan maximum rather than unlimited retention. | | `callbackUrl` | string | no | length 0–4096, format `uri`, pattern `^https://` | Legacy per-job callback endpoint. Must be public HTTPS without URL credentials and must pass DNS/IP safety checks before acceptance and at every redirect hop. | | `callbackSecret` | string | no | length 0–256 | Legacy per-job signing secret. It is never returned. | | `semanticSnapshot` | boolean \| object | no | | | | `diagnostics` | boolean \| object | no | | | | `render` | object | no | | | | `render.disableAnimations` | boolean | no | default `false` | | | `render.colorScheme` | "light" \| "dark" \| "no-preference" | no | | | | `render.locale` | string | no | | BCP 47 locale such as en-US. | | `render.timezone` | string | no | | IANA timezone such as Asia/Kolkata. | | `render.geolocation` | object | no | | | | `render.geolocation.latitude` | number | yes | range -90–90 | | | `render.geolocation.longitude` | number | yes | range -180–180 | | | `render.geolocation.accuracy` | number | no | range 0–∞ | | | `render.userAgent` | string | no | length 0–1024 | Overrides the browser User-Agent string. The API enforces 1024 characters; a longer value is rejected with 400. | | `render.transparentBackground` | boolean | no | default `false` | | | `render.blockResourceTypes` | array of "document" \| "stylesheet" \| "image" \| "media" \| "font" \| "script" \| "xhr" \| "fetch" \| "websocket" \| "other" | no | | | | `render.blockDomains` | array of string | no | items 0–100 | | | `render.capturePadding` | integer | no | default `0`, range 0–1000 | | | `render.devicePreset` | "desktop" \| "iphone13" \| "pixel7" \| "ipad" | no | | Named viewport and device-emulation preset. | | `render.reducedMotion` | boolean | no | default `false` | | | `render.media` | "screen" \| "print" | no | default `"screen"` | | | `cleanup` | boolean \| object | no | | | | `extract` | object | no | | | | `extract.fields` | object | yes | | | | `extract.strict` | boolean | no | default `false` | Fail the request when a required field is missing. | | `renderJs` | boolean | no | default `true` | Set false to answer from one guarded HTTP GET instead of a browser. Roughly an order of magnitude faster and priced identically, but a page that renders its content with JavaScript returns whatever its server-side HTML says, which is frequently an empty shell. The response always reports metadata.renderJs and metadata.contentQuality so a caller can detect that case and retry with the browser. Refused with 400 VALIDATION_FAILED, naming each field and refunding the reservation, when combined with html, screenshot:true, a field that needs a live page (actions, injectJS, injectCSS, waitForSelector, selector, captureSelector, hideSelectors, forceState, cookies, headers, a non-zero delay, semanticNodes), save:true, or any option that carries a credit surcharge. | | `fast` | boolean | no | default `false` | Alias for renderJs:false. Identical behaviour; both spellings are accepted. | | `screenshot` | boolean | no | default `false` | POST /scrape only. Put the base64 image back in the response, which /scrape drops by default. Ignored by POST /screenshot, which always returns one. Requires a browser, so it cannot be combined with renderJs:false. | **Responses** | Status | Meaning | | --- | --- | | 200 | The page as a document. | | 202 | The request was explicitly asynchronous or automatically promoted to a durable job. | | 206 | The action budget ended after some action/timeline work completed. metadata.budgetWarning names the budget that applied: 24s synchronous, 52s for async and other worker-executed captures. | | 400 | The request failed validation. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 409 | The Idempotency-Key was already used with a different request body. | | 413 | The requested binary response cannot be served synchronously. | | 415 | renderJs:false was asked to read a non-textual payload (a PDF, an image, a binary download). Retry with renderJs:true, which renders it in the browser. | | 429 | The plan quota or rate limit has been reached. | | 500 | An unexpected service error occurred. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | | 504 | The browser session timed out during navigation or while executing actions, or did not finish before the synchronous capture deadline (`RENDER_DEADLINE_EXCEEDED`). When frames were captured before an action timeout the body includes partialResult and timelineBase64. Retry with async:true to run outside the synchronous window. | ### GET /captures List retained saved-capture metadata Returns non-expired captures owned by the API key's account. The response never includes S3 object keys or signed URLs; retrieve one capture through GET /capture/{captureId} when an artifact is needed. This is a free control-plane read. **Path and header parameters** | Name | In | Required | Type | Notes | | --- | --- | --- | --- | --- | | `limit` | query | no | integer | Rows per page. Clamped to 1-100; defaults to 50. | | `offset` | query | no | integer | Number of rows to skip. Clamped to 0-10000; defaults to 0. | **Responses** | Status | Meaning | | --- | --- | | 200 | A bounded, newest-first page of retained capture metadata. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 429 | The plan quota or rate limit has been reached. | | 500 | An unexpected service error occurred. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ### GET /capture/{captureId} Get a saved capture and short-lived artifact download URLs Requires an API key owned by the capture's account. The service deliberately returns the same 404 response when the capture is absent, a finite-retention capture has expired, or it belongs to another account. New saves have a finite plan-bounded expiry; a historic saved row with expiresAt null is legacy permanent retention. The response has no separate S3 object-key field, but a signed downloadUrl can contain storage-path information and is a secret capability: do not log it or expose it to an untrusted client. Each URL expires no later than one hour after issuance and, for a finite-retention capture, is clamped not to outlive it. **Responses** | Status | Meaning | | --- | --- | | 200 | Owned retained saved-capture metadata and signed artifact download URLs (finite-retention or legacy permanent). | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 404 | The resource does not exist, has expired, or is not owned by this API key. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ## Jobs Durable asynchronous work and cancellation. ### GET /jobs List durable jobs for this account Returns safe metadata for durable jobs owned by this account, including child jobs. Inputs, result artifact keys, and raw payloads are never returned. This is a free control-plane read. **Path and header parameters** | Name | In | Required | Type | Notes | | --- | --- | --- | --- | --- | | `limit` | query | no | integer | Rows per page. Clamped to 1-100; defaults to 50. | | `offset` | query | no | integer | Number of rows to skip. Clamped to 0-10000; defaults to 0. | **Responses** | Status | Meaning | | --- | --- | | 200 | A bounded, newest-first page of durable jobs. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 429 | The plan quota or rate limit has been reached. | | 500 | An unexpected service error occurred. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ### GET /job/{jobId} Get a durable job and its result when complete **Responses** | Status | Meaning | | --- | --- | | 200 | Job state. A completed job can include its safe result; it does not promise saved-capture retention or artifact descriptors. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 404 | The resource does not exist, has expired, or is not owned by this API key. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ### DELETE /job/{jobId} Request cancellation of a durable job Cancellation is cooperative. Work already running may finish, while unstarted children are cancelled without consuming page quota. **Work already in flight is still charged.** A capture is claimed by its worker within about a second of submission, so a cancellation for a single capture almost always arrives after the browser has started and that capture is billed as a completed one; the credit is not refunded. What cancellation saves is the unstarted remainder — for a batch or crawl, every child that had not yet been claimed. Read the response's counts rather than assuming a cancelled job cost nothing, and treat `GET /credits` as authoritative for what was actually spent. **Responses** | Status | Meaning | | --- | --- | | 200 | The job was already terminal or cancellation completed. | | 202 | Cancellation was requested and will be observed by the worker. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 404 | The resource does not exist, has expired, or is not owned by this API key. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ### GET /job/{jobId}/metadata Get safe job metadata without loading rich result content **Responses** | Status | Meaning | | --- | --- | | 200 | Job timing, state, a safe result summary, and aggregate counts. It does not promise saved-capture retention or artifact descriptors; retrieve an owned saved capture through GET /capture/{captureId} instead. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 404 | The resource does not exist, has expired, or is not owned by this API key. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ## Batch A bounded collection of independent capture jobs. ### POST /batch Submit up to 100 independent capture requests Requires the Business plan or higher. A Pro or Hobby key receives 403 with code PLAN_FEATURE_UNAVAILABLE and an upgrade_url. Business and Enterprise keys can submit normally; when the deployment-wide product emergency pause is active, they receive 403 FEATURE_NOT_ENABLED without an upgrade_url and GET /credits reports capabilities.routes["/batch"].reason as platform. This route is not billed, but the gates still apply: "free" means no credit is charged, not that every account may call it. Each item becomes a bounded child job. Results are aggregated through the returned parent job. **One parent workflow at a time.** A single API key may have only one batch or crawl active (pending, processing, retrying, or cancel_requested). A second submission while one is running is refused with 409 and code WORKFLOW_ALREADY_ACTIVE, and the response's existingJobId names the blocking job. Recover by polling that job until it reaches a terminal state, or by cancelling it with DELETE. The limit is per API key, so separate keys on the same account run independently. Note that a submission which times out at the gateway may still be running: the work continues server-side, so a 504 on submit can be followed by this 409 on the next attempt even though nothing looks active from the caller's side. **Path and header parameters** | Name | In | Required | Type | Notes | | --- | --- | --- | --- | --- | | `Idempotency-Key` | header | no | string | Optional caller-generated key for POST submission. Reusing a key with an identical body for the same owner returns the original job; reusing it with a different body returns 409. | **Request body** | Parameter | Type | Required | Constraints | Notes | | --- | --- | --- | --- | --- | | `requests` | array of object | yes | items 1–100 | | | `metadata` | object | no | | | | `callbackUrl` | string | no | length 0–4096, format `uri`, pattern `^https://` | Public HTTPS callback endpoint checked against private, reserved, and metadata addresses before acceptance and delivery. | **Responses** | Status | Meaning | | --- | --- | | 200 | Idempotent replay of a batch workflow that has already reached a terminal state (done, error, or cancelled). The body carries replayed:true and the final counts. A replay of a workflow that is still running returns 202 instead. | | 202 | Batch parent job accepted. | | 400 | The request failed validation. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 409 | The Idempotency-Key was already used with a different request body. | | 429 | The plan quota or rate limit has been reached. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ### GET /batch/{jobId} Get aggregate batch status and completed child results **Responses** | Status | Meaning | | --- | --- | | 200 | Batch parent status. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 404 | The resource does not exist, has expired, or is not owned by this API key. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ### DELETE /batch/{jobId} Cancel a batch and its unstarted child jobs **Responses** | Status | Meaning | | --- | --- | | 200 | Batch is terminal or was cancelled. | | 202 | Cancellation was requested. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 404 | The resource does not exist, has expired, or is not owned by this API key. | ## Crawl A bounded, allowlisted, robots-aware site crawl. ### POST /crawl Submit a bounded allowlisted site crawl Requires the Business plan or higher. A Pro or Hobby key receives 403 with code PLAN_FEATURE_UNAVAILABLE and an upgrade_url. Business and Enterprise keys can submit normally; when the deployment-wide product emergency pause is active, they receive 403 FEATURE_NOT_ENABLED without an upgrade_url and GET /credits reports capabilities.routes["/crawl"].reason as platform. This route is not billed, but the gates still apply: "free" means no credit is charged, not that every account may call it. Crawls use one queued child job per page. Only HTTPS URLs passing the service SSRF guard and the caller's allowedOrigins policy are eligible. Every started child capture uses the normal capture waterfall: included monthly quota, then prepaid credits, then permitted plan overage. There is no separate crawl-page quota. Once all created child items are terminal, the parent is terminalized promptly rather than waiting for a periodic reconciler. **One parent workflow at a time.** A single API key may have only one batch or crawl active (pending, processing, retrying, or cancel_requested). A second submission while one is running is refused with 409 and code WORKFLOW_ALREADY_ACTIVE, and the response's existingJobId names the blocking job. Recover by polling that job until it reaches a terminal state, or by cancelling it with DELETE. The limit is per API key, so separate keys on the same account run independently. Note that a submission which times out at the gateway may still be running: the work continues server-side, so a 504 on submit can be followed by this 409 on the next attempt even though nothing looks active from the caller's side. **Path and header parameters** | Name | In | Required | Type | Notes | | --- | --- | --- | --- | --- | | `Idempotency-Key` | header | no | string | Optional caller-generated key for POST submission. Reusing a key with an identical body for the same owner returns the original job; reusing it with a different body returns 409. | **Request body** | Parameter | Type | Required | Constraints | Notes | | --- | --- | --- | --- | --- | | `seedUrl` | string | yes | format `uri` | HTTPS starting URL. | | `allowedOrigins` | array of string | yes | items 1–50 | Explicit HTTPS origin allowlist. Include every external origin the crawler may follow. | | `includePatterns` | array of string | no | items 0–100 | | | `excludePatterns` | array of string | no | items 0–100 | | | `maxDepth` | integer | no | default `3`, range 0–5 | | | `maxPages` | integer | no | default `100`, range 1–500 | | | `respectRobots` | boolean | no | default `true` | robots.txt is always respected for crawls; false is not accepted. | | `capture` | object | no | | Per-page screenshot options. url, html, async, callbackUrl, callbackSecret, and save are owned by the crawl job and are not accepted here. | | `capture.format` | "png" \| "jpeg" \| "webp" \| "pdf" | no | | | | `capture.fullPage` | boolean | no | | | | `capture.extractMarkdown` | boolean | no | | | | `capture.semanticSnapshot` | boolean \| object | no | | | | `capture.diagnostics` | boolean \| object | no | | | | `capture.render` | object | no | | | | `capture.render.disableAnimations` | boolean | no | default `false` | | | `capture.render.colorScheme` | "light" \| "dark" \| "no-preference" | no | | | | `capture.render.locale` | string | no | | BCP 47 locale such as en-US. | | `capture.render.timezone` | string | no | | IANA timezone such as Asia/Kolkata. | | `capture.render.geolocation` | object | no | | | | `capture.render.geolocation.latitude` | number | yes | range -90–90 | | | `capture.render.geolocation.longitude` | number | yes | range -180–180 | | | `capture.render.geolocation.accuracy` | number | no | range 0–∞ | | | `capture.render.userAgent` | string | no | length 0–1024 | Overrides the browser User-Agent string. The API enforces 1024 characters; a longer value is rejected with 400. | | `capture.render.transparentBackground` | boolean | no | default `false` | | | `capture.render.blockResourceTypes` | array of "document" \| "stylesheet" \| "image" \| "media" \| "font" \| "script" \| "xhr" \| "fetch" \| "websocket" \| "other" | no | | | | `capture.render.blockDomains` | array of string | no | items 0–100 | | | `capture.render.capturePadding` | integer | no | default `0`, range 0–1000 | | | `capture.render.devicePreset` | "desktop" \| "iphone13" \| "pixel7" \| "ipad" | no | | Named viewport and device-emulation preset. | | `capture.render.reducedMotion` | boolean | no | default `false` | | | `capture.render.media` | "screen" \| "print" | no | default `"screen"` | | | `capture.cleanup` | boolean \| object | no | | | | `capture.extract` | object | no | | | | `capture.extract.fields` | object | yes | | | | `capture.extract.strict` | boolean | no | default `false` | Fail the request when a required field is missing. | | `metadata` | object | no | | | | `callbackUrl` | string | no | length 0–4096, format `uri`, pattern `^https://` | Public HTTPS callback endpoint checked against private, reserved, and metadata addresses before acceptance and delivery. | **Responses** | Status | Meaning | | --- | --- | | 200 | Idempotent replay of a crawl workflow that has already reached a terminal state (done, error, or cancelled). The body carries replayed:true and the final counts. A replay of a workflow that is still running returns 202 instead. | | 202 | Crawl parent job accepted. | | 400 | The request failed validation. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 409 | The Idempotency-Key was already used with a different request body. | | 429 | The plan quota or rate limit has been reached. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ### GET /crawl/{jobId} Get crawl frontier, aggregate status, and completed page results **Responses** | Status | Meaning | | --- | --- | | 200 | Crawl parent status. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 404 | The resource does not exist, has expired, or is not owned by this API key. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ### DELETE /crawl/{jobId} Cancel a crawl and stop scheduling unstarted pages **Responses** | Status | Meaning | | --- | --- | | 200 | Crawl is terminal or was cancelled. | | 202 | Cancellation was requested. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 404 | The resource does not exist, has expired, or is not owned by this API key. | ## Discovery URL discovery from XML sitemaps and same-site page links. ### POST /map Discover URLs from sitemaps and page links Returns a bounded, robots-aware URL map. Sitemap locations and same-site links are deduplicated and canonicalized. The endpoint does not execute JavaScript. Intelligence work has one 18-second synchronous deadline across the complete request. **Request body** | Parameter | Type | Required | Constraints | Notes | | --- | --- | --- | --- | --- | | `url` | string | yes | length 0–4096, format `uri`, pattern `^https://` | | | `maxPages` | integer | no | default `50`, range 1–100 | | | `maxDepth` | integer | no | default `2`, range 0–3 | | | `maxUrls` | integer | no | default `500`, range 1–500 | | | `includeSitemap` | boolean | no | default `true` | | | `discoverLinks` | boolean | no | default `true` | | | `includePaths` | array of string | no | items 0–50 | | | `excludePaths` | array of string | no | items 0–50 | | | `allowExternalLinks` | boolean | no | default `false` | | **Responses** | Status | Meaning | | --- | --- | | 200 | Discovered URLs. | | 400 | The request failed validation. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 429 | The plan quota or rate limit has been reached. | | 503 | The request work may have completed, but the service could not confirm its usage-finalization write. The response code is `INTELLIGENCE_FINALIZATION_UNCERTAIN`; no result is returned. Its state can be ambiguous (including a possible completed index write), so do not automatically retry. Retain `requestId` and contact support so the outcome can be investigated; any pending usage record is reconciled separately. | | 504 | The shared 18-second synchronous intelligence-work deadline expired. In that case the response code is `INTELLIGENCE_DEADLINE_EXCEEDED`. For a URL-based /extract request, retry with a bounded content or HTML excerpt from the page rather than asking the service to fetch it again; that advice is what the response carries, and when fetching the page alone exhausted the budget the message says so, because no model call was attempted. Other intelligence routes should retry with a smaller input or fewer pages. A provider that reaches its own shorter timeout first retains its provider-specific timeout code. | ## AI Provider-backed structured extraction from supplied page content. ### POST /extract Extract structured JSON with an LLM Accepts a URL, HTML, or text content plus a prompt and JSON Schema. The configured LLM provider must return JSON that passes the schema validator. When the first valid JSON object fails that schema, the service may make one corrective provider call inside the same request deadline; a second mismatch remains AI_SCHEMA_VALIDATION_FAILED. Requires Pro or higher. Intelligence work has one 18-second synchronous deadline across the complete request. **Request body** | Parameter | Type | Required | Constraints | Notes | | --- | --- | --- | --- | --- | | `url` | string | no | length 0–4096, format `uri` | | | `content` | string | no | length 0–300000 | | | `html` | string | no | length 0–1000000 | | | `prompt` | string | yes | length 1–4000 | | | `schema` | object | yes | | A JSON Schema object describing the expected output. domscout enforces a declared SUBSET of JSON Schema: type, enum, const, properties, required, additionalProperties, minProperties, maxProperties, items, minItems, maxItems, uniqueItems, minLength, maxLength, pattern, minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, anyOf, oneOf. The annotations title, description, default, examples, $comment and $schema are accepted and carry no constraint. Any other keyword — including format, $ref, allOf, not and patternProperties — is rejected with a 400 rather than accepted and ignored, so a 200 means every constraint supplied was checked. Maximum nesting depth is 12. A `pattern` is a caller-supplied regular expression run against model output and is refused at 400 if it exceeds 512 characters or its structure permits catastrophic backtracking. | | `model` | string | no | length 0–128 | Optional. When present it must equal the model this deployment is configured to run (the value echoed as `model` in a successful response); any other value is refused with 400 VALIDATION_FAILED before a provider is called. The route's credit price does not depend on the model, so the model is the deployment's choice, not the caller's. | **Responses** | Status | Meaning | | --- | --- | | 200 | Validated structured extraction. | | 400 | The request failed validation. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 429 | The plan quota or rate limit has been reached. | | 502 | An unexpected service error occurred. | | 503 | A required intelligence provider is unavailable or misconfigured. This response can also have code `INTELLIGENCE_FINALIZATION_UNCERTAIN`: in that case no result is returned, but the work may have completed, so do not automatically retry. Retain `requestId` and contact support so the outcome can be investigated; any pending usage record is reconciled separately. | | 504 | The shared 18-second synchronous intelligence-work deadline expired. In that case the response code is `INTELLIGENCE_DEADLINE_EXCEEDED`. For a URL-based /extract request, retry with a bounded content or HTML excerpt from the page rather than asking the service to fetch it again; that advice is what the response carries, and when fetching the page alone exhausted the budget the message says so, because no model call was attempted. Other intelligence routes should retry with a smaller input or fewer pages. A provider that reaches its own shorter timeout first retains its provider-specific timeout code. | ## Search External web search with optional guarded result scraping. ### POST /search Search the web and optionally scrape result pages (WITHHELD) WITHHELD AS OF 2026-08-12. This deployment serves no search provider, so every call answers 503 SEARCH_NOT_CONFIGURED regardless of plan or key. It is documented because the route exists and must be described accurately, not because it is offered. Read `capabilities.routes["/search"]` from GET /credits before calling it: while it reports available:false with reason "deployment", nothing you can change as a caller will make this route return results. Marked deprecated so generated clients surface the state rather than presenting it as usable; the marker will be removed when a provider is chosen. When a provider IS configured, the route uses it and then fetches result pages through domscout's guarded transport and robots policy. Requires Pro or higher. Intelligence work has one 18-second synchronous deadline across the complete request. **Request body** | Parameter | Type | Required | Constraints | Notes | | --- | --- | --- | --- | --- | | `query` | string | yes | length 1–1000 | | | `limit` | integer | no | default `5`, range 1–10 | | | `scrape` | boolean | no | default `true` | | | `maxChars` | integer | no | default `20000`, range 1000–40000 | | **Responses** | Status | Meaning | | --- | --- | | 200 | Search results and optional page text. | | 400 | The request failed validation. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 429 | The plan quota or rate limit has been reached. | | 502 | An unexpected service error occurred. | | 503 | A required intelligence provider is unavailable or misconfigured. This response can also have code `INTELLIGENCE_FINALIZATION_UNCERTAIN`: in that case no result is returned, but the work may have completed, so do not automatically retry. Retain `requestId` and contact support so the outcome can be investigated; any pending usage record is reconciled separately. | | 504 | The shared 18-second synchronous intelligence-work deadline expired. In that case the response code is `INTELLIGENCE_DEADLINE_EXCEEDED`. For a URL-based /extract request, retry with a bounded content or HTML excerpt from the page rather than asking the service to fetch it again; that advice is what the response carries, and when fetching the page alone exhausted the budget the message says so, because no model call was attempted. Other intelligence routes should retry with a smaller input or fewer pages. A provider that reaches its own shorter timeout first retains its provider-specific timeout code. | ## Semantic Index Account-scoped embedding, indexing, and semantic retrieval. ### POST /index Chunk, embed, and store content in the account vector index Stores account-scoped content in Supabase pgvector using the configured embedding provider. Requires Pro or higher. Intelligence work has one 18-second synchronous deadline across the complete request. **Request body** | Parameter | Type | Required | Constraints | Notes | | --- | --- | --- | --- | --- | | `url` | string | no | length 0–4096, format `uri` | | | `sourceUrl` | string | no | length 0–4096, format `uri` | | | `content` | string | no | length 0–300000 | | | `html` | string | no | length 0–1000000 | | | `title` | string | no | length 0–512 | | | `metadata` | object | no | | | | `chunkSize` | integer | no | default `4000`, range 500–8000 | | | `chunkOverlap` | integer | no | default `400`, range 0–1000 | | | `maxChunks` | integer | no | default `500`, range 1–500 | | **Responses** | Status | Meaning | | --- | --- | | 200 | Index document created. | | 400 | The request failed validation. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 429 | The plan quota or rate limit has been reached. | | 502 | An unexpected service error occurred. | | 503 | A required intelligence provider is unavailable or misconfigured. This response can also have code `INTELLIGENCE_FINALIZATION_UNCERTAIN`: in that case no result is returned, but the work may have completed, so do not automatically retry. Retain `requestId` and contact support so the outcome can be investigated; any pending usage record is reconciled separately. | | 504 | The shared 18-second synchronous intelligence-work deadline expired. In that case the response code is `INTELLIGENCE_DEADLINE_EXCEEDED`. For a URL-based /extract request, retry with a bounded content or HTML excerpt from the page rather than asking the service to fetch it again; that advice is what the response carries, and when fetching the page alone exhausted the budget the message says so, because no model call was attempted. Other intelligence routes should retry with a smaller input or fewer pages. A provider that reaches its own shorter timeout first retains its provider-specific timeout code. | ### POST /semantic-search Search indexed content by semantic similarity Embeds the query and returns the closest account-owned chunks using cosine similarity. Requires Pro or higher. Intelligence work has one 18-second synchronous deadline across the complete request. **Request body** | Parameter | Type | Required | Constraints | Notes | | --- | --- | --- | --- | --- | | `query` | string | yes | length 1–4000 | | | `limit` | integer | no | default `10`, range 1–20 | | | `documentId` | string | no | format `uuid` | | **Responses** | Status | Meaning | | --- | --- | | 200 | Matching indexed chunks. | | 400 | The request failed validation. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 429 | The plan quota or rate limit has been reached. | | 502 | An unexpected service error occurred. | | 503 | A required intelligence provider is unavailable or misconfigured. This response can also have code `INTELLIGENCE_FINALIZATION_UNCERTAIN`: in that case no result is returned, but the work may have completed, so do not automatically retry. Retain `requestId` and contact support so the outcome can be investigated; any pending usage record is reconciled separately. | | 504 | The shared 18-second synchronous intelligence-work deadline expired. In that case the response code is `INTELLIGENCE_DEADLINE_EXCEEDED`. For a URL-based /extract request, retry with a bounded content or HTML excerpt from the page rather than asking the service to fetch it again; that advice is what the response carries, and when fetching the page alone exhausted the budget the message says so, because no model call was attempted. Other intelligence routes should retry with a smaller input or fewer pages. A provider that reaches its own shorter timeout first retains its provider-specific timeout code. | ## Webhooks Stored outbound webhook deliveries and manual replay. ### POST /webhooks/deliveries/{deliveryId}/replay Queue a signed replay of a stored webhook delivery The original persisted payload and configured signing secret are used. This does not run a browser capture. **Path and header parameters** | Name | In | Required | Type | Notes | | --- | --- | --- | --- | --- | | `Idempotency-Key` | header | no | string | Optional caller-generated key for POST submission. Reusing a key with an identical body for the same owner returns the original job; reusing it with a different body returns 409. | **Responses** | Status | Meaning | | --- | --- | | 202 | Replay delivery accepted. | | 400 | The request failed validation. | | 403 | The request was refused. Returned when the x-api-key header is missing, revoked, or invalid, and when a valid key is blocked by a plan entitlement, the trusted-JavaScript account entitlement, a global product pause, or a security policy. A plan-only restriction returns code PLAN_FEATURE_UNAVAILABLE and can include upgrade_url. FEATURE_NOT_ENABLED identifies either the global product emergency pause or an eligible Business account without trusted-JavaScript permission; it carries no upgrade_url, so upgrading or retrying cannot clear it. Browser capture carries two platform gates that are NOT plan gates and therefore carry no upgrade_url: code BROWSER_ISOLATION_REQUIRED, returned while renderer isolation is not proven, and code BROWSER_EXECUTION_DENIED with reason public_capture_not_enabled, returned when an account without the internal-capture override captures an arbitrary public URL while PUBLIC_CAPTURE_ENABLED is off. Renderer isolation is not resolved by changing plan or key. The API does not use 401: it authenticates with an API key header rather than an HTTP authentication scheme, so there is no WWW-Authenticate challenge to issue. | | 404 | The resource does not exist, has expired, or is not owned by this API key. | | 409 | The Idempotency-Key was already used with a different request body. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ### GET /webhooks/deliveries List this account's webhook deliveries Newest first, and never billed. This is how you obtain a deliveryId — it is generated server-side and appears in no capture response, so without this route the replay endpoint below cannot be called. The delivered payload, your endpoint's response body, and the per-delivery signing secret are never returned. **Path and header parameters** | Name | In | Required | Type | Notes | | --- | --- | --- | --- | --- | | `limit` | query | no | integer | | | `offset` | query | no | integer | | **Responses** | Status | Meaning | | --- | --- | | 200 | Deliveries. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ### GET /webhooks/deliveries/{deliveryId} Read one delivery and its attempt history Every attempt, in order, with the status your endpoint returned and the latency. A delivery belonging to another account answers 404 DELIVERY_NOT_FOUND, identically to one that does not exist. **Path and header parameters** | Name | In | Required | Type | Notes | | --- | --- | --- | --- | --- | | `deliveryId` | path | yes | string | | **Responses** | Status | Meaning | | --- | --- | | 200 | The delivery. | | 404 | Not found. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ## Account Credit balance, quota, and pricing. Never billed. ### GET /credits Credit balance, quota, capabilities, and the credit price list Everything needed to budget a run, without spending anything. This request is NOT billed: it consumes no credits and no quota, and is subject only to the per-second rate limit. Returns the current credit balance, month-to-date quota usage, the overage ceiling, the per-second rate limit, and the full credit weight table so a client can price a request before sending it. The quota resets on the first of each calendar month (UTC). This is true on an annual subscription as well: an annual plan buys the same monthly allowance twelve times over, not twelve months of allowance to spend at once. It also reports which gated capabilities this key can actually use, and why not when it cannot. The normal DOM product is plan-based, but provider-backed routes depend on what this deployment has configured, the product group can be paused globally for rollback, and trusted browser-side JavaScript has its own account entitlement. Read `capabilities` before spending a billed request on /extract, /search, /index, /semantic-search, /batch, /crawl, or a gated capture option. **Responses** | Status | Meaning | | --- | --- | | 200 | Current balance, quota, capability availability, and price list. | | 403 | Missing, invalid, or revoked API key. | | 429 | Rate limit exceeded. Quota and credits are never a reason for this endpoint to refuse. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ## Feedback The calling model reports a problem, gap, or idea. Never billed. ### POST /feedback Report a problem, a documentation gap, or an idea A direct channel from the calling model or developer to the domscout team. **This request is never billed** — it consumes no credits and no quota, and is subject only to the per-second rate limit and a daily report cap. Use it when an error message was not actionable, a documented parameter did not behave as described, a capture succeeded but returned the wrong thing, or a capability you needed was missing. Include `requestId` when the report is about a specific request and the server will attach its own record of that request — status, error, latency, and the parameters it ran with — so there is no need to restate them. Reports are read by humans. Do not send secrets; the server strips credential-shaped strings before storing, but the cheapest redaction is not sending them. **Request body** | Parameter | Type | Required | Constraints | Notes | | --- | --- | --- | --- | --- | | `message` | string | yes | length 1–8000 | What happened, or what would help. The only required field. | | `type` | "bug" \| "error_report" \| "docs_gap" \| "feature_request" \| "praise" \| "other" | no | default `"other"` | Unrecognised values become "other" rather than being rejected. | | `severity` | "blocking" \| "major" \| "minor" \| "info" | no | default `"info"` | "blocking" means you could not complete the task at all. | | `requestId` | string | no | length 0–255 | The X-Request-Id of the request this is about. When present, the server attaches its own record of that request — status, error, latency, format — so you need not restate them. | | `endpoint` | string | no | length 0–256 | e.g. "POST /screenshot". | | `expected` | string | no | length 0–2000 | What you expected to happen. | | `actual` | string | no | length 0–2000 | What happened instead. | | `agent` | object | no | | Who is reporting: model, framework, version. Self-declared, used only to spot patterns across reports, never for authorization. Up to 10 string/number/boolean values. | **Responses** | Status | Meaning | | --- | --- | | 202 | Recorded. Not charged. | | 400 | message is missing or empty. Everything else is coerced rather than rejected. | | 403 | Missing, invalid, or revoked API key. | | 429 | Per-second rate limit, or the daily report cap for this account. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ## Monitors Scheduled unattended checks of a URL, and their run history. ### POST /monitors Create a monitor A monitor is a standing instruction to capture a URL unattended on a schedule and report what changed. It carries the same API-key authentication and the same browser-execution gate as a capture, because it is a capture with a timer. Plan ceilings: Hobby 0 monitors (403 PLAN_FEATURE_UNAVAILABLE), Pro 10 at a minimum interval of 60 minutes, Business 100 at 15 minutes, Enterprise 500 at 5 minutes. GET /credits reports availability under capabilities.routes["/monitors"]. Management calls are not billed. Each RUN reserves capture quota exactly as a scheduled check does, priced from the monitor's own captureParams. If notifyWebhookUrl is set, the creation response carries notifyWebhookSecret. No API read returns it, so store it on receipt. Unlike an API key it is not unrecoverable: the account owner can reveal it in the dashboard, and POST /monitors/{monitorId}/rotate-secret replaces it, disclosing the replacement in that response. **Request body** | Parameter | Type | Required | Constraints | Notes | | --- | --- | --- | --- | --- | | `name` | string | yes | length 0–160 | | | `targetUrl` | string | yes | format `uri` | | | `intervalMinutes` | integer | no | default `1440` | At least the plan minimum, at most 43200 (30 days). | | `captureParams` | object | no | | | | `notifyWebhookUrl` | string \| null | no | format `uri` | HTTPS only. Change notifications are signed with the secret returned at creation. | | `checkMarkdown` | boolean | no | default `true` | | | `checkLinks` | boolean | no | default `true` | | | `checkTitle` | boolean | no | default `true` | | | `checkStatusCode` | boolean | no | default `true` | | | `checkDom` | boolean | no | default `false` | | | `domIgnoreSelectors` | array of string | no | items 0–100 | | | `domIgnoreAttributes` | array of string | no | items 0–100 | | | `domIgnoreTextPatterns` | array of string | no | items 0–100 | | | `domPositionTolerancePx` | integer | no | default `2`, range 0–1000 | | **Responses** | Status | Meaning | | --- | --- | | 201 | Created. | | 400 | Validation failed. | | 403 | Plan ceiling, or a platform hold. | | 429 | Rate or quota wall. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ### GET /monitors List this account's monitors Newest first. Never includes a signing secret. **Path and header parameters** | Name | In | Required | Type | Notes | | --- | --- | --- | --- | --- | | `limit` | query | no | integer | | | `offset` | query | no | integer | | **Responses** | Status | Meaning | | --- | --- | | 200 | Monitors. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ### GET /monitors/{monitorId} Read one monitor A monitor belonging to another account answers 404 MONITOR_NOT_FOUND, identically to one that does not exist: any distinction would be an id oracle. **Path and header parameters** | Name | In | Required | Type | Notes | | --- | --- | --- | --- | --- | | `monitorId` | path | yes | string | | **Responses** | Status | Meaning | | --- | --- | | 200 | The monitor. | | 404 | Not found. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ### PATCH /monitors/{monitorId} Update, pause, or resume a monitor Only the fields you send are changed. Send status to pause or resume: "paused" always succeeds, "active" is re-checked against the plan ceiling and the browser-execution gate, because resuming is the transition that can put an account over its limit. The quota check, the status change and the field updates happen in one transaction, so a patch either fully applies or not at all. **Path and header parameters** | Name | In | Required | Type | Notes | | --- | --- | --- | --- | --- | | `monitorId` | path | yes | string | | **Request body** | Parameter | Type | Required | Constraints | Notes | | --- | --- | --- | --- | --- | | `name` | string | no | length 0–160 | | | `targetUrl` | string | no | format `uri` | | | `status` | "active" \| "paused" | no | | | | `intervalMinutes` | integer | no | | At least the plan minimum, at most 43200 (30 days). | | `captureParams` | object | no | | | | `notifyWebhookUrl` | string \| null | no | format `uri` | HTTPS only. | | `checkMarkdown` | boolean | no | | | | `checkLinks` | boolean | no | | | | `checkTitle` | boolean | no | | | | `checkStatusCode` | boolean | no | | | | `checkDom` | boolean | no | | | | `domIgnoreSelectors` | array of string | no | | | | `domIgnoreAttributes` | array of string | no | | | | `domIgnoreTextPatterns` | array of string | no | | | | `domPositionTolerancePx` | number | no | | | **Responses** | Status | Meaning | | --- | --- | | 200 | Updated. | | 400 | Validation failed. | | 403 | Plan ceiling on resume. | | 404 | Not found. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ### DELETE /monitors/{monitorId} Delete a monitor Permanent. Run history is removed with it. **Path and header parameters** | Name | In | Required | Type | Notes | | --- | --- | --- | --- | --- | | `monitorId` | path | yes | string | | **Responses** | Status | Meaning | | --- | --- | | 200 | Deleted. | | 404 | Not found. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ### POST /monitors/{monitorId}/rotate-secret Rotate a monitor webhook signing secret Replaces the signing secret for an owned monitor that has notifyWebhookUrl configured. The response is the only disclosure of the replacement: monitor reads and lists never include it. Store it on receipt. Every accepted call creates a new secret; this operation is not idempotent. Deliveries already queued retain the secret snapshotted when they were accepted, so a receiver should accept both the previous and replacement secrets until those attempts drain. A missing, foreign, or notification-less monitor answers the same 404 MONITOR_NOT_FOUND. Never billed and never launches a browser. **Path and header parameters** | Name | In | Required | Type | Notes | | --- | --- | --- | --- | --- | | `monitorId` | path | yes | string | | **Responses** | Status | Meaning | | --- | --- | | 200 | Rotated. The replacement secret is shown only in this response. | | 404 | The monitor is missing, foreign, or has no notification URL. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ### POST /monitors/{monitorId}/run Run a monitor now, once Enqueues exactly one extra check immediately, without waiting for the interval and without advancing the schedule — the next scheduled run still happens when it was going to. Bounded three ways: one run per call, with no count parameter; refused with 409 MONITOR_RUN_IN_FLIGHT while a run for this monitor is already pending or running, so it cannot be used to fan out; and billed exactly like a scheduled run, drawing on the same quota and refused by the same 429 wall. A paused monitor is refused with 409 MONITOR_PAUSED. Poll GET /monitors/{monitorId}/runs for the outcome. **Path and header parameters** | Name | In | Required | Type | Notes | | --- | --- | --- | --- | --- | | `monitorId` | path | yes | string | | **Responses** | Status | Meaning | | --- | --- | | 202 | Queued. | | 403 | A platform hold on browser execution. | | 404 | Not found. | | 409 | Paused, or a run is already in flight. | | 429 | Rate or quota wall. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ### GET /monitors/{monitorId}/runs Read a monitor's run history Newest first. changeDetected and changedFields are what a check concluded; captureId points at the saved artifact for that run. **Path and header parameters** | Name | In | Required | Type | Notes | | --- | --- | --- | --- | --- | | `monitorId` | path | yes | string | | | `limit` | query | no | integer | | **Responses** | Status | Meaning | | --- | --- | | 200 | Runs. | | 404 | Not found. | | 503 | A dependency this request needs is unavailable or not configured for this deployment. Codes: SUPABASE_TIMEOUT and SUPABASE_INVALID_RESPONSE (a storage operation did not answer, or answered unreadably — the write may have committed, so any retry must be idempotent), CAPTURE_ARTIFACT_UNAVAILABLE and CAPTURE_ARTIFACT_CORRUPT (a stored artifact could not be read or parsed), ASYNC_JOBS_NOT_CONFIGURED and CAPTURE_RETRIEVAL_NOT_CONFIGURED (the capability is not configured here, on any plan). Retryable unless the code names a deployment configuration. | ## Object reference Nested objects referenced above, expanded once each. ### Locator A selector-based action target. Semantic snapshot node IDs are inspection evidence only and are intentionally not accepted as durable action targets. _No body parameters._ ### Action Use target for new locator types. selector remains supported for legacy CSS actions. Required fields depend on action: target for element actions; url for navigate/openTab; text for type/waitForText; key for pressKey; value for select; files for upload; expected for assert. | Parameter | Type | Required | Constraints | Notes | | --- | --- | --- | --- | --- | | `action` | "click" \| "type" \| "scroll" \| "wait" \| "navigate" \| "back" \| "forward" \| "reload" \| "select" \| "check" \| "uncheck" \| "hover" \| "focus" \| "pressKey" \| "scrollIntoView" \| "waitForText" \| "waitForUrl" \| "waitForFunction" \| "waitForRequest" \| "waitForResponse" \| "waitForNetworkIdle" \| "openTab" \| "switchTab" \| "closeTab" \| "assert" \| "upload" | yes | | | | `target` | object | no | | A selector-based action target. Semantic snapshot node IDs are inspection evidence only and are intentionally not accepted as durable action targets. | | `selector` | string | no | length 0–2000 | Legacy CSS target. Do not combine with target. | | `frame` | object | no | | A same-origin iframe target. Cross-origin frames are reported as metadata only. | | `frame.name` | string | no | length 0–512 | | | `frame.urlIncludes` | string | no | length 0–4096 | | | `tabId` | string | no | length 0–128 | openTab: the id for the new tab, refused with 400 VALIDATION_FAILED if a tab with that id is open; omitted, a free tab-N id is generated. switchTab and closeTab: the tab to act on. closeTab refuses to close the last open tab, whatever its id (400 TAB_CLOSE_FORBIDDEN). | | `url` | string | no | length 0–4096 | | | `text` | string | no | length 0–2000 | | | `exact` | boolean | no | default `true` | | | `key` | string | no | length 0–128 | Puppeteer key name, for example Enter, Tab, or Control+A. | | `amount` | number | no | default `500`, range -5000–5000 | | | `time` | integer | no | default `1000`, range 0–5000 | | | `value` | string \| array of string | no | | | | `expression` | string | no | length 0–4096 | Trusted-JavaScript only; waitForFunction requires the same plan, account entitlement, and platform-wide emergency switch as injectJS. | | `files` | array of object | no | items 1–20 | | | `expected` | string | no | length 0–2048 | Assertion expression: exists, visible, checked, unchecked, disabled, enabled, text:VALUE, or equals:VALUE. | | `continueOnError` | boolean | no | default `false` | | ### Cookie | Parameter | Type | Required | Constraints | Notes | | --- | --- | --- | --- | --- | | `selector` | string | no | length 0–2000 | | | `name` | string | yes | length 0–256 | | | `value` | string | yes | length 0–4096 | | | `domain` | string | no | length 0–255 | | | `path` | string | no | length 0–1024 | | | `httpOnly` | boolean | no | | | | `secure` | boolean | no | | | | `sameSite` | "Strict" \| "Lax" \| "None" | no | | | ### ForceState Hold an element in a hover or focus state for the capture, so a screenshot can show a styling that only exists while the user is interacting. The outcome of every entry is reported back in metadata.forceState. | Parameter | Type | Required | Constraints | Notes | | --- | --- | --- | --- | --- | | `selector` | string | yes | length 0–2000 | A CSS selector. If it matches several elements the state is applied to the first, and the result says so. | | `state` | "hover" \| "focus" | yes | | | ### UploadFile _No body parameters._ ## Errors Every error is JSON: `{ "error": string, "code"?: string, "requestId": string, "suggestions"?: string[] }`. `requestId` is also returned as `X-Request-Id` on every response — quote it in `POST /feedback` and the server attaches its own record of what happened. | Status | Meaning | What to do | | --- | --- | --- | | 200 | Success | — | | 202 | Durable job accepted, or auto-promoted to async | Poll `GET /job/{jobId}` | | 206 | Partial result; the action budget expired mid-run | Use the frames returned, or retry with `async: true` | | 400 | Validation failure | Read `error`; fix the request. Do not retry unchanged | | 403 | Bad key, plan gate, entitlement, or the browser-isolation hold | Check `code`. Never retriable | | 404 | Not found, expired, or not owned by this key | — | | 409 | `IDEMPOTENCY_CONFLICT` — that key is in use for a different body | Use a new key, or poll `existingJobId` | | 413 | Response too large for the gateway | Retry with `async: true` | | 429 | Quota, credit, overage ceiling, or rate limit | Read the headers; see below | | 500 | Internal error | Retry once with backoff; report if it persists | | 504 | Navigation or action timeout | Retry with `async: true` for a 52s budget | **There is no 401.** A missing, invalid, or revoked key returns 403. ### Telling 429s apart Read `X-Domscout-Credits-Remaining` and `X-Domscout-Quota-Remaining`: - both above zero → you exceeded the **per-second rate limit**. Wait `Retry-After` (1 second) and continue. This is the only retriable 429. - both zero → you are **out of quota and credits**. Retrying will not help; buy credits or upgrade. ### Stable error codes These strings are part of the contract and safe to branch on: ``` BROWSER_ISOLATION_REQUIRED renderer isolation is not proven; not clearable by a plan or key PLAN_FEATURE_UNAVAILABLE the plan does not include this feature FEATURE_NOT_ENABLED the account entitlement is not granted IDEMPOTENCY_CONFLICT that Idempotency-Key is in use for another body NAVIGATION_TIMEOUT the target page did not load in time ACTION_BUDGET_EXCEEDED the action sequence ran out of budget ASSERTION_FAILED an `assert` action did not hold LOCATOR_NOT_FOUND the target matched nothing LOCATOR_AMBIGUOUS the target matched more than one node MISSING_TARGET the action needs `target` or `selector` FRAME_NOT_FOUND the named frame was not present TAB_NOT_FOUND the named tab was not open UPLOAD_TOO_LARGE the base64 file exceeded the limit ROBOTS_DISALLOWED robots.txt refused the crawl of that URL EGRESS_BLOCKED the target resolved to a blocked address ``` ## Telling us something If any of this was wrong, unclear, or missing, send it back — `POST /feedback` costs nothing and is read by humans. Quote the `requestId` and the server attaches its own record of what happened. ```bash curl -X POST https://api.domscout.io/feedback \ -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"type":"docs_gap","severity":"minor", "message":"The batch example uses `items` but the API requires `requests`.", "endpoint":"POST /batch"}' ```