{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/spicrawl-com/json-schema/spicrawl-com-batch-result-line-schema.json",
  "title": "BatchResultLine",
  "description": "One line of the JSONL results stream — one finished item. Same field names as the worker's stitched result file.",
  "x-generated": "2026-10-31",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/spicrawl-com-openapi.yaml#/components/schemas/BatchResultLine",
  "type": "object",
  "required": [
    "seq",
    "url",
    "status",
    "attempts",
    "credits_micro",
    "bytes",
    "duration_ms"
  ],
  "properties": {
    "seq": {
      "type": "integer",
      "format": "int64",
      "minimum": 0,
      "description": "0-based position in submission order. Stable; use it to resume and for GET .../tasks/{seq}/content."
    },
    "external_id": {
      "type": "string",
      "description": "The `external_id` you supplied for this item. Absent when none."
    },
    "url": {
      "type": "string",
      "description": "The target URL as submitted."
    },
    "status": {
      "$ref": "#/$defs/BatchItemStatus",
      "description": "The item's terminal status. Only `succeeded` carries a body and credits; for `failed`, read `error`."
    },
    "attempts": {
      "type": "integer",
      "minimum": 0,
      "description": "How many times this item was tried (not reset by retry)."
    },
    "http_status": {
      "type": "integer",
      "description": "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": {
      "type": "string",
      "description": "Id of the underlying request; look it up in request history for diagnostics."
    },
    "credits_micro": {
      "type": "integer",
      "format": "int64",
      "minimum": 0,
      "description": "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": {
      "type": "integer",
      "format": "int64",
      "minimum": 0,
      "description": "Response size recorded for this item."
    },
    "duration_ms": {
      "type": "integer",
      "format": "int64",
      "minimum": 0,
      "description": "Wall-clock time spent on the item's final attempt, in ms. 0 when not recorded."
    },
    "warnings": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Warning codes for this item, e.g. `EMPTY_RENDER` (the page rendered empty, so the item was charged 0 credits). Omitted when there are none."
    },
    "result_ref": {
      "type": "string",
      "description": "Storage pointer for a per-item payload. Currently never populated (the worker writes one file per job); ignore it."
    },
    "result": {
      "$ref": "#/$defs/BatchResultBody",
      "description": "The payload inline. Absent while the job is still running (no stitched file yet), when the deployment has no object storage, or when this item has no stored payload (typically non-succeeded items)."
    },
    "result_url": {
      "type": "string",
      "format": "uri",
      "description": "Pre-signed download link (valid 2 h) for this item's payload. Only present when `result_ref` is, so currently absent."
    },
    "error": {
      "$ref": "#/$defs/BatchResultError",
      "description": "Present when the item failed and a machine code was recorded. Absent on success."
    },
    "finished_at": {
      "type": "string",
      "format": "date-time",
      "description": "When the item reached its terminal status."
    }
  },
  "$defs": {
    "BatchItemStatus": {
      "type": "string",
      "enum": [
        "succeeded",
        "failed",
        "cancelled",
        "skipped"
      ],
      "description": "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."
    },
    "BatchResultBody": {
      "type": "object",
      "description": "The item's payload read from the job's stitched result file. Bounded: 524,288 bytes per item, 2,097,152 bytes per\npage, spent in `seq` order. Exactly one of these applies: full `content`; `truncated` (a prefix); `omitted`\n(no content, page budget spent); `unavailable` (the file could not be read).\n",
      "required": [
        "bytes"
      ],
      "properties": {
        "content": {
          "type": "string",
          "description": "The document as plain text, in the item's `response_format` (the HTML, markdown or text itself). It is the line's ordinary JSON string, already decoded by your JSON parser once, so do not decode it again. For `response_format` `json` it is the JSON document as a string (the body as retrieved), which you parse to get the object. May be a byte prefix when `truncated`."
        },
        "bytes": {
          "type": "integer",
          "format": "int64",
          "minimum": 0,
          "description": "Full size of the payload in bytes, before any truncation; equal to the length of `content` unless `truncated`. 0 when `unavailable`."
        },
        "truncated": {
          "type": "boolean",
          "description": "True when `content` is only a prefix: the payload exceeded 512 KiB, or the page's remaining 2 MiB budget. Request this seq alone (cursor just before it, limit=1) to get up to 512 KiB."
        },
        "omitted": {
          "type": "boolean",
          "description": "True when no content was included because the page's 2 MiB budget was already spent. Request a smaller `limit`, or resume with a cursor just before this seq."
        },
        "unavailable": {
          "type": "string",
          "description": "Why the result file could not be read at all (the same reason is set on every line of the page). Report it or retry later; not a problem with the item itself."
        }
      }
    },
    "BatchResultError": {
      "type": "object",
      "description": "Why an item did not succeed. Same code catalogue as /v1/scrape errors.",
      "required": [
        "code",
        "retryable"
      ],
      "properties": {
        "code": {
          "type": "string",
          "description": "Stable machine code, e.g. ERR::UPSTREAM::TIMEOUT or ERR::PROXY::EXHAUSTED. Switch on this, not on text."
        },
        "retryable": {
          "type": "boolean",
          "description": "True when running the item again may succeed. Use POST /v1/batch/{batchID}/retry to re-run all failed items."
        }
      }
    }
  }
}