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

# Export everything this tenant holds, as a ZIP

> Every row of the tenant (jobs with their results, corrections, mailboxes and tracked emails, webhook endpoints, integrations, API keys, usage, the audit log, settings) as NDJSON, with the input files and raw emails opened. Streamed, so a large tenant is not held in memory. Never included: API key hashes, mailbox credentials, webhook and integration secrets, the data key; nor the caches and indexes rebuilt from the inputs. Audited (`tenant.export`). Needs `settings:manage`: the export holds what `jobs:read`, `mailboxes:manage`, `keys:manage`, `usage:read` and `audit:read` guard separately, and only an owner or admin holds them all.



## OpenAPI

````yaml /openapi.json get /v1/tenant/export
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, go to **Mistral OCR**, which returns each page as markdown with
       its tables inline; **GPT-6 Luna** reads that markdown as text into rows with a strict JSON schema: one owned
       page per call, the neighbouring pages' edges as context, fanned out in parallel. Every amount and balance
       Luna returns must be printed in its page's markdown.
    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. Every 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"}}`.
    * *Test mode*: a `pk_test_...` key made with `mode: test` is a sandbox. Its
    jobs run with no Luna, Mistral OCR or
      transact (text-layer statements are still read by the layout engine), are marked `test_mode: true`, are not
      metered, and are listed and fetched by test keys only (a live key never sees them); their webhook events
      carry `livemode: false`.
    * *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/tenant/export:
    get:
      tags:
        - tenant
      summary: Export everything this tenant holds, as a ZIP
      description: >-
        Every row of the tenant (jobs with their results, corrections, mailboxes
        and tracked emails, webhook endpoints, integrations, API keys, usage,
        the audit log, settings) as NDJSON, with the input files and raw emails
        opened. Streamed, so a large tenant is not held in memory. Never
        included: API key hashes, mailbox credentials, webhook and integration
        secrets, the data key; nor the caches and indexes rebuilt from the
        inputs. Audited (`tenant.export`). Needs `settings:manage`: the export
        holds what `jobs:read`, `mailboxes:manage`, `keys:manage`, `usage:read`
        and `audit:read` guard separately, and only an owner or admin holds them
        all.
      operationId: export_tenant_v1_tenant_export_get
      responses:
        '200':
          description: >-
            A ZIP, streamed: `data/<table>.ndjson` (one row per line),
            `files/<job id>/<nnn>_<name>`, `emails/<email id>.eml` and
            `manifest.json`
          content:
            application/zip: {}
        '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
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - APIKeyHeader: []
        - HTTPBearer: []
components:
  schemas:
    ErrorResponse:
      properties:
        error:
          $ref: '#/components/schemas/ErrorBody'
      additionalProperties: false
      type: object
      required:
        - error
      title: ErrorResponse
    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.