openapi: 3.1.0

info:
  title: Handwriting OCR API
  version: '3'
  description: |
    REST API for converting handwriting to text. Upload PDFs or images,
    poll for status, and download transcribed results in your preferred
    format.
  contact:
    name: Handwriting OCR support
    email: support@handwritingocr.com

servers:
  - url: https://api.handwritingocr.com/v3

tags:
  - name: Documents
    description: Upload, list, retrieve, and delete documents.
  - name: Users
    description: Account information for the authenticated user.
  - name: Credits
    description: Buy and inspect page credits.

security:
  - BearerAuth: []

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: |
        All requests require a Bearer token in the `Authorization` header.
        Generate keys at the [API tokens settings page](https://app.handwritingocr.com/settings?tab=api).

  parameters:
    DocumentId:
      name: id
      in: path
      required: true
      description: The document's unique identifier (e.g. `abcde12345`).
      schema:
        type: string
        example: abcde12345

  schemas:
    Document:
      type: object
      properties:
        id:
          type: string
          example: k2D9ZRz9Ob
        file_name:
          type: string
          example: My-Document.pdf
        action:
          type: string
          enum: [transcribe, tables, extractor]
        page_count:
          type: integer
          example: 24
        status:
          type: string
          enum: [queued, processing, processed, failed]
        automatically_deleted_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

paths:
  /documents:
    post:
      operationId: upload-document
      tags: [Documents]
      summary: Upload document
      description: |
        Upload a new document for processing. Supports PDF files and
        various image formats. The API checks the page count of the
        submitted document against your credit balance before queueing
        for processing.
      x-codeSamples:
        - lang: bash
          source: |
            curl -X POST "https://api.handwritingocr.com/v3/documents" \
                 -H "Authorization: Bearer your-api-token" \
                 -H "Accept: application/json" \
                 -F "file=@/path/to/document.pdf" \
                 -F "action=transcribe" \
                 -F "delete_after=604800"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [action, file]
              properties:
                action:
                  type: string
                  enum: [transcribe, tables, extractor]
                  description: |
                    What to do with the document. `transcribe` extracts
                    text, `tables` extracts table structure, `extractor`
                    runs a Custom Extractor.
                file:
                  type: string
                  format: binary
                  description: |
                    The document to process. Valid file types are PDF,
                    JPG, PNG, TIFF, HEIC, GIF. Maximum file size is 20MB.
                delete_after:
                  type: integer
                  minimum: 300
                  maximum: 1209600
                  description: |
                    Seconds until auto-deletion. Overrides the
                    auto-deletion period set in your user settings.
                    Minimum 300 seconds, maximum 1209600 seconds (14 days).
                extractor_id:
                  type: string
                  description: |
                    A 10-character alphanumeric string e.g. `Ks08XVPyMd`.
                    Create and test an extractor in the dashboard to get
                    the extractor ID. Required when `action` is `extractor`.
                webhook_url:
                  type: string
                  format: uri
                  description: |
                    A webhook URL to send the results for this request.
                    Overrides your global webhook URL.
      responses:
        '201':
          description: Document created and queued for processing.
          content:
            application/json:
              example:
                id: abc123
                status: queued
        '400':
          description: Bad Request — missing required fields.
        '401':
          description: Unauthorized — invalid or missing API token.
        '403':
          description: Forbidden — insufficient page credits.
        '415':
          description: Unsupported Media Type.
        '422':
          description: Validation Error — invalid parameters.
        '429':
          description: Too many requests — rate limited.
        '500':
          description: Server Error — file storage or processing failed.
    get:
      operationId: list-documents
      tags: [Documents]
      summary: List documents
      description: |
        Retrieves a paginated list of documents belonging to the
        authenticated user. Documents are sorted by creation date in
        descending order.
      parameters:
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            default: 50
            maximum: 200
          description: Number of items per page. Default 50, maximum 200.
        - name: page
          in: query
          required: false
          schema:
            type: integer
            default: 1
          description: The page number for pagination. Defaults to 1.
      responses:
        '200':
          description: Returns a paginated list of documents.
          content:
            application/json:
              example:
                documents:
                  - id: k2D9ZRz9Ob
                    file_name: My-Document.pdf
                    action: transcribe
                    page_count: 24
                    status: processed
                    automatically_deleted_at: '2026-03-12T14:49:19.000000Z'
                    created_at: '2026-03-05T14:49:19.000000Z'
                    updated_at: '2026-03-05T14:49:19.000000Z'
                  - id: ql78YGv4dE
                    file_name: Test_Doc_2.pdf
                    action: transcribe
                    page_count: 36
                    status: processed
                    automatically_deleted_at: '2026-02-24T14:09:00.000000Z'
                    created_at: '2026-02-18T18:37:03.000000Z'
                    updated_at: '2026-02-20T00:50:30.000000Z'
                current_page: 1
                from: 1
                to: 2
                total: 63
                per_page: 2
                last_page: 63
                next_page_url: https://api.handwritingocr.com/v3/documents?page=2
                prev_page_url: null
        '401':
          description: Unauthorized — invalid or missing API token.
        '422':
          description: Validation Error — invalid parameters.

  /documents/{id}:
    get:
      operationId: download-result
      tags: [Documents]
      summary: Download result
      x-display-path: /documents/{id}[.{format}]
      description: |
        Retrieve the status of a document, or download the processed
        results. The format extension is optional — if not provided,
        returns a JSON status response. If the format extension is
        provided (e.g. `documents/abc123.txt`), downloads the processed
        document in that format.

        Image thumbnail URLs are provided for each page. These images
        must be authenticated with your API token to download.

        ### Webhooks

        We strongly encourage using a **webhook** instead of polling
        this endpoint repeatedly. Webhooks deliver the processed result
        in JSON format to a URL you choose as soon as the document is
        ready, saving bandwidth and reducing latency. Configure a
        webhook in the [user dashboard](https://app.handwritingocr.com/settings?tab=documents).
      parameters:
        - $ref: '#/components/parameters/DocumentId'
        - name: format
          in: query
          required: false
          description: |
            Output format passed as a URL extension on the path
            (e.g. `documents/abc123.txt`). Varies by action: `txt`,
            `docx`, `xlsx`, `csv`, `json`.
          schema:
            type: string
            enum: [txt, docx, xlsx, csv, json]
      responses:
        '200':
          description: Returns the processed result.
          content:
            application/json:
              examples:
                transcribe:
                  summary: action = transcribe
                  value:
                    id: 3486EvMD9p
                    file_name: page-1.jpg
                    action: transcribe
                    page_count: 2
                    status: processed
                    results:
                      - page_number: 1
                        transcript: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
                      - page_number: 2
                        transcript: 'Ut enim ad minim veniam, quis nostrud exercitation ullamco.'
                    thumbnails:
                      - page_number: 1
                        url: https://api.handwritingocr.com/v3/document/3486EvMD9p/image-1.jpg
                    automatically_deleted_at: '2025-03-05 19:47:44'
                    created_at: '2025-02-19T19:47:44.000000Z'
                    updated_at: '2025-02-21T03:05:42.000000Z'
                tables:
                  summary: action = tables
                  value:
                    id: NV8OOaZk87
                    file_name: invoice_template_14.jpeg
                    action: tables
                    page_count: 1
                    status: processed
                    results:
                      - page_number: 1
                        tables:
                          - rows: []
                            rowCount: 1
                            columnCount: 2
                            tableNumber: 1
                            hasHeaderRow: false
                            headerContent: null
                        key_value_pairs:
                          - { key: VAT Number, value: '1234567' }
                extractor:
                  summary: action = extractor
                  value:
                    id: gj8kAvDQ4p
                    file_name: page-1.jpg
                    action: extractor
                    page_count: 1
                    status: processed
                    results:
                      - page_number: 1
                        extractions:
                          - - { key: date_of_birth, name: Date of birth, type: string, array: false, value: '12/12/1994' }
                            - { key: place, name: Place, type: string, array: false, value: Paris }
        '202':
          description: Accepted — document is still being processed.
        '400':
          description: Bad Request — invalid format for action type.
        '401':
          description: Unauthorized — invalid or missing API token.
        '403':
          description: Forbidden — no permission to access document.
        '404':
          description: Not found — document not found.
        '429':
          description: Too many requests — rate limited.
        '500':
          description: Server Error — error preparing file for download.
    delete:
      operationId: delete-document
      tags: [Documents]
      summary: Delete document
      description: |
        Permanently delete a document and its associated files. This
        action cannot be undone.
      parameters:
        - $ref: '#/components/parameters/DocumentId'
      responses:
        '204':
          description: Document deleted.
        '401':
          description: Unauthorized — invalid or missing API token.
        '403':
          description: Forbidden — no permission to delete document.
        '404':
          description: Not Found — document not found.
        '500':
          description: Server Error — error deleting document.

  /users/me:
    get:
      operationId: get-user-information
      tags: [Users]
      summary: Get user information
      description: |
        Returns the authenticated user's account details, including
        core user fields (name, email, account tier) alongside the
        current credit balance.
      responses:
        '200':
          description: Returns the authenticated user's account details.
          content:
            application/json:
              example:
                id: rjq89y8DRY
                name: Test User
                email: tester@email.com
                verified: true
                type: medium-monthly
                balance: 11020
                token: '1|GS8ouqzNOCbSEzXCiFlWEBve7PisNL4Yg33cfpoB6cde17b2'
                updated_at: '2026-03-09T10:16:25.000000Z'
        '401':
          description: Unauthorized — invalid or missing API token.

  /credits/topup:
    post:
      operationId: purchase-additional-credits
      tags: [Credits]
      summary: Purchase additional credits
      description: |
        Triggers an immediate credit top-up for the authenticated user
        using their saved payment method. Works independently of
        auto-topup settings. Rate-limited to one successful purchase
        per 5-minute window.
      x-codeSamples:
        - lang: bash
          source: |
            curl -X POST "https://api.handwritingocr.com/v3/credits/topup" \
                 -H "Authorization: Bearer your-api-token" \
                 -H "Accept: application/json" \
                 -F "amount=500"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [amount]
              properties:
                amount:
                  type: integer
                  example: 500
                  description: |
                    Number of credits to purchase. Must be a multiple of 100.
      responses:
        '200':
          description: |
            Purchase succeeded. Returns the number of credits added,
            the user's updated balance, and the formatted charge amount.
          content:
            application/json:
              example:
                success: true
                credits_purchased: 500
                new_balance: 1250
                amount_charged: '$4.99'
        '402':
          description: |
            The payment requires 3DS or bank authentication before it
            can complete. Follow `authentication_url` to verify, then retry.
        '403':
          description: |
            Invalid API token, or the user does not have an active
            subscription. A subscription is required to purchase credits.
        '422':
          description: |
            Validation error. `amount_not_multiple_of_100`,
            `amount_below_minimum`, `no_payment_method`, or
            `payment_failed` (with `decline_code`).
        '429':
          description: |
            A successful purchase was already made within the last
            5 minutes. Check `retry_after` (ISO 8601) for when the next
            purchase is permitted.
