> ## 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.

# Upload and add files

> Upload files into an existing batch and extract.

Multipart upload into an existing batch, then enqueue extraction. Completing those files is a **batch page charge**. Same 64 MB / format rules as the library.


## OpenAPI

````yaml POST /api/v2/batches/{batch_id}/upload-and-add/
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/batches/{batch_id}/upload-and-add/:
    post:
      tags:
        - v2/batches
      summary: Upload new files and attach them to a batch in one round trip
      description: >-
        Multipart endpoint that combines doc upload with add-to-batch. Accepts
        new PDF files via the ``files`` field, stores each as a library
        ``ChatAttachment``, then attaches all of them to the batch with the same
        per-doc version pinning semantics as ``POST
        /api/v2/batches/{batch_id}/documents/``.


        Form fields:

        - ``files``: one or more PDF uploads. Files exceeding  
        ``MAX_UPLOAD_SIZE`` or with a non-PDF extension are skipped   with a
        structured reason.

        - ``version_number`` (optional integer): request-level   override
        applied to every uploaded file unless a per-file   override is set.

        - ``per_doc_versions`` (optional JSON string):   ``{file_index:
        version_number}`` map keyed by zero-based   file index (the ordering the
        user uploaded the files in).   Per-file beats request-level beats batch
        default.


        Validation:

        - Batch must already have a ``default_struct_version`` (no-schema  
        batches return 400).

        - Every override version must belong to the batch's parent   struct and
        be non-deprecated (400 otherwise).

        - Credit-estimation gate runs *before* file storage; insufficient  
        credits return 402 and no docs are persisted.


        Side effects: same as ``BatchAddDocumentsView`` — bumps
        ``Batch.total_files``, transitions COMPLETED/FAILED back to PROCESSING,
        enqueues ``process_batch``, increments the ``extract.batch.docs_added``
        counter.
      operationId: v2_batches_upload_and_add_create
      parameters:
        - in: path
          name: batch_id
          schema:
            type: integer
          required: true
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              additionalProperties: {}
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchUploadAndAddResponse'
          description: ''
        '400':
          content:
            application/json:
              schema:
                type: object
                additionalProperties: {}
          description: ''
        '402':
          content:
            application/json:
              schema:
                type: object
                additionalProperties: {}
          description: ''
        '404':
          content:
            application/json:
              schema:
                type: object
                additionalProperties: {}
          description: ''
      security:
        - extractApiKey: []
        - bearerAuth: []
        - cookieAuth: []
components:
  schemas:
    BatchUploadAndAddResponse:
      type: object
      description: |-
        Response shape for POST /api/v2/batches/{batch_id}/upload-and-add/.

        Combines two skip arrays:
        - ``skipped`` — docs that uploaded successfully but couldn't attach to
          the batch (already in batch, etc.). Empty in the common path.
        - ``upload_skipped`` — files rejected before upload (wrong type, too
          large). Lets the SPA surface partial-upload feedback inline without
          a separate error toast.
      properties:
        added:
          type: array
          items:
            $ref: '#/components/schemas/BatchDocumentSummary'
        skipped:
          type: array
          items:
            $ref: '#/components/schemas/BatchAddDocumentsSkipped'
        upload_skipped:
          type: array
          items:
            $ref: '#/components/schemas/BatchUploadSkipped'
      required:
        - added
        - skipped
        - upload_skipped
    BatchDocumentSummary:
      type: object
      description: >-
        Compact per-doc projection for the add-documents response.


        Mirrors the relevant slice of ``BatchDocument`` plus the

        ``StructVersion.version_number`` so the SPA can render the row without a

        follow-up GET.


        The three ``*_field_count`` / ``extraction_status`` fields surface

        extraction quality so abacus-spa renders the same colored status chip

        + "Show problems only" filter that the legacy HTML grid does (Task 3 of

        the extraction-failure-surfacing plan). They are ``None`` / ``0`` for

        freshly-added docs (PENDING — the worker hasn't classified them yet)

        and populated for COMPLETED docs whose
        ``ChatAttachment.extraction_result``

        has been written.
      properties:
        id:
          type: integer
        document_id:
          type: integer
          description: ChatAttachment.pk for the library doc backing this row.
        original_filename:
          type: string
        status:
          type: string
        processing_order:
          type: integer
        version_number:
          type:
            - integer
            - 'null'
          description: The pinned StructVersion.version_number for this doc's extraction.
        extraction_status:
          type:
            - string
            - 'null'
          description: >-
            Mirrors ``lynx_core.attachments.schema.ExtractionStatus`` — one of
            ``success`` / ``partial`` / ``schema_mismatch`` / ``ocr_failed``.
            ``null`` for PENDING / PROCESSING docs whose extraction has not
            produced a payload yet (clients fall back to ``status`` for the
            lifecycle pill in that case).
        extracted_field_count:
          type: integer
          minimum: 0
          description: >-
            Number of populated fields in the doc's
            ``ChatAttachment.extraction_result.fields``. ``0`` for PENDING /
            PROCESSING docs.
        expected_field_count:
          type:
            - integer
            - 'null'
          minimum: 0
          description: >-
            Number of fields declared by the doc's pinned
            ``StructVersion.schema_json.fields``. ``null`` for
            transcription-only docs (no schema attached).
      required:
        - document_id
        - id
        - original_filename
        - processing_order
        - status
        - version_number
    BatchAddDocumentsSkipped:
      type: object
      description: |-
        Per-doc skip record for the add-documents response.

        Surfaced inline (no failure) so the SPA can render a partial-success
        toast: "Added 3 docs, skipped 2 (1 not owned, 1 already in batch)".

        Possible ``reason`` values:
        - ``"already in batch"`` — the doc is already attached to this batch.
        - ``"already in another batch"`` — the doc is attached to a different
          batch. ``ChatAttachment.extraction_result`` is singular, so a doc
          cannot live in two batches simultaneously.
        - ``"not found or not owned"`` — the doc id doesn't belong to the
          requesting user (or the underlying chat is soft-deleted).
        - ``"duplicate content"`` — another attachment with the same SHA-256
          bytes is already in this batch (AD-421). ``duplicate_of`` is the
          existing ``BatchDocument.pk`` so the grid can flash that row.
      properties:
        document_id:
          type: integer
        reason:
          type: string
        duplicate_of:
          type:
            - integer
            - 'null'
          description: >-
            BatchDocument pk of the existing row when reason is duplicate
            content.
      required:
        - document_id
        - reason
    BatchUploadSkipped:
      type: object
      description: |-
        Per-file skip record for the upload-and-add response.

        Distinct from ``BatchAddDocumentsSkippedSerializer`` — this one keys
        on the user-supplied ``name`` (filename) rather than a server-side
        document id, because the upload-and-add path skips files *before*
        they're persisted (file rejected for size/type → never assigned an id).

        ``duplicate_content`` / ``duplicate_content_in_request`` reasons carry
        optional ``duplicate_of`` (existing ``BatchDocument.pk``) for UI flash.
      properties:
        name:
          type: string
        reason:
          type: string
        duplicate_of:
          type:
            - integer
            - 'null'
      required:
        - name
        - reason
  securitySchemes:
    extractApiKey:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: |
        Organisation API key from Settings → API / `/api/keys/`. The secret is
        shown once. Send `Authorization: Bearer sk-abacus-…`. One active key
        per organisation. Ingest (`/api/v2/ingest/*`) refuses API keys — that
        surface is Auth0-only with `ingest:read` / `ingest:write` scopes.
    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.
    cookieAuth:
      type: apiKey
      in: cookie
      name: sessionid
      description: |
        Django session cookie from a browser login. Present so the web app can
        call `/api/v2` without a header. Do not rely on this for integrations.

````