Get scrape result
Always returns 200. Check the status discriminator for the scrape state.
For AI agents: https://bytekit.com/llms.txt — every docs page is available as markdown by appending .md
Authorization
AuthorizationRequiredBearer <token>API key. Manage keys from the dashboard at app.bytekit.com.
In: header
Path Parameters
idRequiredstring"^sc_"Scrape state (queued, success, or failed).
POST /v1/scrapeScrape a URL
Fetches and processes a URL, returning content in one or more formats wrapped in a ScrapeEnvelope. **The response shape is decided by your request, not by how we route it.** In precedence order: 1. `async: true` in the body, or `?async=true` in the query string (either one is enough; `false` in one never cancels `true` in the other) → **202** with a `ScrapeQueuedEnvelope` receipt, returned immediately. Poll `GET /v1/scrape/{id}` for the result. 2. Otherwise, a request that needs a full page render — today that is `delay_ms > 0` — is **held open** until the render reaches a terminal state, then returns **200** with the content envelope (`success` or `failed`). If the render is still running when the hold window expires (28 seconds by default), the request returns the **202** receipt instead and the job continues in the background. 3. Otherwise the request is served by the fast HTTP path and returns **200** with the content envelope, typically in under a second. **A failed fetch is a result, not a transport error.** An unreachable host, a refused connection, or a target whose bot protection defeats every proxy tier all come back on the same **200** as a `failed` envelope — never a 5xx. Narrow on the envelope's `status` before reading the body. A failure the fast path decides inline is recorded as a failed job, so its envelope normally carries that job's `id` and `GET /v1/scrape/{id}` returns the same envelope; if recording the failed job itself fails, the envelope still arrives on the same 200 with `id` set to `null` and there is nothing to poll. A failure a held render reaches carries that render's real `id`. Either way the request transfers no billable bytes and is charged nothing. **A target that rate-limits us is not ByteKit rate-limiting you.** The `failed` envelope's `error.code` is `target_rate_limited` when the target rate-limits the request. On the fast path that means our proxy attempts end on an HTTP 429 and no bot protection is identified on any attempt, even if the browser fallback then fails or reaches only an unidentified block page; the inline failure carries `error.retry_after_seconds` when the target sent a usable `Retry-After`. On a held render the browser's own answer decides: it observes a 429 on the page itself, or it fails outright after the proxy attempts ended on an HTTP 429. A bot-protection verdict on an earlier fetch attempt does not keep a held render `blocked` when the browser then observes the 429. A held render never carries `error.retry_after_seconds`. Retrying a `target_rate_limited` failure later is likely to work. A held render whose browser reaches an unidentified 403 or 503 page after a proxy 429 is `blocked`. A 429 that bot protection sends as a block stays `blocked` when it is the answer that decides the code: the fast path's proxy attempts, or the browser render on a held render. Your own per-key limit is a separate case: it answers HTTP 429 `rate_limited` with a `Retry-After` header (see the `429` response). So a 202 means one of exactly two things: you asked for one with `async: true` or `?async=true`, or the hold window expired. `wait_for_selector` and `wait_until` are `ScreenshotRequest` options, not `ScrapeRequest` options — sending either to this endpoint returns `422 validation_error`, so neither can produce a 202 here.
Scrape Bulk
Scrape Bulk API — endpoints, requests, and responses.