Skip to main content
Every endpoint returns one of three envelope shapes: completed, queued, or error. Check the top-level status first, then read data (success or queued) or error (failure).

Status codes

Success envelope

data holds the payload — extracted JSON for /v1/smartscraper, fetched content for /v1/scrape. credits_used is what this call cost after deduction.

Queued envelope

Returned by the async-dispatch endpoint (POST /v1/smartbrowse/recipes/:id/run). No credit fields here because the work hasn’t finished and nothing’s been billed yet.
Poll data.poll_url (i.e. GET /v1/smartbrowse/runs/:id) until the inner field hits a terminal state. We named it run_status so it doesn’t clash with the envelope’s outer status:
  • data.run_status is one of queued | running | completed | failed | cancelled
Or set up a webhook in Settings > Webhooks and skip polling entirely.

Error envelope

error.code is stable — branch on it. error.message is human-readable and can change between releases, so log it for debugging but don’t pattern-match on the text. error.details is optional structured context (e.g. {balance, required} for insufficient_credits). The request_id also comes back as the X-Request-ID response header on every response, success or failure. Include it when you ping support.

Retrying

Transient failures (timeouts, 429, 5xx) are safe to retry. Since failed requests cost 0 credits, a capped backoff loop with jitter costs you nothing beyond the eventual successful call. Don’t retry on 400, 401, 402, 403, 404, or 422 — those won’t change until you fix the request.