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

# Correct a finished job: re-categorize rows, override positions, exclude months

> Saves one or more corrections on the job, in order; they apply every time the job is read (`GET /v1/jobs/{job_id}`), and the result is recomputed from the stored ledgers with no model call. `transaction` sets a row's category (one that fits its direction) and/or counterparty, and moves the row's amount between its month's figures (from how the month counted it: `counted_as`, `mca_projected`); only a verified statement the result is computed from changes the figures. `position_override` sets a position's status, frequency, payment, funder or kind (null keeps the computed value); it changes the existing MCA burden and the withhold, not the pulls each month observed. `month_exclusion` leaves a month out of the averages, or counts one that was left out; with `account_key`, it leaves one account's figures out of the month (`months[].by_account`), merged again from the others. Per field, the latest correction in force wins; revert one to remove it. Amounts, dates, balances and rows cannot be corrected, and gates, statuses and review reasons never change. Audited; the endpoints and integrations subscribed to `job.corrected` are told.



## OpenAPI

````yaml /openapi.json post /v1/jobs/{job_id}/corrections
openapi: 3.1.0
info:
  title: parser_backend
  summary: >-
    MCA packet parsing with GPT-6 Luna (OpenRouter), verified ledgers and
    underwriting metrics
  description: >

    MCA document parsing: merchant packets in (upload or email), verified
    bank-statement ledgers, ISO

    applications and underwriting metrics out.


    **How a packet is read**


    1. *Intake* - files validated by content (PDF, PNG, JPEG), PDF
    safety-parsed, images converted to PDF;
       email attachments extracted from every MIME part.
    2. *Split* - each PDF is split into documents (bank statement, merchant
    application, tax return, ...) by
       per-page classification and statement boundaries.
    3. *Read* - a statement with a text layer is read by the **layout engine**:
    tables rebuilt from the text
       layer's geometry become the ledger JSON, with no model call. Pages without a text layer, and any statement
       whose layout read does not pass the gate, are rendered at 300 DPI and read by **GPT-6 Luna** through
       OpenRouter with a strict JSON schema at full image resolution (Luna's answer is the OCR): one owned page per
       call, neighbours as context, fanned out in parallel; the same answer categorizes every row. An image-read
       ledger is accepted only when a second, independent image read agrees on every amount, date and balance.
    4. *Gate* - a ledger is **verified** only when it reconciles to the cent
       (`opening + credits - debits = closing`), reproduces the bank's printed totals and keeps every running
       balance chain. If it still fails after the image read it is returned as `review_required`, never as data.
    5. *Categorize* - a category and counterparty per row: revenue, MCA funding
    / payment, loan, NSF, overdraft,
       returned item, transfers, payroll, taxes, ... or unknown. Image reads return them with the OCR; a
       layout-engine ledger goes to GPT-6 Luna once as JSON (rows grouped by description, with counts, amounts and
       dates). Deterministic rules cross-check every row.
    6. *Aggregate* - accounts and statement months (missing months flagged); MCA
    and loan positions decided by
       GPT-6 Luna over the packet's possible financing streams and checked against the rows (cadence
       cross-checks); monthly summaries, and the withhold, EBITDA and revenue math.
    7. *Packet* - the files are one submission (`result.submission_packet`): its
    typed documents in order, which
       ones the decision rests on and why the others are excluded (duplicates, failed or unreconciled reads,
       unclassified documents, another business's statement), the merchant's details with their sources,
       month-by-month coverage, whether the statements are one unbroken history, the balance chain across
       statements, undisclosed accounts and cross-document fraud signals.

    Every Luna call is metered (prompt, cached, completion and reasoning tokens;
    provider-reported cost) and

    every answer is replay-cached: a re-sent document or window costs nothing.


    **Integrating**


    * *Authentication*: `Authorization: Bearer <credential>` (or `X-API-Key`) on
    every `/v1` route. The credential
      is an API key (`pk_live_...`, created with `POST /v1/api-keys`, scoped to permissions) or a WorkOS AuthKit
      access token (a person, with the permissions of their role). Each belongs to one tenant; jobs, caches,
      webhooks and settings are tenant-scoped. Errors are `{"error": {"code", "message", "request_id"}}`.
    * *Submit*: `POST /v1/parse` (multipart) or `POST /v1/intake/email` (raw
    MIME). The answer is `202` with a
      `job_id` and `status_url`, or the finished job when `wait` seconds suffice. Send an `Idempotency-Key` to
      make a retry return the same job.
    * *Get the result*: register a webhook once (`POST /v1/webhooks`) and
    receive `job.completed`,
      `job.review_required` or `job.failed` when each job ends, signed with the endpoint's secret; then fetch
      `GET /v1/jobs/{job_id}`. Or poll that route (`?wait=` long-polls). Every job ends in one of those three
      states, and its event is stored with it: a job that runs past `JOB_TIMEOUT_SECONDS`, or that the service
      stopped during too many runs, fails with its `job.failed` event.
    * *Status*: `completed` - every check passed; `review_required` - the result
    is usable but
      `review_reasons` name what a person should look at; `failed` - nothing could be processed (`error`).
  version: 0.1.0
servers:
  - url: https://api.parser-app.com
security: []
tags:
  - name: parse
    description: Submit documents for parsing
  - name: email
    description: Submit a raw email; its attachments are parsed as one packet
  - name: jobs
    description: Poll jobs and read results
  - name: underwriting
    description: Withhold, EBITDA and capacity calculator
  - name: webhooks
    description: Signed job events pushed to your endpoint, retried until delivered
  - name: usage
    description: Token usage, cost and service metrics
  - name: identity
    description: 'Who is calling: tenant, permissions, modules'
  - name: tenant
    description: This tenant's settings
  - name: api-keys
    description: 'Scoped API keys: create, rotate, revoke'
  - name: mailboxes
    description: Forwarding addresses and Gmail mailboxes, and each message's outcome
  - name: emails
    description: 'Every inbound email tracked: its thread, raw .eml, tags and erasure'
  - name: integrations
    description: Connections to the tenant's CRM and other systems, fed by job events
  - name: audit
    description: Every change made in the tenant
  - name: platform
    description: Tenants across the platform (platform:admin)
  - name: health
    description: Liveness and readiness
paths:
  /v1/jobs/{job_id}/corrections:
    post:
      tags:
        - jobs
      summary: >-
        Correct a finished job: re-categorize rows, override positions, exclude
        months
      description: >-
        Saves one or more corrections on the job, in order; they apply every
        time the job is read (`GET /v1/jobs/{job_id}`), and the result is
        recomputed from the stored ledgers with no model call. `transaction`
        sets a row's category (one that fits its direction) and/or counterparty,
        and moves the row's amount between its month's figures (from how the
        month counted it: `counted_as`, `mca_projected`); only a verified
        statement the result is computed from changes the figures.
        `position_override` sets a position's status, frequency, payment, funder
        or kind (null keeps the computed value); it changes the existing MCA
        burden and the withhold, not the pulls each month observed.
        `month_exclusion` leaves a month out of the averages, or counts one that
        was left out; with `account_key`, it leaves one account's figures out of
        the month (`months[].by_account`), merged again from the others. Per
        field, the latest correction in force wins; revert one to remove it.
        Amounts, dates, balances and rows cannot be corrected, and gates,
        statuses and review reasons never change. Audited; the endpoints and
        integrations subscribed to `job.corrected` are told.
      operationId: create_corrections_v1_jobs__job_id__corrections_post
      parameters:
        - name: job_id
          in: path
          required: true
          schema:
            type: string
            title: Job Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CorrectionsCreate'
      responses:
        '201':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CorrectionsOut'
        '401':
          description: Missing, invalid, revoked or expired API key or token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: The caller lacks the route's permission, or the tenant is suspended
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: >-
            No such job for this tenant, or no such row, position, month or
            correction in it
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: >-
            The job has not finished (or has no result), or the correction is
            already reverted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '413':
          description: Payload too large
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Invalid request
      security:
        - APIKeyHeader: []
        - HTTPBearer: []
components:
  schemas:
    CorrectionsCreate:
      properties:
        corrections:
          items:
            oneOf:
              - $ref: '#/components/schemas/TransactionCorrection'
              - $ref: '#/components/schemas/PositionOverride-Input'
              - $ref: '#/components/schemas/MonthExclusion'
            discriminator:
              propertyName: type
              mapping:
                month_exclusion: '#/components/schemas/MonthExclusion'
                position_override: '#/components/schemas/PositionOverride-Input'
                transaction: '#/components/schemas/TransactionCorrection'
          type: array
          maxItems: 100
          minItems: 1
          title: Corrections
          description: Applied in order
      additionalProperties: false
      type: object
      required:
        - corrections
      title: CorrectionsCreate
    CorrectionsOut:
      properties:
        job_id:
          type: string
          title: Job Id
        corrections:
          items:
            $ref: '#/components/schemas/CorrectionOut'
          type: array
          title: Corrections
        summary:
          $ref: '#/components/schemas/CorrectionsSummary'
      additionalProperties: false
      type: object
      required:
        - job_id
        - corrections
        - summary
      title: CorrectionsOut
    ErrorResponse:
      properties:
        error:
          $ref: '#/components/schemas/ErrorBody'
      additionalProperties: false
      type: object
      required:
        - error
      title: ErrorResponse
    TransactionCorrection:
      properties:
        document_id:
          type: string
          title: Document Id
          description: '`statements[].document_id`'
        index:
          type: integer
          minimum: 0
          title: Index
          description: '`statements[].transactions[].index`'
        type:
          type: string
          const: transaction
          title: Type
        category:
          anyOf:
            - $ref: '#/components/schemas/TransactionCategory'
            - type: 'null'
          description: >-
            A category that fits the row's direction (credit or debit); null:
            unchanged
        counterparty:
          anyOf:
            - type: string
              maxLength: 200
              minLength: 1
            - type: 'null'
          title: Counterparty
          description: 'Null: unchanged'
        reason:
          anyOf:
            - type: string
              maxLength: 500
            - type: 'null'
          title: Reason
      additionalProperties: false
      type: object
      required:
        - document_id
        - index
        - type
      title: TransactionCorrection
      description: >-
        Set one ledger row's category and/or counterparty. Amounts, dates,
        balances and the rows themselves are

        never corrected: a verified ledger reconciles to the cent as read.
    PositionOverride-Input:
      properties:
        type:
          type: string
          const: position_override
          title: Type
        position_key:
          type: string
          maxLength: 40
          title: Position Key
          description: '`positions[].key`'
        status:
          anyOf:
            - type: string
              enum:
                - active
                - paid_off
                - not_a_position
            - type: 'null'
          title: Status
          description: >-
            active: counted in the existing MCA burden whatever its last pull's
            date; paid_off and not_a_position: not counted
        frequency:
          anyOf:
            - type: string
              enum:
                - daily
                - weekly
                - biweekly
            - type: 'null'
          title: Frequency
          description: >-
            Payment frequency; the burden converts daily x21, weekly x4,
            biweekly x2
        payment_amount:
          anyOf:
            - type: number
              exclusiveMinimum: 0
            - type: string
            - type: 'null'
          title: Payment Amount
          description: The payment pulled now, USD
        funder:
          anyOf:
            - type: string
              maxLength: 200
              minLength: 1
            - type: 'null'
          title: Funder
        kind:
          anyOf:
            - type: string
              enum:
                - mca
                - loan
            - type: 'null'
          title: Kind
          description: 'mca: counted in the withhold; loan: reported, never counted'
        reason:
          anyOf:
            - type: string
              maxLength: 500
            - type: 'null'
          title: Reason
      additionalProperties: false
      type: object
      required:
        - type
        - position_key
      title: PositionOverride
      description: >-
        Override what was detected for one position; null fields keep the
        computed value.
    MonthExclusion:
      properties:
        type:
          type: string
          const: month_exclusion
          title: Type
        month:
          type: string
          pattern: ^\d{4}-\d{2}$
          title: Month
          description: '`months[].month`'
        account_key:
          anyOf:
            - type: string
              maxLength: 300
              minLength: 1
            - type: 'null'
          title: Account Key
          description: >-
            `accounts[].account_key`: only this account's figures for the month.
            The month is merged again from its other accounts, so it and the
            averages lose exactly this account's part; null: the whole month
        excluded:
          type: boolean
          title: Excluded
          description: >-
            true: left out of the averages (with `account_key`: out of the
            month); false: counted (e.g. a partial month)
        reason:
          anyOf:
            - type: string
              maxLength: 500
            - type: 'null'
          title: Reason
      additionalProperties: false
      type: object
      required:
        - type
        - month
        - excluded
      title: MonthExclusion
      description: >-
        Leave a calendar month out of the underwriting averages, or count one
        the pipeline left out; or leave one

        account's figures out of a month.
    CorrectionOut:
      properties:
        id:
          type: string
          title: Id
        change:
          oneOf:
            - $ref: '#/components/schemas/TransactionCorrection'
            - $ref: '#/components/schemas/PositionOverride-Output'
            - $ref: '#/components/schemas/MonthExclusion'
          title: Change
          discriminator:
            propertyName: type
            mapping:
              month_exclusion: '#/components/schemas/MonthExclusion'
              position_override: '#/components/schemas/PositionOverride-Output'
              transaction: '#/components/schemas/TransactionCorrection'
        created_at:
          type: string
          format: date-time
          title: Created At
        created_by:
          $ref: '#/components/schemas/ActorOut'
        reverted_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          title: Reverted At
          description: 'Set once reverted: no longer in force'
        reverted_by:
          anyOf:
            - $ref: '#/components/schemas/ActorOut'
            - type: 'null'
        revert_reason:
          anyOf:
            - type: string
            - type: 'null'
          title: Revert Reason
      additionalProperties: false
      type: object
      required:
        - id
        - change
        - created_at
        - created_by
      title: CorrectionOut
    CorrectionsSummary:
      properties:
        active:
          type: integer
          title: Active
          description: Corrections in force
        reverted:
          type: integer
          title: Reverted
        last_changed_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          title: Last Changed At
          description: The latest correction or revert
        correction_ids:
          items:
            type: string
          type: array
          title: Correction Ids
          description: The corrections in force, oldest first
        transactions:
          items:
            $ref: '#/components/schemas/RowRef'
          type: array
          title: Transactions
          description: Rows that carry a correction
        positions:
          items:
            type: string
          type: array
          title: Positions
          description: Keys of positions that carry a correction
        months:
          items:
            type: string
          type: array
          title: Months
          description: Months whose figures or inclusion changed
        underwriting:
          additionalProperties:
            $ref: '#/components/schemas/ChangedValue'
          type: object
          title: Underwriting
          description: Each underwriting field that changed, computed next to corrected
      additionalProperties: false
      type: object
      required:
        - active
        - reverted
      title: CorrectionsSummary
      description: >-
        What the job's corrections in force change. Gates, statuses and review
        reasons never change.
    ErrorBody:
      properties:
        code:
          type: string
          title: Code
          description: unauthenticated, forbidden, not_found, conflict, limit_reached, ...
        message:
          type: string
          title: Message
        request_id:
          type: string
          title: Request Id
          description: Also in the X-Request-ID response header
      additionalProperties: false
      type: object
      required:
        - code
        - message
        - request_id
      title: ErrorBody
    TransactionCategory:
      type: string
      enum:
        - revenue
        - mca_funding
        - loan_proceeds
        - transfer_in
        - refund_reversal
        - other_credit
        - mca_payment
        - loan_payment
        - nsf_fee
        - overdraft_fee
        - returned_item
        - bank_fee
        - transfer_out
        - payroll
        - taxes
        - operating_expense
        - cash_withdrawal
        - unknown
      title: TransactionCategory
      description: >-
        What a transaction IS, assigned by Luna per row from its own
        description.


        Categorization never decides extraction: amounts, dates, direction and

        which rows exist come from the source. Deterministic rules

        (``analysis.categories``) cross-check every row and flag disagreements.
    PositionOverride-Output:
      properties:
        type:
          type: string
          const: position_override
          title: Type
        position_key:
          type: string
          maxLength: 40
          title: Position Key
          description: '`positions[].key`'
        status:
          anyOf:
            - type: string
              enum:
                - active
                - paid_off
                - not_a_position
            - type: 'null'
          title: Status
          description: >-
            active: counted in the existing MCA burden whatever its last pull's
            date; paid_off and not_a_position: not counted
        frequency:
          anyOf:
            - type: string
              enum:
                - daily
                - weekly
                - biweekly
            - type: 'null'
          title: Frequency
          description: >-
            Payment frequency; the burden converts daily x21, weekly x4,
            biweekly x2
        payment_amount:
          anyOf:
            - type: string
            - type: 'null'
          title: Payment Amount
          description: The payment pulled now, USD
        funder:
          anyOf:
            - type: string
              maxLength: 200
              minLength: 1
            - type: 'null'
          title: Funder
        kind:
          anyOf:
            - type: string
              enum:
                - mca
                - loan
            - type: 'null'
          title: Kind
          description: 'mca: counted in the withhold; loan: reported, never counted'
        reason:
          anyOf:
            - type: string
              maxLength: 500
            - type: 'null'
          title: Reason
      additionalProperties: false
      type: object
      required:
        - type
        - position_key
      title: PositionOverride
      description: >-
        Override what was detected for one position; null fields keep the
        computed value.
    ActorOut:
      properties:
        type:
          type: string
          title: Type
          description: api_key, user or platform
        id:
          type: string
          title: Id
      additionalProperties: false
      type: object
      required:
        - type
        - id
      title: ActorOut
    RowRef:
      properties:
        document_id:
          type: string
          title: Document Id
          description: '`statements[].document_id`'
        index:
          type: integer
          minimum: 0
          title: Index
          description: '`statements[].transactions[].index`'
      additionalProperties: false
      type: object
      required:
        - document_id
        - index
      title: RowRef
    ChangedValue:
      properties:
        computed:
          title: Computed
          description: The machine's value
        corrected:
          title: Corrected
          description: The value with the job's corrections applied
      additionalProperties: false
      type: object
      required:
        - computed
        - corrected
      title: ChangedValue
  securitySchemes:
    APIKeyHeader:
      type: apiKey
      description: API key
      in: header
      name: X-API-Key
    HTTPBearer:
      type: http
      description: API key or AuthKit access token
      scheme: bearer

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.