# Page through a job's finished items (JSONL)

> Streams one page of FINISHED items (status succeeded, failed, cancelled or skipped) in `seq` order as JSON Lines: one `BatchResultLine` object per line, each terminated by `\n`, no array, no trailing pagination object.

Source: https://docs.spicrawl.com/api-reference/batch/batch-results

## GET /v1/batch/{batchID}/results

Operation ID: `batchResults`. API key scope: `batch`.

Streams one page of FINISHED items (status succeeded, failed, cancelled or skipped) in `seq` order as JSON Lines:
one `BatchResultLine` object per line, each terminated by `\n`, no array, no trailing pagination object.
Works while the job is still running (returns what has finished so far). Unfinished items are never listed.

Pagination is in headers: if `X-Next-Cursor` is present, request again with `cursor=<that value>` (the `Link`
header already contains that URL with your `limit` and `status` preserved). No `X-Next-Cursor` means this page
is the last one right now; on a running job, later calls may return more items after the last `seq` you saw.
A stream that ends early without the header you expected on a full page indicates a dropped connection — retry
the same cursor.

Bodies: `result.content` is inlined from the job's stitched result file, which exists only after the job is
terminal, so `result` is absent on a running job. Caps: 524,288 bytes (512 KiB) per item and 2,097,152 bytes
(2 MiB) per page; see `BatchResultBody` for `truncated` / `omitted` / `unavailable`.

Retention: results are readable until `results_expire_at` (72 h after submission by default). After that this
endpoint answers 410 `ERR::REQUEST::BEYOND_RETENTION` even if rows still exist; the job object itself stays
readable via GET /v1/batch/{batchID}.

### Example

```bash
curl "https://api.spicrawl.com/v1/batch/{batchID}/results" \
  -H "Authorization: Bearer $SPICRAWL_API_KEY"
```

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `batchID` | path | string | yes | The job `id` (26-character ULID as returned). The 32/36-character UUID form of the same id is also accepted. A malformed id and another tenant's id both answer 404. |
| `limit` | query | integer; default `500` | no | Result lines per page. Out-of-range or non-integer values are a 400. If you see `omitted` bodies, lower this. |
| `cursor` | query | string | no | Opaque token from the previous page's `X-Next-Cursor` header (hex-encoded). Pass it back verbatim; omit to start from item 0. |
| `status` | query | string: `succeeded`, `failed`, `cancelled`, `skipped` | no | Return only items with this terminal status (case-insensitive), e.g. `failed` to list only failures. `queued` and `running` are rejected with 400. |

### Responses

#### 200

One page of result lines.

Headers: `X-Next-Cursor`, `Link`, `Cache-Control`, `X-Request-Id`.

`application/x-ndjson`.

| Field | Type | Required | Description |
|---|---|---|---|
| `seq` | integer (int64) | yes | 0-based position in submission order. Stable; use it to resume and for GET .../tasks/{seq}/content. |
| `external_id` | string | no | The `external_id` you supplied for this item. Absent when none. |
| `url` | string | yes | The target URL as submitted. |
| `status` | string: `succeeded`, `failed`, `cancelled`, `skipped` | yes | Terminal item status on a result line. `succeeded` is the only status that carries credits or a body. Items still `queued`/`running` are never returned. |
| `attempts` | integer | yes | How many times this item was tried (not reset by retry). |
| `http_status` | integer | no | The TARGET site's HTTP status, not the API's. Absent when no response was received. A 404 here is a successful scrape of a missing page. |
| `request_id` | string | no | Id of the underlying request; look it up in request history for diagnostics. |
| `credits_micro` | integer (int64) | yes | Micro-credits charged for this item (1 credit = 1,000,000). Always 0 unless `status` is `succeeded`; billable target statuses are 200, 404 and 410. |
| `bytes` | integer (int64) | yes | Response size recorded for this item. |
| `duration_ms` | integer (int64) | yes | Wall-clock time spent on the item's final attempt, in ms. 0 when not recorded. |
| `result_ref` | string | no | Storage pointer for a per-item payload. Currently never populated (the worker writes one file per job); ignore it. |
| `result` | object (`BatchResultBody`) | no | The item's payload read from the job's stitched result file. Bounded: 524,288 bytes per item, 2,097,152 bytes per page, spent in `seq` order. Exactly one of these applies: full `content`; `truncated` (a prefix); `omitted` (no content, page budget spent); `unavailable` (the file could not be read). |
| `result_url` | string (uri) | no | Pre-signed download link (valid 2 h) for this item's payload. Only present when `result_ref` is, so currently absent. |
| `error` | object (`BatchResultError`) | no | Why an item did not succeed. Same code catalogue as /v1/scrape errors. |
| `finished_at` | string (date-time) | no | When the item reached its terminal status. |

```json
{"seq":0,"url":"https://example.com/1","status":"succeeded","attempts":1,"http_status":200,"request_id":"01J9Z7A1B2C3D4E5F6G7H8J9KA","credits_micro":0,"bytes":326000,"duration_ms":1840,"result":{"content":"\"<!doctype html><html>…\"","bytes":326002},"finished_at":"2026-09-22T06:41:14Z"}
{"seq":1,"external_id":"sku-42","url":"https://example.com/2","status":"failed","attempts":3,"credits_micro":0,"bytes":0,"duration_ms":15000,"error":{"code":"ERR::UPSTREAM::TIMEOUT","retryable":true},"finished_at":"2026-09-22T06:42:01Z"}

```

#### 400

The request is invalid. Not retryable; fix the request using `code` and `diagnostics.hint`.

Headers: `X-Request-Id`.

`application/problem+json` (`Problem` schema).

#### 401

Missing, invalid, revoked or expired key. Not retryable with the same key.

Headers: `X-Request-Id`.

`application/problem+json` (`Problem` schema).

#### 403

The key lacks the scope this route requires (`ERR::AUTH::INSUFFICIENT_SCOPE`), or the action is not permitted.

Headers: `X-Request-Id`.

`application/problem+json` (`Problem` schema).

#### 404

No such resource for this key's organization.

Headers: `X-Request-Id`.

`application/problem+json` (`Problem` schema).

#### 410

The resource existed but is past its retention window or was purged. Not retryable.

Headers: `X-Request-Id`.

`application/problem+json` (`Problem` schema).

#### 500

Internal error. Retryable when `retryable` is true.

Headers: `X-Request-Id`.

`application/problem+json` (`Problem` schema).

Full OpenAPI spec: https://docs.spicrawl.com/openapi.yaml
