> ## Documentation Index
> Fetch the complete documentation index at: https://help.abacusdocs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Ingest items feed

> Keyset-cursor feed of ingest items.

Keyset cursor (`?since=` + `?limit=`), not `?page=`. Auth0 + allowlist. Send `X-Agent-Id` from [Register](/api-reference/ingest/register-agent) — the poll bumps `last_seen_at`. Agents poll this; humans use the organization Black Hole tab.


## OpenAPI

````yaml GET /api/v2/ingest/items/
openapi: 3.1.0
info:
  title: Abacus Docs API
  version: 1.0.0
  description: |
    REST + SSE surface for Abacus Docs on the same host as the web app
    (`https://abacusdocs.com`). Paths are `/api/v2/…`.

    Authenticate with an organisation API key (`sk-abacus-*`) as a Bearer
    token, or an Auth0 access token. Cross-tenant reads return **404**, not
    403. Page-based lists use `?page=` and `?page_size=`.

    Extraction, reprocess, schema tests, and chat turns spend credits.
    See the help centre Credits pages for rates.
  contact:
    name: Abacus Docs
    url: https://abacusdocs.com/contact
servers:
  - url: https://abacusdocs.com
    description: Production (Extract)
security: []
paths:
  /api/v2/ingest/items/:
    get:
      tags:
        - v2/ingest
      summary: Poll the ingest items feed (keyset cursor)
      description: >-
        Returns the caller org's items strictly after the ``since`` cursor in
        ``(updated_at, id)`` order (CONTRACT §2.4). At-least-once: an item whose
        state changes replays under the next poll — the agent upserts by
        ``item_id``. An empty page echoes the request cursor unchanged. Requires
        ``ingest:read`` and a registered ``X-Agent-Id`` (the poll bumps its
        ``last_seen_at``).
      operationId: v2_ingest_items_retrieve
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
          description: Page size, clamped to [1, 500]. Defaults to 200.
        - in: query
          name: since
          schema:
            type: string
          description: Opaque cursor from a previous poll; absent = from the beginning.
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ItemsFeedResponse'
          description: ''
        '400':
          content:
            application/json:
              schema:
                type: object
                additionalProperties: {}
          description: ''
        '403':
          content:
            application/json:
              schema:
                type: object
                additionalProperties: {}
          description: ''
        '404':
          content:
            application/json:
              schema:
                type: object
                additionalProperties: {}
          description: ''
        '426':
          content:
            application/json:
              schema:
                type: object
                additionalProperties: {}
          description: ''
      security:
        - bearerAuth: []
components:
  schemas:
    ItemsFeedResponse:
      type: object
      description: 200 body of GET /api/v2/ingest/items/ (CONTRACT §2.4).
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/IngestItem'
        next_cursor:
          type: string
          description: Opaque keyset cursor; empty items + same cursor = no changes.
      required:
        - items
        - next_cursor
    IngestItem:
      type: object
      description: |-
        One feed item in the CONTRACT §2.4 shape.

        Instances must be loaded with ``select_related("struct_version__struct",
        "batch")`` (the feed service does) — the nested projections traverse
        those FKs without lazy loads.
      properties:
        item_id:
          type: string
        sha256:
          type: string
        filename:
          type: string
        folder_path:
          type: string
        state:
          type: string
        struct:
          type: object
          additionalProperties: {}
          description: '``{slug, name, version}`` of the pinned target; null pre-CLASSIFIED.'
          readOnly: true
        batch:
          type: object
          additionalProperties: {}
          description: >-
            ``{id, name, web_url}`` of the assigned batch; null pre-ASSIGNED.


            ``id`` is the integer Batch PK (contract clarification #2);
            ``web_url``

            is the navigation affordance. ``reverse()`` gets the explicit

            ``urlconf`` because the ``documents:`` namespace lives only in

            ``documents.urls_root`` — api_v2 requests resolve against

            ``slides.urls`` (the CLAUDE.md redirect gotcha; same fix as

            ``batch_serialization.build_row``). The language is pinned: batch

            pages live inside ``i18n_patterns``, so an unpinned reverse would

            emit a locale-prefixed path (``/ar/batch/N/``) whenever the agent's

            request carries an ``Accept-Language`` — an unstable wire shape.
          readOnly: true
        processing:
          type: object
          additionalProperties: {}
          description: >-
            Lifecycle telemetry for the item's backing document (CONTRACT §2.4).


            Non-null once the item has a backing ``ChatAttachment`` — the

            assignment phase sets the ``attachment`` FK at the
            CLASSIFIED→ASSIGNED

            claim, so ``processing`` is null for every pre-assignment state and

            first appears at ASSIGNED. The block reports the item LIFECYCLE, not

            extraction quality (partial / schema_mismatch stays out of the v1
            feed

            — contract clarification #9):

                ASSIGNED   → "pending"     (queued; worker hasn't claimed it yet)
                PROCESSING → "processing"
                PROCESSED  → "processed"
                FAILED     → "failed"
                (any other state that defensively has an attachment) → "pending"

            ``error_count`` is 1 iff the outcome is failed (exactly one document

            per item), else 0. Derived from ``item.state`` alone — no extra
            queries

            (do NOT join ``BatchDocument`` here; the feed only
            ``select_related``s

            the struct/batch projections).


            Args:
                item: The feed row; only ``attachment_id`` + ``state`` are read.

            Returns:
                ``{"status": <str>, "error_count": <int>}`` when a backing document
                exists, else ``None``.
          readOnly: true
        updated_at:
          type: string
          format: date-time
      required:
        - batch
        - filename
        - folder_path
        - item_id
        - processing
        - sha256
        - state
        - struct
        - updated_at
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >
        Auth0 access token for a signed-in user. Send `Authorization: Bearer
        <token>`.

        The SPA and desktop agent use this. Prefer an organisation API key for

        server-to-server integrations.

````