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

# Withhold, EBITDA and capacity math over supplied months and positions

> Recompute the underwriting block without re-parsing: e.g. after an underwriter excludes a month or edits the MCA table. Months marked `excluded` are dropped. Existing MCA monthly burden converts daily x21, weekly x4 and biweekly x2; offers use x21 / x4.2 / x2.1.



## OpenAPI

````yaml /openapi.json post /v1/underwriting/withhold
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/underwriting/withhold:
    post:
      tags:
        - underwriting
      summary: Withhold, EBITDA and capacity math over supplied months and positions
      description: >-
        Recompute the underwriting block without re-parsing: e.g. after an
        underwriter excludes a month or edits the MCA table. Months marked
        `excluded` are dropped. Existing MCA monthly burden converts daily x21,
        weekly x4 and biweekly x2; offers use x21 / x4.2 / x2.1.
      operationId: withhold_v1_underwriting_withhold_post
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WithholdRequest'
        required: true
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnderwritingOut'
        '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'
        '413':
          description: Payload too large
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Invalid request
      security:
        - APIKeyHeader: []
        - HTTPBearer: []
components:
  schemas:
    WithholdRequest:
      properties:
        months:
          items:
            $ref: '#/components/schemas/WithholdMonthIn'
          type: array
          minItems: 1
          title: Months
        cash_advances:
          items:
            $ref: '#/components/schemas/CashAdvanceIn'
          type: array
          title: Cash Advances
          description: >-
            The MCA positions to count; business loans are not part of the
            withhold
        ebitda_margin_pct:
          anyOf:
            - type: number
            - type: 'null'
          title: Ebitda Margin Pct
          description: Override; else computed from operating_expenses
        stated_monthly_revenue:
          anyOf:
            - type: number
            - type: 'null'
          title: Stated Monthly Revenue
        score:
          anyOf:
            - type: number
              maximum: 100
              minimum: 0
            - type: 'null'
          title: Score
        factor_rate:
          anyOf:
            - type: number
              maximum: 3
              exclusiveMinimum: 1
            - type: 'null'
          title: Factor Rate
        proposed_offer:
          anyOf:
            - $ref: '#/components/schemas/ProposedOffer'
            - type: 'null'
        affordability:
          anyOf:
            - $ref: '#/components/schemas/AffordabilityOverrides'
            - type: 'null'
      additionalProperties: false
      type: object
      required:
        - months
      title: WithholdRequest
      description: >-
        Recompute the withhold/EBITDA/capacity math from supplied months and
        positions.
    UnderwritingOut:
      properties:
        months_included:
          items:
            type: string
          type: array
          title: Months Included
        months_excluded:
          items:
            type: string
          type: array
          title: Months Excluded
        avg_monthly_revenue:
          anyOf:
            - type: number
            - type: 'null'
          title: Avg Monthly Revenue
          description: Mean true revenue over months with revenue > 0 (the scorecard's)
        avg_monthly_revenue_all_months:
          anyOf:
            - type: number
            - type: 'null'
          title: Avg Monthly Revenue All Months
        weighted_monthly_revenue:
          anyOf:
            - type: number
            - type: 'null'
          title: Weighted Monthly Revenue
          description: 0.85 decay per month back, newest weight 1
        avg_monthly_gross_deposits:
          anyOf:
            - type: number
            - type: 'null'
          title: Avg Monthly Gross Deposits
        avg_monthly_deposit_count:
          type: number
          title: Avg Monthly Deposit Count
        avg_monthly_nsf_count:
          type: number
          title: Avg Monthly Nsf Count
        avg_monthly_negative_days:
          type: number
          title: Avg Monthly Negative Days
        avg_ending_balance:
          anyOf:
            - type: number
            - type: 'null'
          title: Avg Ending Balance
        avg_daily_balance:
          anyOf:
            - type: number
            - type: 'null'
          title: Avg Daily Balance
        revenue_slope_3m:
          anyOf:
            - type: number
            - type: 'null'
          title: Revenue Slope 3M
        revenue_slope_6m:
          anyOf:
            - type: number
            - type: 'null'
          title: Revenue Slope 6M
        ebitda_margin_pct:
          anyOf:
            - type: number
            - type: 'null'
          title: Ebitda Margin Pct
          description: >-
            (sum revenue - sum operating expenses) / sum revenue x 100, IQR
            outlier months removed
        ebitda_months_used:
          items:
            type: string
          type: array
          title: Ebitda Months Used
        ebitda_months_excluded_as_outliers:
          items:
            type: string
          type: array
          title: Ebitda Months Excluded As Outliers
        existing_mca_monthly_payment:
          type: number
          title: Existing Mca Monthly Payment
          description: >-
            Monthly burden of the MCA positions counted in the withhold (daily
            x21, weekly x4, biweekly x2). Business loans are reported in
            `positions` with kind `loan` and never counted
        existing_mca_daily_payment:
          anyOf:
            - type: number
            - type: 'null'
          title: Existing Mca Daily Payment
        current_withhold_pct:
          type: number
          title: Current Withhold Pct
          description: >-
            Existing MCA monthly payments / avg monthly revenue x 100: MCA
            positions only, never loans
        holdback_pct_avg:
          anyOf:
            - type: number
            - type: 'null'
          title: Holdback Pct Avg
          description: Mean of monthly cadence-projected holdback %
        active_positions:
          type: integer
          title: Active Positions
          description: Active MCA positions (loans excluded)
        priced_positions:
          anyOf:
            - type: integer
            - type: 'null'
          title: Priced Positions
          description: >-
            Active positions counted in the withhold: the position count
            affordability pricing reads
        is_stacked:
          type: boolean
          title: Is Stacked
          description: Two or more established, active MCA positions
        ebitda_capacity_pct:
          anyOf:
            - type: number
            - type: 'null'
          title: Ebitda Capacity Pct
          description: EBITDA margin x 1.15 (the cap on total withhold)
        remaining_withhold_pct_ebitda:
          anyOf:
            - type: number
            - type: 'null'
          title: Remaining Withhold Pct Ebitda
        score_bands:
          items:
            additionalProperties: true
            type: object
          type: array
          title: Score Bands
          description: Scorecard withhold capacity for every score band
        selected_band:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Selected Band
        affordability_pricing:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Affordability Pricing
        proposed_offer:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Proposed Offer
        stated_vs_verified:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Stated Vs Verified
          description: Application's stated monthly revenue vs verified bank revenue
      additionalProperties: false
      type: object
      required:
        - months_included
        - months_excluded
        - avg_monthly_revenue
        - avg_monthly_revenue_all_months
        - weighted_monthly_revenue
        - avg_monthly_gross_deposits
        - avg_monthly_deposit_count
        - avg_monthly_nsf_count
        - avg_monthly_negative_days
        - avg_ending_balance
        - avg_daily_balance
        - revenue_slope_3m
        - revenue_slope_6m
        - ebitda_margin_pct
        - ebitda_months_used
        - ebitda_months_excluded_as_outliers
        - existing_mca_monthly_payment
        - existing_mca_daily_payment
        - current_withhold_pct
        - holdback_pct_avg
        - active_positions
        - is_stacked
        - ebitda_capacity_pct
        - remaining_withhold_pct_ebitda
        - score_bands
      title: UnderwritingOut
      description: Withhold, EBITDA and revenue math over the gate-accepted months.
    ErrorResponse:
      properties:
        error:
          $ref: '#/components/schemas/ErrorBody'
      additionalProperties: false
      type: object
      required:
        - error
      title: ErrorResponse
    WithholdMonthIn:
      properties:
        month:
          type: string
          pattern: ^\d{4}-\d{2}$
          title: Month
        revenue:
          type: number
          title: Revenue
          description: True revenue for the month (USD)
        operating_expenses:
          anyOf:
            - type: number
            - type: 'null'
          title: Operating Expenses
          description: For the EBITDA margin; omit to supply ebitda_margin_pct
        gross_deposits:
          anyOf:
            - type: number
            - type: 'null'
          title: Gross Deposits
        deposit_count:
          type: number
          title: Deposit Count
          default: 0
        nsf_count:
          type: number
          title: Nsf Count
          default: 0
        negative_days:
          anyOf:
            - type: number
            - type: 'null'
          title: Negative Days
        ending_balance:
          anyOf:
            - type: number
            - type: 'null'
          title: Ending Balance
        average_balance:
          anyOf:
            - type: number
            - type: 'null'
          title: Average Balance
        excluded:
          type: boolean
          title: Excluded
          description: 'Underwriter exclusion: the month is left out'
          default: false
      additionalProperties: false
      type: object
      required:
        - month
        - revenue
      title: WithholdMonthIn
      description: >-
        One month of an underwriter-edited monthly table (a monthly summary
        row).
    CashAdvanceIn:
      properties:
        funder:
          anyOf:
            - type: string
            - type: 'null'
          title: Funder
        frequency:
          type: string
          enum:
            - daily
            - weekly
            - biweekly
          title: Frequency
        payment_amount:
          type: number
          exclusiveMinimum: 0
          title: Payment Amount
        last_payment_date:
          anyOf:
            - type: string
              format: date
            - type: 'null'
          title: Last Payment Date
        active:
          anyOf:
            - type: boolean
            - type: 'null'
          title: Active
          description: >-
            Explicit Active flag; honored when set. Unset: active when
            last_payment_date is within the 14-day window (28 for biweekly), or
            when no last_payment_date is given
      additionalProperties: false
      type: object
      required:
        - frequency
        - payment_amount
      title: CashAdvanceIn
      description: An existing MCA position (a cash-advance row).
    ProposedOffer:
      properties:
        amount:
          type: number
          exclusiveMinimum: 0
          title: Amount
          description: Funded amount in USD
        factor_rate:
          type: number
          maximum: 3
          exclusiveMinimum: 1
          title: Factor Rate
          description: Factor rate, e.g. 1.35
        term:
          type: integer
          maximum: 1000
          exclusiveMinimum: 0
          title: Term
          description: Number of payments (days for daily, weeks for weekly)
        frequency:
          type: string
          enum:
            - daily
            - weekly
          title: Frequency
          default: daily
        fees_percent:
          type: number
          maximum: 100
          minimum: 0
          title: Fees Percent
          default: 0
      additionalProperties: false
      type: object
      required:
        - amount
        - factor_rate
        - term
      title: ProposedOffer
      description: An offer to test against the merchant's withhold capacity.
    AffordabilityOverrides:
      properties:
        ebitda_margin_multiplier:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: Ebitda Margin Multiplier
        default_factor_rate:
          anyOf:
            - type: number
              maximum: 3
              exclusiveMinimum: 1
            - type: 'null'
          title: Default Factor Rate
        min_avg_monthly_deposits:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: Min Avg Monthly Deposits
        min_avg_balance:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: Min Avg Balance
        max_monthly_nsfs:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: Max Monthly Nsfs
        max_monthly_negative_days:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: Max Monthly Negative Days
        min_monthly_deposit_count:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: Min Monthly Deposit Count
        max_offer_pct:
          anyOf:
            - type: number
              exclusiveMinimum: 0
            - type: 'null'
          title: Max Offer Pct
        min_offer_pct:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: Min Offer Pct
        payment_frequency:
          anyOf:
            - type: string
              enum:
                - daily
                - weekly
                - biweekly
                - monthly
            - type: 'null'
          title: Payment Frequency
      additionalProperties: false
      type: object
      title: AffordabilityOverrides
      description: Overrides for the default underwriting settings.
    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
  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.