Skip to content
domscout / docs

Durable workflows · v1

Run work that outlives one HTTP request

Use durable jobs when a capture is explicitly async, when rich output is too large for an inline response, or when you need a batch, crawl, callback, retry, and cancellation boundary. Every durable submission returns a job ID and polling URL.

Accepted first

POST returns 202 with accepted/pending state and pollUrl.

Safe retries

Idempotency-Key replays identical durable submissions without duplicate work.

Cooperative stop

Cancel unstarted work; a browser already running may finish cleanly.

01 · Captures

Make a single capture durable

Set async: true on POST /screenshot to force worker execution. You should also handle automatic 202 responses: large rich JSON, multiple whole-DOM passes, or slow combinations such as rich analysis plus fullPage can be promoted even without the flag.

explicit async capture
curl -X POST "$DOMSCOUT_API_BASE/screenshot" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $DOMSCOUT_API_KEY" \
  -H "Idempotency-Key: capture-UNIQUE_RUN_ID" \
  -d '{
    "url": "https://example.com/article",
    "async": true,
    "responseType": "json",
    "extractMarkdown": true,
    "save": true,
    "captureName": "article-snapshot",
    "retentionDays": 30
  }'

Accepted shape

The response includes status, jobId, pollUrl, and possibly autoPromoted or metadata.autoPromotedAsync. The completed job contains result, an error object, safe timing, a result summary, and counts. It does not promise saved-capture retention or artifact descriptors.

02 · Batch

Submit up to 100 independent captures

A batch is a parent job with one child job per request. Each item may use a URL or inline HTML and the normal screenshot options, but async, save, callbackUrl, and callbackSecret belong to the parent and are rejected inside an item.

POST /batch
curl -X POST "$DOMSCOUT_API_BASE/batch" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $DOMSCOUT_API_KEY" \
  -H "Idempotency-Key: batch-products-UNIQUE_RUN_ID" \
  -d '{
    "requests": [
      { "url": "https://example.com/products/1", "responseType": "json", "extractMarkdown": true },
      { "url": "https://example.com/products/2", "responseType": "json", "extractMarkdown": true }
    ],
    "metadata": { "source": "catalog-import" },
    "callbackUrl": "https://YOUR_PUBLIC_HTTPS_ENDPOINT/domscout"
  }'

Parent status

GET /batch/{jobId} returns aggregate counts and completed child items. Each item has status, URL/canonical URL, depth, timestamps, error, and resultSummary.

Quota timing

Creating the parent is not a page capture. A child consumes normal capture quota only when it is about to start browser work.

Reserved fieldsFields beginning with _ are reserved for orchestration and rejected. Every child is checked against the same plan and entitlement gates as direct captures; batch cannot bypass a capability gate.

03 · Crawl

Crawl a bounded site frontier

Crawls create one queued child per discovered page. They require seedUrl and a non-empty list of HTTPS allowedOrigins. The seed and every discovered URL must pass the service SSRF guard and the explicit allowlist.

POST /crawl
curl -X POST "$DOMSCOUT_API_BASE/crawl" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $DOMSCOUT_API_KEY" \
  -H "Idempotency-Key: docs-crawl-UNIQUE_RUN_ID" \
  -d '{
    "seedUrl": "https://docs.example.com/start",
    "allowedOrigins": ["https://docs.example.com"],
    "includePatterns": ["/docs/.*"],
    "excludePatterns": ["/docs/private/.*"],
    "maxDepth": 3,
    "maxPages": 100,
    "capture": {
      "format": "png",
      "extractMarkdown": true,
      "cleanup": true
    },
    "metadata": { "source": "docs-ingest" },
    "callbackUrl": "https://YOUR_PUBLIC_HTTPS_ENDPOINT/domscout"
  }'

Plan access for workflows.The cleanup shown in this template is available on Pro+; crawls are Business+. A 403 FEATURE_NOT_ENABLED with a platform reason means the operator has temporarily paused this product group for everyone.

Discovery

Fragments are removed, URLs are canonicalized, and include/exclude patterns bound the frontier.

Robots

robots.txt is always respected. Sending respectRobots: false is rejected; there is no bypass.

Usage & completion

Each started child follows the normal included-quota → prepaid-credit → permitted-overage waterfall. There is no crawl-only quota; the parent terminalizes promptly once all created children are terminal.

Crawl patterns are bounded for safety. Catastrophic backtracking patterns such as nested quantifiers, overlapping repeated alternatives, or more than 40 quantifiers are rejected; a CPU budget can also stop admitting remaining candidates and report patternWarning.

04 · Poll

Poll, read metadata, and cancel

poll until terminal
async function waitForJob(pollUrl, apiKey) {
  for (;;) {
    const response = await fetch(pollUrl, { headers: { "x-api-key": apiKey } });
    const job = await response.json();
    if (["done", "error", "cancelled"].includes(job.status)) return job;
    await new Promise((resolve) => setTimeout(resolve, 2000));
  }
}
pendingAccepted but not started.
processingA worker currently owns the job.
retryingA recoverable failure is being retried.
doneRead result for a capture or items/counts for a parent workflow.
errorThe job reached a terminal failure; inspect error.code, message, and suggestions.
cancel_requested / cancelledCancellation is being observed or has completed.
read safe metadata
curl "$DOMSCOUT_API_BASE/job/JOB_ID/metadata" \
  -H "x-api-key: $DOMSCOUT_API_KEY"
request cancellation
curl -X DELETE "$DOMSCOUT_API_BASE/crawl/JOB_ID" \
  -H "x-api-key: $DOMSCOUT_API_KEY"

The metadata endpoint returns state, timing breakdowns, result summaries, and aggregate counts without echoing job input. It does not expose S3 keys or promise saved-artifact metadata. Use GET /capture/{captureId} with the owning account's API key for a retained saved capture's finite expiry or legacy permanent state and short-lived signed downloads. That route has no separate S3-key field, but its URL can contain storage-path information and is a secret capability. Cancelling an already-terminal owned job returns safe terminal metadata; unknown or foreign jobs return 404.

05 · Delivery

Receive signed callbacks and replay them safely

Batch and crawl requests may supply a public HTTPS callbackUrl. Legacy per-job callbacks remain supported. Callback destinations must pass private-address, metadata-address, DNS, and redirect checks at admission and delivery.

delivery headers
x-domscout-id: DELIVERY_ID
x-domscout-timestamp: ISO_8601_UTC
x-domscout-signature: HMAC_SHA256(secret, timestamp + "." + JSON_PAYLOAD)

Verify exactly

Use the exact received payload and the timestamp header verbatim. Sign the string timestamp + "." + JSON_PAYLOAD with the configured HMAC secret, then compare safely.

Retries and replay

Durable deliveries retry at 1, 5, 15, and 60 minutes. POST /webhooks/deliveries/{deliveryId}/replay requeues the stored signed payload without launching Chromium.

replay a stored delivery
curl -X POST "$DOMSCOUT_API_BASE/webhooks/deliveries/DELIVERY_ID/replay" \
  -H "x-api-key: $DOMSCOUT_API_KEY" \
  -H "Idempotency-Key: replay-UNIQUE_RUN_ID"

06 · Retention & billing

Know what persists and what costs quota

PlanArtifact retention presetsWorkflow access
Hobby7 daysBasic synchronous image capture
Pro7 or 30 daysPDF, full-page, async, DOM intelligence, and extended actions
Business7, 30, or 90 daysPro plus injectJS, timelines, trusted waitForFunction, batch, crawl
Enterprise7, 30, 90, or 365 daysBusiness plus Enterprise limits

Only capture-producing work consumes quota: direct captures, started batch children, and started crawl children. A crawl child uses the same included-quota → prepaid-credit → permitted-overage waterfall as a direct capture; it has no separate crawl quota. Job reads, metadata reads, cancellation, webhook replay, GET /credits, and POST /feedback are control-plane operations. Credits and plans are separate: credits pay for captures but do not unlock plan capabilities.

07 · Recover

Retry the right thing

Network error before a responseRetry with the same Idempotency-Key for durable POSTs. An identical body replays the original workflow.
409 idempotency conflictThe same key was used with a different body. Stop and generate a new key only when you intend to create different work.
429Respect the response and retry-after guidance if present. A credit balance does not raise the per-second rate limit.
403 plan or platform gateRead code, detail, upgrade_url, and suggestions. Retrying unchanged cannot clear a plan, trusted-JavaScript entitlement, or active product pause.
504 or child errorInspect partialResult, error.code, and resultSummary. Use async or reduce action/rich-output work when the timeout is repeatable.
Continue with rich capture or the playground to inspect a request.