# Bulk

Functions for interacting with the `bulk` API resource. Each operation ships in four forms — `sync` / `sync_detailed` and their `async` counterparts.

## Create Bulk

Create mixed bulk job

Enqueues screenshots or scrapes in a single batch.
Provide `urls` (simple list) or `items` (per-item config), not both.
Webhook deliveries include `X-ByteKit-Event: bulk.completed` so
receivers can dispatch on the header without parsing the body shape.
The body follows `BulkCompletedWebhook`: at most 500 complete items and
1 MiB, followed by cursor recovery through the existing mixed-item route
whenever `recovery_required` is true.

**Limits:** request bodies are guarded in a two-tier shape. A streamed body
with no `Content-Length` header is capped at 4 MB before your API key
is checked; a body that declares `Content-Length` is instead bounded
there by a 32 MB fail-safe (not the real limit — see below), since
that branch reads nothing until your key is verified. Once your key
is verified, a second check re-gates the body against your account's
own `max_bulk_size` (at this endpoint's richer per-item override
budget), which reaches 28.7 MB for the largest documented account. A
standard-plan account's effective ceiling is 4 MB; either tier's
overrun returns a `400` with error code `request_too_large`.

**Parameters**

- `body (CreateBulkBody): Provide `urls` (simple list) OR `items` (per-item config), not`
- `both. `type` selects the capture kind per entry — only`
- ``screenshot` and `scrape` are accepted (`recording` is`
- `deactivated). Behaviour fields may be set per item or via`
- ``defaults`; a per-item value overrides the matching default.`
- `This includes `type`: an entry with no `type` of its own`
- `inherits `defaults.type`, falling back to `screenshot` only`
- `when neither is set.`
- `The top-level object, each `items[]` entry, and `defaults``
- `each reject unrecognized keys with a 422 `validation_error``
- `(the offending key name appears in the error's `details``
- `message text) rather than silently discarding them.`
- ``items[]` and `defaults` do not share one field set`
- ``device_scale_factor` is accepted only under `defaults` (a`
- `per-item value 422s), and `duration`/`scenario` are accepted`
- `only per item (a `defaults` value 422s). `metadata` is`
- `different from those three: it is valid both at the top`
- `level of the body and per item, but rejected with a 422 if`
- `placed under `defaults`. See the `422` response below.`
- `webhook_url` — `str` — Webhook endpoint URL (HTTPS only; http:// rejected with 400 invalid_url, reason scheme_unsupported)
- `urls` — `list[str] | Unset` — Simple URL list. Provide urls OR items, not both.
- `items` — `list[CreateBulkBodyItemsItem] | Unset` — Per-item configs. Provide urls OR items, not both.
- `defaults` — `CreateBulkBodyDefaults | Unset` — Default behaviour applied to every entry unless the item overrides it.
- `webhook_secret` — `str | Unset` — HMAC secret used to sign webhook deliveries. Each delivery carries `X-Webhook- Timestamp` and `X-Webhook-Signature` (`sha256=` + the hex HMAC-SHA256 of `timestamp.body`). See https://bytekit.com/docs/guides/webhooks for verification code.
- `metadata` — `Metadata | Unset` — Arbitrary key-value metadata. Max 4096 bytes when JSON-serialized.

**Call styles**

```python
sync_detailed(client=client, body=body) -> Response[CreateBulkResponse202 | CreateBulkResponse403 | Error]
sync(client=client, body=body) -> CreateBulkResponse202 | CreateBulkResponse403 | Error | None
await asyncio_detailed(client=client, body=body) -> Response[CreateBulkResponse202 | CreateBulkResponse403 | Error]
await asyncio(client=client, body=body) -> CreateBulkResponse202 | CreateBulkResponse403 | Error | None
```

## Delete Bulk

Cancel a bulk job

Cancels an in-flight bulk job: drains its still-queued (pending) child jobs and refunds the quota
held for exactly those drained children. Children already completed, failed, or in progress are
untouched and keep their charge. Idempotent — cancelling an already-terminal job (completed, failed,
or cancelled) is a no-op that returns 200 with the job's real, unchanged status and refunds nothing.

**Parameters**

- `id (str)`

**Call styles**

```python
sync_detailed(id=id, client=client) -> Response[DeleteBulkResponse200 | Error]
sync(id=id, client=client) -> DeleteBulkResponse200 | Error | None
await asyncio_detailed(id=id, client=client) -> Response[DeleteBulkResponse200 | Error]
await asyncio(id=id, client=client) -> DeleteBulkResponse200 | Error | None
```

## Get Bulk

Get bulk job status

Returns the job envelope. Items are listed by `GET /v1/bulk/\{id\}/screenshots`.

**Cancelled by the platform.** When the account can no longer spend (its bandwidth
balance is exhausted, or the account is deleted) while items are still pending, ByteKit
cancels the job itself. It then reads `status: cancelled` even though you never called
`DELETE /v1/bulk/\{id\}`, and the cancellation sends no webhook.

**Job ids from other create routes.** The job is looked up by account only, so the id
of a job created by `POST /v1/scrape/bulk` or `POST /v1/fetch/bulk` is found here too, not
a `404`, with that job's envelope. An id from another account answers `404`.

**Parameters**

- `id (str)`

**Call styles**

```python
sync_detailed(id=id, client=client) -> Response[Error | GetBulkResponse200 | GetBulkResponse202]
sync(id=id, client=client) -> Error | GetBulkResponse200 | GetBulkResponse202 | None
await asyncio_detailed(id=id, client=client) -> Response[Error | GetBulkResponse200 | GetBulkResponse202]
await asyncio(id=id, client=client) -> Error | GetBulkResponse200 | GetBulkResponse202 | None
```

## List Bulk Screenshots

List bulk job items

Returns the bulk job's items, discriminated by `type`.

**Downloading item artifacts.** For a `completed` item with a storage key,
`image_url` (`type: screenshot`) and both `content_url` and `markdown_url`
(`type: scrape`) are **pre-signed** URLs, minted fresh on every read of
this endpoint and valid for **600 seconds (10 minutes)** —
the same signing lifetime as `content_url` on `GET /v1/fetch/bulk/\{id\}`.
Download inside that window, or re-poll this endpoint for a fresh
signature.

**When no signature is minted.** Three cases mint none: an item that is not
`completed`, a `completed` item with no storage key, and a `completed` item
whose object could not be signed — during an object-storage outage, for
instance. In all three the item's stored value is returned unchanged: `null`
while nothing has been stored, and otherwise a **private, unsigned**
object-storage URL that answers `401`. These fields are neither nulled nor
flagged in that case, so treat an artifact URL as usable only once you have
downloaded it, and re-poll for a fresh signature otherwise.
`artifact_warnings` and its `artifact_unavailable` code describe the sibling
`artifacts` object on `type: scrape` items only — they say nothing about
`image_url`, `content_url`, or `markdown_url`.

**Parameters**

- `id (str)`
- `cursor (str | Unset)`
- `limit (int | Unset)`
- `status (ListBulkScreenshotsStatus | Unset)`

**Call styles**

```python
sync_detailed(id=id, client=client, cursor=cursor, limit=limit, status=status) -> Response[Error | ListBulkScreenshotsResponse200]
sync(id=id, client=client, cursor=cursor, limit=limit, status=status) -> Error | ListBulkScreenshotsResponse200 | None
await asyncio_detailed(id=id, client=client, cursor=cursor, limit=limit, status=status) -> Response[Error | ListBulkScreenshotsResponse200]
await asyncio(id=id, client=client, cursor=cursor, limit=limit, status=status) -> Error | ListBulkScreenshotsResponse200 | None
```