openapi: 3.1.0
info:
  title: ImageToTable.ai API
  version: "1.0.0"
  summary: >-
    REST API for extracting structured data (tables and fields) from documents
    and images — invoices, receipts, bank statements, purchase orders, and more.
  description: >-
    Every endpoint requires `Authorization: Bearer <api_key>` — session
    cookies are never accepted, even if the browser making the request is
    already logged in as a paid account. The API is only available on Basic
    plan and above (or an active Team on Growth+); a Free-plan key returns
    `403 plan_required`.


    Human-readable docs (with narrative Guides on the async model, webhooks,
    idempotency, pagination, errors, rate limits, bbox annotation, and how
    account-level settings affect API behavior) live at
    https://imagetotable.ai/developers/ — this spec mirrors the "API
    Reference" section of that site field-for-field and is meant to be
    consumed by coding assistants / codegen tooling, not read top-to-bottom
    by a human.


    **Status enum**: every resource in this API reports one of exactly five
    public lifecycle values — `queued`, `processing`, `succeeded`, `failed`,
    `canceled` — regardless of what the equivalent field is called internally.


    **No official SDK.** Every example in the human-facing docs is a raw HTTP
    call (curl / Python `requests` / JavaScript `fetch`), not a packaged
    client library — this spec is the intended path for anyone who wants a
    generated client instead of hand-written HTTP calls.
  contact:
    name: ImageToTable.ai
    url: https://imagetotable.ai/developers/
  license:
    name: Proprietary
servers:
  - url: https://imagetotable.ai/api/v1
    description: Production

security:
  - bearerAuth: []

tags:
  - name: Documents
    description: Upload, status, image/crop retrieval, and bbox annotation for individual documents.
  - name: Batches
    description: Start processing, list/filter, poll, fetch reshaped results, export, and delete batches of documents.
  - name: Templates & Fields
    description: Saved extraction field lists (Templates) and their Fields, plus built-in Presets.
  - name: Webhooks
    description: Register a batch-completion callback URL.
  - name: Account
    description: Effective plan/credits snapshot and credit-consumption history.

paths:
  /documents:
    post:
      operationId: createDocument
      tags: [Documents]
      summary: Upload a document
      description: >-
        Uploads a single image or PDF into a batch. Provide either `file`
        (multipart) or `url` — the server fetches it for you — but not both.
        PDFs are split one Document per page (max 30 pages) and the
        response's `document_id` becomes an array instead of a scalar
        string — see the two response schemas below
        (`DocumentCreateResponseImage` / `DocumentCreateResponsePdf`). Does
        not start extraction; call `POST /batches/{batch_name}/process` once
        you're done uploading.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              description: Exactly one of `file` or `url` is required.
              properties:
                file:
                  type: string
                  format: binary
                  description: >-
                    An image (JPEG/PNG/etc.) or a PDF (max 30 pages).
                    Required unless `url` is given.
                url:
                  type: string
                  format: uri
                  description: >-
                    Alternative to `file` — a public http(s) URL the server
                    downloads the file from (max 30MB, 15s read timeout).
                    Mutually exclusive with `file`.
                batch_name:
                  type: string
                  description: >-
                    Batch to add this document to. Omit to auto-generate one
                    (returned in the response).
                template_id:
                  type: integer
                  description: >-
                    A Template ID owned by your account to pre-associate with
                    this document.
                password:
                  type: string
                  description: >-
                    PDF-only. Tried first if the file is password-protected.
                    Falls back to any passwords saved under your account's
                    Email Inbox settings if omitted or if it doesn't unlock
                    the file.
      responses:
        '201':
          description: Document(s) created.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/DocumentCreateResponseImage'
                  - $ref: '#/components/schemas/DocumentCreateResponsePdf'
              examples:
                single_image:
                  summary: Single image upload
                  value:
                    document_id: "8f14e45f-ceea-467e-9de1-a3e9c93a9c95"
                    batch_name: "july-invoices"
                    remaining_batch_capacity: 199
                multi_page_pdf:
                  summary: Multi-page PDF upload
                  value:
                    document_id:
                      - "8f14e45f-ceea-467e-9de1-a3e9c93a9c95_p1"
                      - "8f14e45f-ceea-467e-9de1-a3e9c93a9c95_p2"
                    batch_name: "july-invoices"
                    page_count: 2
                    remaining_batch_capacity: 198
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'

  /documents/{document_id}:
    get:
      operationId: getDocument
      tags: [Documents]
      summary: Get a document
      description: Single-document status and reshaped `line_items`, once extraction has succeeded.
      parameters:
        - $ref: '#/components/parameters/DocumentId'
      responses:
        '200':
          description: The document.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Document'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'

  /documents/{document_id}/image:
    get:
      operationId: getDocumentImage
      tags: [Documents]
      summary: Get a document's image (or a crop of it)
      description: >-
        Returns the raw page image. With `crop`, returns a cropped JPEG of
        that normalized region instead of the full page.
      parameters:
        - $ref: '#/components/parameters/DocumentId'
        - name: crop
          in: query
          required: false
          description: >-
            `"x1,y1,x2,y2"` — four floats between 0 and 1 (normalized to the
            page's own width/height), same convention as every `bbox` object
            in the API.
          schema:
            type: string
            example: "0.10,0.08,0.42,0.30"
      responses:
        '200':
          description: JPEG image bytes.
          content:
            image/jpeg:
              schema:
                type: string
                format: binary
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'

  /documents/{document_id}/bbox:
    post:
      operationId: triggerDocumentBbox
      tags: [Documents]
      summary: Trigger bbox annotation
      description: >-
        Starts the optional, paid, second-pass job that locates where each
        extracted field's value sits on the page. A document's own account
        may also trigger this automatically via the `auto_annotate_bbox`
        account setting — see the Account Settings & API Behavior guide.
      parameters:
        - $ref: '#/components/parameters/DocumentId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: A job for this document was already running — nothing new was started or charged.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BboxTriggerResponse'
        '202':
          description: A new bbox-annotation job was enqueued.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BboxTriggerResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
    get:
      operationId: getDocumentBbox
      tags: [Documents]
      summary: Get bbox annotation
      description: >-
        Polls status and fetches results for the most recent bbox-annotation
        job on this document. `exists: false` (with `status`/`group_batch_id`
        null) means no job has ever been triggered — a normal, common state.
      parameters:
        - $ref: '#/components/parameters/DocumentId'
      responses:
        '200':
          description: Current bbox annotation state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BboxGetResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'

  /batches/{batch_name}/process:
    post:
      operationId: processBatch
      tags: [Batches]
      summary: Start processing a batch
      description: >-
        Starts extraction on every eligible document in the batch. Deducts
        credits. If this batch already has a webhook registered whose
        `fired_at` was claimed by an earlier wave, and this call finds new
        eligible documents, it automatically re-arms the webhook so this
        wave's completion notifies too — see the Webhooks guide.
      parameters:
        - $ref: '#/components/parameters/BatchName'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProcessBatchRequest'
      responses:
        '200':
          description: Processing started.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProcessBatchResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'

  /batches:
    get:
      operationId: listBatches
      tags: [Batches]
      summary: List batches
      description: Paginated, filterable summary list of your batches.
      parameters:
        - name: source
          in: query
          required: false
          schema:
            type: string
            enum: [direct, collect, email_inbox, api, share]
        - name: q
          in: query
          required: false
          description: Case-insensitive substring match against filenames within the batch.
          schema:
            type: string
        - name: date_from
          in: query
          required: false
          schema:
            type: string
            format: date
        - name: date_to
          in: query
          required: false
          schema:
            type: string
            format: date
        - name: status
          in: query
          required: false
          description: Comma-separated list of public statuses.
          schema:
            type: string
            example: "succeeded,failed"
        - name: template_id
          in: query
          required: false
          schema:
            type: integer
        - name: mode
          in: query
          required: false
          description: Only `"table"` is accepted this round.
          schema:
            type: string
            enum: [table]
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/PageToken'
      responses:
        '200':
          description: A page of batch summaries.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '429':
          $ref: '#/components/responses/RateLimited'

  /batches/{batch_name}:
    get:
      operationId: getBatchStatus
      tags: [Batches]
      summary: Get batch status
      description: Lightweight aggregate status — counts per public status plus `all_done`.
      parameters:
        - $ref: '#/components/parameters/BatchName'
      responses:
        '200':
          description: Aggregate batch status.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchStatusResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
    delete:
      operationId: deleteBatch
      tags: [Batches]
      summary: Delete a batch
      description: >-
        Deletes every document in the batch, refunding any still-queued
        documents first. Also removes the batch's webhook registration and
        any bbox-annotation jobs tied to its documents.
      parameters:
        - $ref: '#/components/parameters/BatchName'
      responses:
        '200':
          description: Deletion summary.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchDeleteResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'

  /batches/{batch_name}/results:
    get:
      operationId: getBatchResults
      tags: [Batches]
      summary: Get batch results
      description: >-
        The primary way to retrieve extracted data — every document in the
        batch with its reshaped `line_items`. Pass `include=bbox` to back-fill
        already-computed bbox data (this never triggers a new bbox job).
      parameters:
        - $ref: '#/components/parameters/BatchName'
        - name: include
          in: query
          required: false
          description: Set to `"bbox"` to include bbox data on each field value.
          schema:
            type: string
            enum: [bbox]
      responses:
        '200':
          description: Batch results.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchResultsResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'

  /batches/{batch_name}/export:
    get:
      operationId: exportBatch
      tags: [Batches]
      summary: Export a batch
      description: >-
        Convenience download of the same data `results` returns, as an Excel
        workbook. JSON `results` remains the canonical format — this is
        secondary. v1 only supports table (extract) mode, so there's no docx
        option here — on the main app, Word export is exclusively the
        page_word mode's native output (a different prompt/result shape
        entirely), which v1 doesn't expose.
      parameters:
        - $ref: '#/components/parameters/BatchName'
        - name: format
          in: query
          required: false
          schema:
            type: string
            enum: [xlsx]
            default: xlsx
      responses:
        '200':
          description: File download.
          content:
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                format: binary
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'

  /batches/{batch_name}/webhook:
    put:
      operationId: registerBatchWebhook
      tags: [Webhooks]
      summary: Register a batch webhook
      description: >-
        Creates or updates the completion callback for a batch.
        `webhook_secret` is returned on every successful call (not masked
        after first creation) since there is no separate GET for this
        resource. Updating an existing registration only ever touches
        `callback_url` — the secret is never rotated and `fired_at` is never
        reset by this call itself. See the Webhooks guide for payload shape,
        signature headers, and retry behavior — a registration fires once per
        wave of processing, and is automatically re-armed the next time
        `POST /batches/{batch_name}/process` finds new eligible documents in
        this batch (no need to re-`PUT` this endpoint just to get notified
        about a later wave).
      parameters:
        - $ref: '#/components/parameters/BatchName'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [callback_url]
              properties:
                callback_url:
                  type: string
                  format: uri
                  description: Must be an http:// or https:// URL.
      responses:
        '200':
          description: Webhook registered or updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookRegistration'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '429':
          $ref: '#/components/responses/RateLimited'

  /templates:
    get:
      operationId: listTemplates
      tags: [Templates & Fields]
      summary: List templates
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/PageToken'
      responses:
        '200':
          description: A page of templates, each with its full ordered field list.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TemplateListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      operationId: createTemplate
      tags: [Templates & Fields]
      summary: Create a template
      description: >-
        Two creation modes: from scratch (optionally cloning
        `base_template_id`'s fields), or from a built-in preset via
        `preset_id`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTemplateRequest'
      responses:
        '201':
          description: Template created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  template:
                    $ref: '#/components/schemas/Template'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'

  /templates/{id}:
    delete:
      operationId: deleteTemplate
      tags: [Templates & Fields]
      summary: Delete a template
      description: Deletes a template and all of its fields. Does not affect documents already processed with it.
      parameters:
        - $ref: '#/components/parameters/TemplateId'
      responses:
        '204':
          description: Deleted — no body.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'

  /presets:
    get:
      operationId: listPresets
      tags: [Templates & Fields]
      summary: List presets
      description: Built-in field lists for common document types. Static configuration, not queryable/mutable resources.
      parameters:
        - name: category
          in: query
          required: false
          schema:
            type: string
            example: "Finance & Accounting"
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/PageToken'
      responses:
        '200':
          description: A page of presets.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PresetListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '429':
          $ref: '#/components/responses/RateLimited'

  /templates/{id}/fields:
    get:
      operationId: listFields
      tags: [Templates & Fields]
      summary: List fields
      parameters:
        - $ref: '#/components/parameters/TemplateId'
      responses:
        '200':
          description: All fields on this template, sorted by sort_order.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FieldListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      operationId: createField
      tags: [Templates & Fields]
      summary: Create a field
      parameters:
        - $ref: '#/components/parameters/TemplateId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FieldWriteRequest'
      responses:
        '201':
          description: Field created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  field:
                    $ref: '#/components/schemas/Field'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'

  /templates/{id}/fields/{field_id}:
    put:
      operationId: updateField
      tags: [Templates & Fields]
      summary: Update a field
      description: Full replace, not a partial patch — omitted `format_requirement` is cleared, not left unchanged.
      parameters:
        - $ref: '#/components/parameters/TemplateId'
        - $ref: '#/components/parameters/FieldId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FieldWriteRequest'
      responses:
        '200':
          description: Field updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  field:
                    $ref: '#/components/schemas/Field'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
    delete:
      operationId: deleteField
      tags: [Templates & Fields]
      summary: Delete a field
      parameters:
        - $ref: '#/components/parameters/TemplateId'
        - $ref: '#/components/parameters/FieldId'
      responses:
        '204':
          description: Deleted — no body.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'

  /templates/{id}/fields/order:
    patch:
      operationId: reorderFields
      tags: [Templates & Fields]
      summary: Reorder fields
      description: >-
        Sets explicit field order, which drives output column order in
        `line_items` and exports. IDs not belonging to this template are
        silently ignored.
      parameters:
        - $ref: '#/components/parameters/TemplateId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [field_ids]
              properties:
                field_ids:
                  type: array
                  items:
                    type: integer
                  description: Every field ID on this template, in the desired order.
      responses:
        '200':
          description: Fields in new order.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FieldListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'

  /account:
    get:
      operationId: getAccount
      tags: [Account]
      summary: Get account
      description: >-
        Effective plan/role/credits (team-inherited, if applicable) plus the
        two SDK-throttling numbers `max_batch_size` and `upload_concurrency`.
      responses:
        '200':
          description: Account snapshot.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Account'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '429':
          $ref: '#/components/responses/RateLimited'

  /account/usage:
    get:
      operationId: getAccountUsage
      tags: [Account]
      summary: Get account usage
      description: >-
        Paginated, newest-first ledger of credit-affecting events. Paginates
        newest-first, unlike every other v1 list endpoint (oldest-first) —
        the `page_token` is still opaque and used the same way.
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: batch_name
          in: query
          required: false
          schema:
            type: string
        - $ref: '#/components/parameters/PageToken'
      responses:
        '200':
          description: A page of usage-ledger entries.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UsageListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/PlanRequired'
        '429':
          $ref: '#/components/responses/RateLimited'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: >-
        Send your API key as `Authorization: Bearer <api_key>`. Session
        cookies are never accepted as a fallback, even for an already-logged-in
        browser session. Regenerate your key from Profile → API Key in the
        web app.

  parameters:
    DocumentId:
      name: document_id
      in: path
      required: true
      schema:
        type: string
    BatchName:
      name: batch_name
      in: path
      required: true
      schema:
        type: string
    TemplateId:
      name: id
      in: path
      required: true
      schema:
        type: integer
    FieldId:
      name: field_id
      in: path
      required: true
      schema:
        type: integer
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: >-
        Opt-in safe-retry key for mutating/billable calls. A retried request
        with the same key and the same parameters replays the original
        response instead of re-executing (e.g. re-deducting credits). Reusing
        the same key with different parameters returns
        `400 idempotency_key_reused`. Only meaningful on
        `POST /documents`, `POST /batches/{batch_name}/process`, and
        `POST /documents/{document_id}/bbox`.
      schema:
        type: string
    Limit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    PageToken:
      name: page_token
      in: query
      required: false
      description: Opaque cursor from a previous response's `next_page_token`.
      schema:
        type: string

  responses:
    BadRequest:
      description: >-
        Malformed or invalid request (`invalid_request_error` type — codes
        like `missing_parameter`, `invalid_parameter`,
        `duplicate_field_name`, `idempotency_key_reused`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: >-
        Missing or invalid API key (`authentication_error` type — codes
        `missing_api_key` / `invalid_api_key`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    PlanRequired:
      description: >-
        Valid key, but the account's effective plan is Free
        (`authentication_error` / `plan_required`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    NotFound:
      description: >-
        The referenced document/batch/template doesn't exist or isn't yours
        (`not_found_error` type).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InsufficientCredits:
      description: Not enough available credits for this action (`insufficient_credits` type).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimited:
      description: >-
        Too many requests (`rate_limit_error` / `rate_limit_exceeded`), or a
        business-logic queue limit was hit. See the `X-RateLimit-*` response
        headers.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InternalError:
      description: Unexpected server error (`internal_error` type).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

  schemas:
    Error:
      type: object
      required: [type, code, message, doc_url]
      properties:
        type:
          type: string
          enum:
            - authentication_error
            - invalid_request_error
            - not_found_error
            - insufficient_credits
            - rate_limit_error
            - internal_error
        code:
          type: string
          description: >-
            Stable machine-readable error code, e.g. `missing_api_key`,
            `invalid_parameter`, `document_not_found`. Full list and meaning
            at https://imagetotable.ai/developers/guides/errors.
        message:
          type: string
        doc_url:
          type: string
          format: uri
          description: Deep link to this code's explanation in the Errors guide.
        param:
          type: string
          description: The offending request parameter name, present only for validation errors.
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          $ref: '#/components/schemas/Error'

    Status:
      type: string
      enum: [queued, processing, succeeded, failed, canceled]
      description: The single closed status vocabulary used everywhere in v1.

    Bbox:
      type: object
      description: Normalized 0–1 bounding box. `unit` is always the literal string `"normalized"`.
      required: [x1, y1, x2, y2, unit]
      properties:
        x1: { type: number }
        y1: { type: number }
        x2: { type: number }
        y2: { type: number }
        unit:
          type: string
          enum: [normalized]

    BboxFieldValue:
      type: object
      description: >-
        Shape a field's value takes when `?include=bbox` is set — the
        extracted text plus its (possibly null) location.
      required: [value, bbox]
      properties:
        value:
          description: The extracted value (usually a string).
        bbox:
          oneOf:
            - $ref: '#/components/schemas/Bbox'
            - type: 'null'

    ImageRegionFieldValue:
      type: object
      description: >-
        Shape used for "pure location" fields, where the field's entire
        answer IS a location on the page (e.g. "locate the portrait photo") —
        used unconditionally for such fields regardless of `?include=bbox`.
      required: [type, bbox, image_url]
      properties:
        type:
          type: string
          enum: [image_region]
        bbox:
          $ref: '#/components/schemas/Bbox'
        image_url:
          type: string
          format: uri
          description: Fetchable URL to a pre-cropped JPEG of this region (GET /documents/{id}/image?crop=...).

    LineItemFieldValue:
      description: >-
        A single extracted field's value. Three possible shapes: a bare
        scalar (default, no `?include=bbox`), a `{value, bbox}` object (with
        `?include=bbox`), or an `image_region` object (pure-location fields,
        always, regardless of `?include=bbox`).
      oneOf:
        - type: string
        - type: number
        - type: boolean
        - type: 'null'
        - $ref: '#/components/schemas/BboxFieldValue'
        - $ref: '#/components/schemas/ImageRegionFieldValue'

    LineItem:
      type: object
      description: One extracted row — field name to field value.
      additionalProperties:
        $ref: '#/components/schemas/LineItemFieldValue'

    DocumentCreateResponseImage:
      type: object
      required: [document_id, batch_name, remaining_batch_capacity]
      properties:
        document_id:
          type: string
        batch_name:
          type: string
        remaining_batch_capacity:
          type: integer
          description: >-
            How many more documents this batch can accept before hitting your
            plan's max batch size, after this upload. Saves a separate call to
            GET /account to look up max_batch_size and do your own bookkeeping.

    DocumentCreateResponsePdf:
      type: object
      required: [document_id, batch_name, page_count, remaining_batch_capacity]
      properties:
        document_id:
          type: array
          items:
            type: string
        batch_name:
          type: string
        page_count:
          type: integer
        remaining_batch_capacity:
          type: integer
          description: Same as DocumentCreateResponseImage's field, already accounting for all pages this PDF added.

    Document:
      type: object
      required: [document_id, filename, status, created_at, started_at, completed_at, line_items]
      properties:
        document_id:
          type: string
        batch_name:
          type: string
        filename:
          type: string
        status:
          $ref: '#/components/schemas/Status'
        created_at:
          type: string
          format: date-time
        started_at:
          type: string
          format: date-time
          nullable: true
        completed_at:
          type: string
          format: date-time
          nullable: true
          description: >-
            Set once the document reaches a terminal state (succeeded, failed,
            or canceled). Null while still queued or processing.
        line_items:
          type: array
          items:
            $ref: '#/components/schemas/LineItem'
        bbox_status:
          allOf:
            - $ref: '#/components/schemas/Status'
          nullable: true
          description: Only present when the containing response was fetched with `?include=bbox`.

    BboxTriggerResponse:
      type: object
      required: [document_id, group_batch_id, status, already_running]
      properties:
        document_id:
          type: string
        group_batch_id:
          type: string
        status:
          $ref: '#/components/schemas/Status'
        already_running:
          type: boolean
        row_groups:
          type: integer
          description: Number of row-groups the annotation job was split into. Present on newly-enqueued jobs.

    BboxGetResponse:
      type: object
      required: [document_id, exists, status, group_batch_id, rows]
      properties:
        document_id:
          type: string
        exists:
          type: boolean
          description: False if no bbox job has ever been triggered for this document.
        status:
          allOf:
            - $ref: '#/components/schemas/Status'
          nullable: true
        group_batch_id:
          type: string
          nullable: true
        rows:
          type: object
          description: Row index (as a string) → field name → normalized Bbox or null.
          additionalProperties:
            type: object
            additionalProperties:
              oneOf:
                - $ref: '#/components/schemas/Bbox'
                - type: 'null'

    ProcessBatchRequest:
      type: object
      properties:
        template_id:
          type: integer
          description: Takes priority over `fields` if both are given.
        fields:
          type: array
          description: Ad-hoc field list for this run only. Ignored if `template_id` is given.
          items:
            oneOf:
              - type: string
              - type: object
                properties:
                  name:
                    type: string
                  format_requirement:
                    type: string
        quality:
          type: string
          enum: [fast, high]
          description: Omit to fall back to the account's `thinking_type` setting.
        webhook_url:
          type: string
          format: uri
          description: Registers this batch's completion callback in the same call.

    ProcessBatchResponse:
      type: object
      required: [batch_name, queued, quality, webhook_registered]
      properties:
        batch_name:
          type: string
        queued:
          type: integer
          description: Number of documents queued for extraction by this call.
        quality:
          type: string
          enum: [fast, high]
        webhook_registered:
          type: boolean

    BatchSummary:
      type: object
      required: [batch_name, status, document_count, created_at]
      properties:
        batch_name:
          type: string
        status:
          $ref: '#/components/schemas/Status'
        document_count:
          type: integer
        created_at:
          type: string
          format: date-time
          nullable: true

    BatchListResponse:
      type: object
      required: [data, has_more, next_page_token]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/BatchSummary'
        has_more:
          type: boolean
        next_page_token:
          type: string
          nullable: true

    BatchStatusResponse:
      type: object
      required: [batch_name, document_count, status_counts, all_done, created_at]
      properties:
        batch_name:
          type: string
        document_count:
          type: integer
        status_counts:
          type: object
          properties:
            queued: { type: integer }
            processing: { type: integer }
            succeeded: { type: integer }
            failed: { type: integer }
            canceled: { type: integer }
        all_done:
          type: boolean
        created_at:
          type: string
          format: date-time
          nullable: true

    BatchResultsResponse:
      type: object
      required: [batch_name, status, created_at, started_at, completed_at, documents]
      properties:
        batch_name:
          type: string
        status:
          $ref: '#/components/schemas/Status'
        created_at:
          type: string
          format: date-time
          nullable: true
        started_at:
          type: string
          format: date-time
          nullable: true
        completed_at:
          type: string
          format: date-time
          nullable: true
          description: >-
            Set once every document in the batch has reached a terminal state.
            Null if anything is still queued or processing, even if some
            documents in the batch already finished.
        documents:
          type: array
          items:
            $ref: '#/components/schemas/Document'

    BatchDeleteResponse:
      type: object
      required: [batch_name, deleted, canceled]
      properties:
        batch_name:
          type: string
        deleted:
          type: integer
        canceled:
          type: integer
          description: Of the deleted documents, how many were still queued and got refunded.

    WebhookRegistration:
      type: object
      required: [batch_name, callback_url, webhook_secret, created_at, fired_at]
      properties:
        batch_name:
          type: string
        callback_url:
          type: string
          format: uri
        webhook_secret:
          type: string
          description: >-
            Used to verify the `webhook-signature` header on delivered
            payloads (Standard Webhooks scheme). Unchanged across repeat
            calls to this endpoint for the same batch — regenerated only on
            first registration.
        created_at:
          type: string
          format: date-time
        fired_at:
          type: string
          format: date-time
          nullable: true
          description: Null until this batch's completion notification has been delivered once. A registration fires at most once per batch.

    Field:
      type: object
      required: [id, name, format_requirement, sort_order, template_id, created_at]
      properties:
        id:
          type: integer
        name:
          type: string
        format_requirement:
          type: string
        sort_order:
          type: integer
        template_id:
          type: integer
        created_at:
          type: string
          format: date-time

    FieldWriteRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
        format_requirement:
          type: string

    FieldListResponse:
      type: object
      required: [data, has_more, next_page_token]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Field'
        has_more:
          type: boolean
          description: Always false — field lists are not paginated (a template's full field list is always returned).
        next_page_token:
          type: string
          nullable: true

    Template:
      type: object
      required: [id, name, created_at, fields]
      properties:
        id:
          type: integer
        name:
          type: string
        created_at:
          type: string
          format: date-time
          nullable: true
        fields:
          type: array
          items:
            $ref: '#/components/schemas/Field'

    CreateTemplateRequest:
      type: object
      description: Provide either `preset_id`, or `name` (with optional `base_template_id`).
      properties:
        name:
          type: string
        preset_id:
          type: string
        base_template_id:
          type: integer
          description: Only used when `preset_id` is not given — clones an existing template's fields.

    TemplateListResponse:
      type: object
      required: [data, has_more, next_page_token]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Template'
        has_more:
          type: boolean
        next_page_token:
          type: string
          nullable: true

    Preset:
      type: object
      required: [id, category, name, fields]
      properties:
        id:
          type: string
        category:
          type: string
        name:
          type: string
        fields:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
              format_requirement:
                type: string

    PresetListResponse:
      type: object
      required: [data, has_more, next_page_token]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Preset'
        has_more:
          type: boolean
        next_page_token:
          type: string
          nullable: true

    Account:
      type: object
      required: [plan, role, available_credits, max_batch_size, upload_concurrency]
      properties:
        plan:
          type: string
          example: Pro
        role:
          type: string
          example: User
        available_credits:
          type: integer
        max_batch_size:
          type: integer
        upload_concurrency:
          type: integer

    UsageEntry:
      type: object
      required: [id, action, batch_name, document_id, point_type, amount, consumer_id, created_at]
      properties:
        id:
          type: integer
        action:
          type: string
          description: e.g. `deduction`, `bbox_deduction`, `refund`, `cancel_refund`.
        batch_name:
          type: string
          nullable: true
        document_id:
          type: string
          nullable: true
        point_type:
          type: string
        amount:
          type: integer
          description: Signed — negative for spends, positive for credits back.
        consumer_id:
          type: integer
          description: The account whose credit pool was actually charged (may differ from the caller under a Team plan).
        created_at:
          type: string
          format: date-time
          nullable: true

    UsageListResponse:
      type: object
      required: [data, has_more, next_page_token]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/UsageEntry'
        has_more:
          type: boolean
        next_page_token:
          type: string
          nullable: true
