openapi: 3.1.0
info:
  title: LAC Customer API
  version: '2026-07-02'
  description: >
    The Last Accounting Company (LAC) is an AI-native accounting firm for
    Finnish SMBs:

    continuous reconciliation, daily close, real-time dashboards, and full Vero
    reporting.

    This API is the same surface the LAC customer portal UI uses. It gives you
    programmatic

    access to your books (read-only general-ledger views), financial statements,
    documents,

    invoicing, payroll, source-system connections, and the message channel to
    Björn, the

    accounting agent.


    ## Authentication


    Every request carries `Authorization: Bearer <token>`. Two token kinds are
    accepted:


    - **Portal API key** (`lac_sk_...`) — minted in portal Settings → API
    access. Keys have a
      scope of `read` or `write`. Read-scoped keys are refused on all mutating methods
      (POST/PATCH/DELETE). Write-scoped keys act as the `operator` role: they can never
      use payroll endpoints (reads included), change company settings, grant access, or
      manage API keys — those operations require an interactive admin/approver session.
    - **Firebase ID token** — the interactive session token used by the portal
    UI itself.


    API keys are pinned to a single company, so the `customer_id` parameter
    (query string on

    GETs, body field on writes) can always be omitted when authenticating with
    an API key.

    Interactive tokens with access to multiple companies use `customer_id` to
    select one.


    ## Conventions


    - All field names are `snake_case` on the wire.

    - Monetary amounts are **integer cents** (fields suffixed `_cents`).

    - Rates and percentages are **basis points** (fields suffixed
    `_basis_points`;
      10000 = 100%, e.g. Finnish standard VAT 25.5% = 2550).
    - Quantities on invoice lines are decimal strings (e.g. `"1"`, `"2.5"`).

    - Dates are `YYYY-MM-DD`; periods are `YYYY-MM`; ranges accept `YYYY-MM`,
    `YYYY-Qn`,
      `YYYY`, `all`, `this_month`, or `last_6_months`.

    ## Limits


    - Document uploads: at most 20 files and 25 MB per request.

    - Sheet pagination: `limit` is capped at 200 rows per page (0 =
    unpaginated).


    ## Errors


    Errors return a JSON envelope `{"error": "...", "detail": "..."}` with a
    conventional

    HTTP status code (401 missing/invalid token, 403 insufficient scope or role,
    404 not

    found, 422 validation failure).


    More at
    [docs.lastaccountingcompany.com](https://docs.lastaccountingcompany.com).
  contact:
    name: The Last Accounting Company
    url: https://docs.lastaccountingcompany.com
servers:
  - url: https://api.app.lastaccountingcompany.com/portal
    description: Production (note the /portal base path).
security:
  - apiKey: []
tags:
  - name: Company
    description: Company profile, identity, overview, and agent status.
  - name: Books
    description: >-
      Read-only windows into the canonical general ledger: the spreadsheet-style
      Books/VAT sheet, financial statements, and analytics. There is no
      cell-edit or batch-edit API — the books are maintained by LAC.
  - name: Documents
    description: Receipt/invoice/contract uploads and per-file ingest metadata.
  - name: Messages
    description: The chat channel to Björn, the accounting agent.
  - name: Filings
    description: Filing authorization, review requests, and produced output packages.
  - name: Invoicing
    description: >-
      Sales invoicing — drafts, sending (Maventa e-invoice / email PDF / PDF
      download), credit notes, reminders, recurring templates, and the
      invoice-customer register.
  - name: Payroll
    description: >-
      Payroll workspace — employees, tax cards, employer settings, draft runs,
      and payslips. Every payroll operation (reads included) requires an
      interactive admin/approver session and is NOT available to API keys.
  - name: Connections
    description: >-
      Source-system connections (bank, Stripe, Procountor, Fennoa, inbox/Gmail,
      Google Sheets) and service-connection requests/confirmations. Note: the
      browser-interactive OAuth start/complete flows (Yapily bank consent,
      Google OAuth) are portal-only and intentionally not part of this API — use
      the portal UI to establish a connection, then read and sync it here.
  - name: Imports
    description: Historical import runs (e.g. migrated books) and their artifacts.
  - name: API keys
    description: >-
      Manage portal API keys. These endpoints require an interactive admin
      session (Firebase token) and are NOT available to API-key bearers, which
      receive 403.
paths:
  /me:
    get:
      operationId: getMe
      tags:
        - Company
      summary: Get the authenticated identity
      description: >-
        Returns the authenticated principal and the companies it can access. For
        an API key this is the single pinned company.
      responses:
        '200':
          description: Identity and accessible companies.
          content:
            application/json:
              schema:
                type: object
                description: Identity plus a list of accessible companies.
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /customers:
    get:
      operationId: listCustomers
      tags:
        - Company
      summary: List accessible companies
      description: >-
        Lists the companies (customers) the bearer can act on. API keys always
        see exactly one.
      responses:
        '200':
          description: Accessible companies.
          content:
            application/json:
              schema:
                type: object
                properties:
                  customers:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /company:
    get:
      operationId: getCompanyProfile
      tags:
        - Company
      summary: Get the company profile
      description: >-
        Returns the company's registered details and accounting configuration:
        fiscal year start month, VAT period length in months, company form, and
        statement format. `editable` indicates whether the caller could change
        settings (always false for API keys).
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Company profile.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompanyProfile'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /company/settings:
    post:
      operationId: updateCompanySettings
      tags:
        - Company
      summary: Update company accounting settings (admin/approver only)
      description: >-
        Changes the fiscal year start month and/or VAT period length. Requires
        an interactive admin/approver session — NOT available to API keys (even
        write-scoped ones), which receive 403. `vat_period_valid_from` must be a
        period-aligned `YYYY-MM-DD` date.
      security:
        - firebaseToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                customer_id:
                  type: string
                  description: Target company. Optional for single-company sessions.
                fiscal_year_start_month:
                  type: integer
                  description: Month the fiscal year starts (1-12).
                vat_period_months:
                  type: integer
                  description: VAT reporting period length in months (e.g. 1, 3, 12).
                vat_period_valid_from:
                  type: string
                  description: Period-aligned effective date, `YYYY-MM-DD`.
      responses:
        '200':
          description: Updated company profile.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompanyProfile'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /overview:
    get:
      operationId: getOverview
      tags:
        - Company
      summary: Get the portal overview dashboard
      description: >-
        One-call summary of the company's current state: the active period,
        headline metrics, items needing attention, filing readiness, connection
        health, import status, and document intake.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Overview payload.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PortalOverview'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /status:
    get:
      operationId: getAgentStatus
      tags:
        - Company
      summary: Get the accounting agent's status
      description: >-
        Returns the current status of Björn, the accounting agent, for this
        company (e.g. idle/working and progress notes).
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Agent status.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /ingest-status:
    get:
      operationId: getIngestStatus
      tags:
        - Documents
      summary: Get per-file ingest progress
      description: >-
        Reports ingest progress for uploaded files — whether each document has
        been parsed and booked yet.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Per-file ingest progress.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /sheet:
    get:
      operationId: getSheet
      tags:
        - Books
      summary: Get the Books or VAT sheet
      description: >-
        Returns the spreadsheet-style read model over the canonical general
        ledger: summary cards, column definitions, and rows for the selected tab
        and range. Amounts inside rows are integer cents. Paginate with
        `limit`/`offset` (limit 0 = unpaginated, max 200 per page).
      parameters:
        - name: tab
          in: query
          required: true
          description: Which sheet to return.
          schema:
            type: string
            enum:
              - books
              - vat
        - name: range
          in: query
          required: true
          description: >-
            Period selector: `YYYY-MM`, `YYYY-Qn`, `YYYY`, `all`, `this_month`,
            or `last_6_months`.
          schema:
            type: string
          example: 2026-06
        - $ref: '#/components/parameters/CustomerIdQuery'
        - name: limit
          in: query
          description: Rows per page. 0 = unpaginated. Maximum 200.
          schema:
            type: integer
            minimum: 0
            maximum: 200
        - name: offset
          in: query
          description: Row offset for pagination.
          schema:
            type: integer
            minimum: 0
        - name: sort
          in: query
          description: Row ordering. Empty string = default order.
          schema:
            type: string
            enum:
              - ''
              - newest
              - oldest
              - amount
              - category
              - status
      responses:
        '200':
          description: Sheet payload.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PortalSheet'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /statements:
    get:
      operationId: getStatements
      tags:
        - Books
      summary: Get financial statements
      description: >-
        Returns statement read models (income statement, balance sheet, and —
        where available — cash flow via `statement=cashflow`) for the selected
        range. Returns JSON `null` when no statement data exists for the
        selection.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
        - $ref: '#/components/parameters/RangeQuery'
        - name: statement
          in: query
          description: >-
            Specific statement to return (e.g. `cashflow`). Omit for the default
            set.
          schema:
            type: string
        - name: months
          in: query
          description: Number of trailing months to include.
          schema:
            type: integer
      responses:
        '200':
          description: Statements payload, or null when nothing is available.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/StatementsPayload'
                  - type: 'null'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /statements/drill:
    get:
      operationId: drillStatements
      tags:
        - Books
      summary: Drill into statement lines by account
      description: >-
        Returns the underlying detail behind statement lines for the given GL
        account codes. `account_codes` is a comma-joined list (e.g.
        `3000,3010`). Returns JSON `null` when there is nothing to drill into.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
        - $ref: '#/components/parameters/RangeQuery'
        - name: account_codes
          in: query
          required: true
          description: Comma-joined GL account codes, e.g. `3000,3010`.
          schema:
            type: string
          example: 3000,3010
        - name: months
          in: query
          description: Number of trailing months to include.
          schema:
            type: integer
      responses:
        '200':
          description: Drill payload, or null when nothing is available.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/StatementsDrill'
                  - type: 'null'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /analytics:
    get:
      operationId: getAnalytics
      tags:
        - Books
      summary: Get ledger analytics
      description: >-
        Returns aggregated ledger analytics (trends, category breakdowns) for
        the selected range, suitable for charting. Amounts are integer cents.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
        - $ref: '#/components/parameters/RangeQuery'
      responses:
        '200':
          description: Analytics payload.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnalyticsLedgerData'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /messages:
    get:
      operationId: listMessages
      tags:
        - Messages
      summary: List chat messages
      description: >-
        Returns the chat history between the company and Björn, the accounting
        agent, newest context included.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Chat messages.
          content:
            application/json:
              schema:
                type: object
                properties:
                  messages:
                    type: array
                    items:
                      $ref: '#/components/schemas/ChatMessage'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      operationId: sendMessage
      tags:
        - Messages
      summary: Send a message to the accounting agent
      description: >-
        Posts a message to Björn. The message is dispatched as an event to the
        agent runtime; the returned `event_id` identifies that dispatch.
        Requires a write-scoped key.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - text
              properties:
                text:
                  type: string
                  description: The message text.
                customer_id:
                  type: string
                  description: Target company. Optional for API keys.
                context:
                  type: object
                  description: Optional structured context attached to the message.
                  additionalProperties: true
                source:
                  type: string
                  description: Message source label.
                  default: portal
      responses:
        '200':
          description: Message accepted and dispatched.
          content:
            application/json:
              schema:
                type: object
                properties:
                  event_id:
                    type: string
                  message:
                    $ref: '#/components/schemas/ChatMessage'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /filings:
    get:
      operationId: getFilings
      tags:
        - Filings
      summary: Get the filing state
      description: >-
        Returns the filing state for a period (default `this_month`): the
        resolved period, the VAT preview, filing drafts, filed returns, produced
        output packages, the latest review request, and the standing
        authorization state. Amounts are integer cents.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
        - name: range
          in: query
          required: false
          description: >-
            Period selector: `YYYY-MM`, `YYYY-Qn`, `YYYY`, `all`, `this_month`,
            or `last_6_months`.
          schema:
            type: string
            default: this_month
      responses:
        '200':
          description: Filing state for the selected period.
          content:
            application/json:
              schema:
                type: object
                description: Filing state for the selected period.
                properties:
                  period:
                    type: object
                    additionalProperties: true
                  row_count:
                    type: integer
                  open_items:
                    type: integer
                  vat_preview:
                    type: object
                    additionalProperties: true
                  review_system:
                    type: object
                    additionalProperties: true
                  drafts:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
                  filings:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
                  outputs:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
                  latest_request:
                    type: object
                    additionalProperties: true
                  authorization:
                    type: object
                    additionalProperties: true
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /filings/authorization:
    post:
      operationId: setFilingAuthorization
      tags:
        - Filings
      summary: Set filing authorization
      description: >-
        Grants or revokes LAC's standing authorization to submit filings on the
        company's behalf. Requires a write-scoped key.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - authorized
              properties:
                customer_id:
                  type: string
                  description: Target company. Optional for API keys.
                authorized:
                  type: boolean
                  description: Whether LAC is authorized to submit filings.
      responses:
        '200':
          description: Authorization state recorded.
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  event_id:
                    type: string
                  authorization:
                    type: object
                    properties:
                      authorized:
                        type: boolean
                      set_by:
                        type: string
                      actor:
                        type: string
                      ts:
                        type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /filings/review-requests:
    post:
      operationId: requestFilingReview
      tags:
        - Filings
      summary: Request a filing review
      description: >-
        Asks the accounting agent to review the books and prepare filings for
        the given range (default `this_month`). Accepted asynchronously — the
        work is dispatched as an event. Requires a write-scoped key.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                customer_id:
                  type: string
                  description: Target company. Optional for API keys.
                range:
                  type: string
                  description: >-
                    Period to review: `YYYY-MM`, `YYYY-Qn`, `YYYY`, `all`,
                    `this_month`, or `last_6_months`.
                  default: this_month
      responses:
        '202':
          description: Review request accepted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  accepted:
                    type: boolean
                  event_id:
                    type: string
                  request:
                    type: object
                    additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /outputs:
    get:
      operationId: listOutputs
      tags:
        - Filings
      summary: List output packages
      description: >-
        Lists produced output packages (e.g. VAT return drafts, close reports)
        with their available file roles.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Output packages.
          content:
            application/json:
              schema:
                type: object
                properties:
                  outputs:
                    type: array
                    items:
                      $ref: '#/components/schemas/PortalOutputPackage'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /outputs/{output_id}:
    get:
      operationId: getOutput
      tags:
        - Filings
      summary: Get one output package
      description: Returns a single output package with its metadata and file roles.
      parameters:
        - name: output_id
          in: path
          required: true
          schema:
            type: string
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Output package.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PortalOutputPackage'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /outputs/{output_id}/files/{role}:
    get:
      operationId: downloadOutputFile
      tags:
        - Filings
      summary: Download an output package file
      description: >-
        Downloads one file of an output package by role: `excel`, `pdf`, `json`,
        or `instructions`. Returns raw file bytes with the appropriate content
        type.
      parameters:
        - name: output_id
          in: path
          required: true
          schema:
            type: string
        - name: role
          in: path
          required: true
          schema:
            type: string
            enum:
              - excel
              - pdf
              - json
              - instructions
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: File bytes.
          content:
            application/octet-stream: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /invoices:
    get:
      operationId: listInvoices
      tags:
        - Invoicing
      summary: List sales invoices
      description: Lists the company's sales invoices and drafts with their statuses.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Invoices payload.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PortalInvoices'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      operationId: createInvoice
      tags:
        - Invoicing
      summary: Create or update an invoice draft
      description: >-
        Creates a sales-invoice draft (or updates an existing one via
        `draft_id`). Monetary fields are integer cents; VAT rates are basis
        points (25.5% = 2550); line quantities are decimal strings. Delivery
        channel is one of `maventa_einvoice`, `email_pdf`, or `pdf_download`.
        Requires a write-scoped key.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvoiceDraftInput'
      responses:
        '200':
          description: Draft created or updated.
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  entity_id:
                    type: string
                  status:
                    type: string
                  draft:
                    type: object
                    additionalProperties: true
                  customer:
                    $ref: '#/components/schemas/Party'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /invoices/{invoice_id}:
    get:
      operationId: getInvoice
      tags:
        - Invoicing
      summary: Get an invoice
      description: >-
        Returns the full detail of one invoice or draft, amounts in integer
        cents.
      parameters:
        - $ref: '#/components/parameters/InvoiceIdPath'
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Invoice detail.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PortalInvoice'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /invoices/{invoice_id}/finvoice:
    get:
      operationId: getInvoiceFinvoice
      tags:
        - Invoicing
      summary: Download the invoice as Finvoice XML
      description: Returns the invoice rendered as a Finvoice XML document.
      parameters:
        - $ref: '#/components/parameters/InvoiceIdPath'
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Finvoice XML.
          content:
            application/xml: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /invoices/{invoice_id}/pdf:
    get:
      operationId: getInvoicePdf
      tags:
        - Invoicing
      summary: Download the invoice as PDF
      description: Returns the invoice rendered as a PDF document.
      parameters:
        - $ref: '#/components/parameters/InvoiceIdPath'
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Invoice PDF.
          content:
            application/pdf: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /invoices/{invoice_id}/send:
    post:
      operationId: sendInvoice
      tags:
        - Invoicing
      summary: Send an invoice
      description: >-
        Sends the invoice via its configured delivery channel (Maventa e-invoice
        or email PDF). Requires a write-scoped key.
      parameters:
        - $ref: '#/components/parameters/InvoiceIdPath'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerIdBody'
      responses:
        '200':
          description: Updated invoice.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PortalInvoice'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /invoices/{invoice_id}/retry-send:
    post:
      operationId: retrySendInvoice
      tags:
        - Invoicing
      summary: Retry sending an invoice
      description: >-
        Retries delivery of an invoice whose previous send attempt failed.
        Requires a write-scoped key.
      parameters:
        - $ref: '#/components/parameters/InvoiceIdPath'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomerIdBody'
      responses:
        '200':
          description: Updated invoice.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PortalInvoice'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /invoices/{invoice_id}/credit-note:
    post:
      operationId: createCreditNote
      tags:
        - Invoicing
      summary: Issue a credit note
      description: >-
        Requests a credit note against the invoice. Accepted asynchronously and
        dispatched as an event. Requires a write-scoped key.
      parameters:
        - $ref: '#/components/parameters/InvoiceIdPath'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                customer_id:
                  type: string
                  description: Target company. Optional for API keys.
                reason:
                  type: string
                  description: Reason for the credit note.
      responses:
        '202':
          description: Credit note request accepted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventAccepted'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /invoices/{invoice_id}/reminder:
    post:
      operationId: sendInvoiceReminder
      tags:
        - Invoicing
      summary: Send a payment reminder
      description: >-
        Requests a payment reminder for an unpaid invoice. Accepted
        asynchronously. Pass `idempotency_key` to make retries safe. Requires a
        write-scoped key.
      parameters:
        - $ref: '#/components/parameters/InvoiceIdPath'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                customer_id:
                  type: string
                  description: Target company. Optional for API keys.
                note:
                  type: string
                  description: Optional note to include with the reminder.
                idempotency_key:
                  type: string
                  description: Client-chosen key to deduplicate retries.
      responses:
        '202':
          description: Reminder request accepted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventAccepted'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /invoice-customers:
    get:
      operationId: listInvoiceCustomers
      tags:
        - Invoicing
      summary: List invoice customers
      description: >-
        Lists the invoicing counterparty register — the parties you invoice,
        with their e-invoice addresses and per-party defaults.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Invoice customers.
          content:
            application/json:
              schema:
                type: object
                properties:
                  customer_id:
                    type: string
                  customers:
                    type: array
                    items:
                      $ref: '#/components/schemas/Party'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      operationId: upsertInvoiceCustomer
      tags:
        - Invoicing
      summary: Create or update an invoice customer
      description: >-
        Upserts a party in the invoicing counterparty register. Include `id` to
        update an existing party. Requires a write-scoped key.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Party'
      responses:
        '200':
          description: Upserted party.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Party'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /invoice-recurring:
    get:
      operationId: listRecurringTemplates
      tags:
        - Invoicing
      summary: List recurring invoice templates
      description: >-
        Lists recurring invoice templates with their cadence and next issue
        date.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Recurring templates.
          content:
            application/json:
              schema:
                type: object
                properties:
                  customer_id:
                    type: string
                  templates:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      operationId: upsertRecurringTemplate
      tags:
        - Invoicing
      summary: Create or update a recurring invoice template
      description: >-
        Creates a recurring invoice template (or updates one via `template_id`).
        `cadence_months` is an integer number of months between issues; `draft`
        carries the same shape as the invoice-draft input (integer cents, basis
        points, decimal-string quantities). Requires a write-scoped key.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - cadence_months
                - next_issue_date
                - draft
              properties:
                customer_id:
                  type: string
                  description: Target company. Optional for API keys.
                template_id:
                  type: string
                  description: Existing template to update; omit to create.
                name:
                  type: string
                cadence_months:
                  type: integer
                  description: Months between issues.
                next_issue_date:
                  type: string
                  description: Next issue date, `YYYY-MM-DD`.
                draft:
                  $ref: '#/components/schemas/InvoiceDraftInput'
      responses:
        '200':
          description: Template created or updated.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /invoice-recurring/{template_id}:
    patch:
      operationId: updateRecurringTemplate
      tags:
        - Invoicing
      summary: Update a recurring template's status or schedule
      description: >-
        Partially updates a recurring invoice template — rename it,
        pause/resume/ archive it, or change its cadence and next issue date.
        Requires a write-scoped key.
      parameters:
        - name: template_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                customer_id:
                  type: string
                  description: Target company. Optional for API keys.
                name:
                  type: string
                status:
                  type: string
                  enum:
                    - active
                    - paused
                    - archived
                cadence_months:
                  type: integer
                  description: Months between issues.
                next_issue_date:
                  type: string
                  description: Next issue date, `YYYY-MM-DD`.
      responses:
        '200':
          description: Updated template.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /invoice-recurring/{template_id}/draft-next:
    post:
      operationId: draftNextRecurringInvoice
      tags:
        - Invoicing
      summary: Draft the next invoice from a template
      description: >-
        Materializes the template's next occurrence as an invoice draft,
        optionally overriding the issue date. Requires a write-scoped key.
      parameters:
        - name: template_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                customer_id:
                  type: string
                  description: Target company. Optional for API keys.
                issue_date:
                  type: string
                  description: Override issue date, `YYYY-MM-DD`.
      responses:
        '200':
          description: Template and the drafted invoice.
          content:
            application/json:
              schema:
                type: object
                properties:
                  template:
                    type: object
                    additionalProperties: true
                  draft:
                    type: object
                    additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /payroll:
    get:
      operationId: getPayrollWorkspace
      tags:
        - Payroll
      summary: Get the payroll workspace
      description: >-
        NOT available to API keys — requires an interactive admin/approver
        session. Returns the payroll workspace for a period (`YYYY-MM`, default
        current): employees, employer settings, draft/approved runs. Amounts are
        integer cents; rates are basis points.
      security:
        - firebaseToken: []
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
        - name: period
          in: query
          description: Payroll period, `YYYY-MM`.
          schema:
            type: string
          example: 2026-06
      responses:
        '200':
          description: Payroll workspace.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayrollWorkspaceData'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /payroll/employees:
    post:
      operationId: createPayrollEmployee
      tags:
        - Payroll
      summary: Add an employee
      description: >-
        NOT available to API keys — requires an interactive admin/approver
        session. Adds an employee to payroll. Salary and rate fields are integer
        cents; withholding/pension rates are basis points (10000 = 100%).
      security:
        - firebaseToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PayrollEmployeeInput'
      responses:
        '200':
          description: Updated payroll workspace.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayrollWorkspaceData'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    patch:
      operationId: updatePayrollEmployee
      tags:
        - Payroll
      summary: Update an employee
      description: >-
        NOT available to API keys — requires an interactive admin/approver
        session. Partially updates an employee identified by `employee_id` or
        `employee_key`. Same units as creation: integer cents and basis points.
      security:
        - firebaseToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PayrollEmployeeInput'
      responses:
        '200':
          description: Updated payroll workspace.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayrollWorkspaceData'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /payroll/employees/tax-card:
    patch:
      operationId: updateEmployeeTaxCard
      tags:
        - Payroll
      summary: Update an employee's tax card
      description: >-
        NOT available to API keys — requires an interactive admin/approver
        session. Updates the withholding tax-card values for one employee: base
        and additional withholding rates in basis points, income ceiling in
        integer cents, effective-from date, and prior year-to-date gross in
        cents.
      security:
        - firebaseToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - employee_id
              properties:
                customer_id:
                  type: string
                  description: Target company. Optional for API keys.
                period:
                  type: string
                  description: Payroll period, `YYYY-MM`.
                employee_id:
                  type: string
                contract_id:
                  type: string
                withholding_basis_points:
                  type: integer
                  description: Base withholding rate in basis points.
                tax_card_additional_basis_points:
                  type: integer
                  description: Additional withholding rate in basis points.
                tax_card_income_ceiling_cents:
                  type: integer
                  description: Tax-card income ceiling in integer cents.
                tax_card_effective_from:
                  type: string
                  description: Effective-from date, `YYYY-MM-DD`.
                tax_card_prior_ytd_gross_cents:
                  type: integer
                  description: Prior year-to-date gross earnings in integer cents.
      responses:
        '200':
          description: Updated payroll workspace.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayrollWorkspaceData'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /payroll/settings:
    patch:
      operationId: updatePayrollSettings
      tags:
        - Payroll
      summary: Update employer payroll settings
      description: >-
        NOT available to API keys — requires an interactive admin/approver
        session. Updates employer-level payroll rates — pension, side costs,
        holiday accrual — all in basis points, effective from a given date.
      security:
        - firebaseToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                customer_id:
                  type: string
                  description: Target company. Optional for API keys.
                period:
                  type: string
                  description: Payroll period, `YYYY-MM`.
                effective_from:
                  type: string
                  description: Effective-from date, `YYYY-MM-DD`.
                employer_pension_basis_points:
                  type: integer
                  description: Employer pension (TyEL) rate in basis points.
                employer_side_cost_basis_points:
                  type: integer
                  description: Employer side-cost rate in basis points.
                holiday_accrual_basis_points:
                  type: integer
                  description: Holiday pay accrual rate in basis points.
      responses:
        '200':
          description: Updated payroll workspace.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayrollWorkspaceData'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /payroll/draft:
    post:
      operationId: draftPayrollRun
      tags:
        - Payroll
      summary: Draft a payroll run
      description: >-
        NOT available to API keys — requires an interactive admin/approver
        session. Computes a draft payroll run for the period from current
        employees, tax cards, and employer settings. Drafting does not pay
        anyone — a separate approval is required. Pass `idempotency_key` to make
        retries safe.
      security:
        - firebaseToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - period
              properties:
                customer_id:
                  type: string
                  description: Target company. Optional for API keys.
                period:
                  type: string
                  description: Payroll period, `YYYY-MM`.
                payment_date:
                  type: string
                  description: Intended payment date, `YYYY-MM-DD`.
                idempotency_key:
                  type: string
                  description: Client-chosen key to deduplicate retries.
      responses:
        '200':
          description: Updated payroll workspace including the draft run.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayrollWorkspaceData'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /payroll/approve:
    post:
      operationId: approvePayrollRun
      tags:
        - Payroll
      summary: Approve a payroll run (admin/approver only)
      description: >-
        NOT available to API keys — requires an interactive admin/approver
        session. Approves a drafted payroll run for payment and reporting. API
        keys (even write-scoped ones) receive 403.
      security:
        - firebaseToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - period
                - run_id
              properties:
                customer_id:
                  type: string
                  description: Target company. Optional for single-company sessions.
                period:
                  type: string
                  description: Payroll period, `YYYY-MM`.
                run_id:
                  type: string
                  description: The drafted run to approve.
                idempotency_key:
                  type: string
                  description: Client-chosen key to deduplicate retries.
      responses:
        '200':
          description: Updated payroll workspace with the approved run.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayrollWorkspaceData'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /payroll/payslip:
    get:
      operationId: getPayslip
      tags:
        - Payroll
      summary: Get an employee's payslip
      description: >-
        NOT available to API keys — requires an interactive admin/approver
        session. Returns one employee's payslip for a period (optionally a
        specific run). Amounts are integer cents.
      security:
        - firebaseToken: []
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
        - name: period
          in: query
          description: Payroll period, `YYYY-MM`.
          schema:
            type: string
        - name: run_id
          in: query
          description: Specific payroll run; defaults to the period's latest.
          schema:
            type: string
        - name: employee_id
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Payslip.
          content:
            application/json:
              schema:
                type: object
                properties:
                  payslip:
                    type: object
                    additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /uploads:
    get:
      operationId: listUploads
      tags:
        - Documents
      summary: List uploaded files
      description: >-
        Lists uploaded documents with their metadata. Filter by `kind` (e.g.
        `receipt`, `invoice`, `contract`).
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
        - name: kind
          in: query
          description: Filter by document kind.
          schema:
            type: string
      responses:
        '200':
          description: Uploaded files.
          content:
            application/json:
              schema:
                type: object
                properties:
                  customer_id:
                    type: string
                  files:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      operationId: uploadFiles
      tags:
        - Documents
      summary: Upload documents
      description: >-
        Uploads receipts, purchase invoices, contracts, or bank statements for
        ingestion into the books. Multipart form; repeat the `files` part for
        multiple files (the singular part name `file` is also accepted). Limits:
        20 files and 25 MB per request. Requires a write-scoped key.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - files
              properties:
                files:
                  type: array
                  description: The file parts. Repeat the `files` part per file.
                  items:
                    type: string
                    contentMediaType: application/octet-stream
                customer_id:
                  type: string
                  description: Target company. Optional for API keys.
                message:
                  type: string
                  description: Free-text note for the accounting agent.
                submission_id:
                  type: string
                  description: Client-chosen id grouping the files into one submission.
                dump_kind:
                  type: string
                  description: What kind of documents these are.
                  enum:
                    - receipt
                    - invoice
                    - contract
                source:
                  type: string
                  description: Upload source label.
                  default: portal
      responses:
        '200':
          description: Upload result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PortalUploadResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /uploads/export:
    get:
      operationId: exportUploads
      tags:
        - Documents
      summary: Export all uploads as a zip
      description: Downloads every uploaded document as a single zip archive.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Zip archive of all uploads.
          content:
            application/zip: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /uploads/{filename}:
    get:
      operationId: downloadUpload
      tags:
        - Documents
      summary: Download one uploaded file
      description: Returns the raw bytes of one uploaded file.
      parameters:
        - $ref: '#/components/parameters/UploadFilenamePath'
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: File bytes.
          content:
            application/octet-stream: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    patch:
      operationId: updateUpload
      tags:
        - Documents
      summary: Update an uploaded file's metadata
      description: >-
        Renames the file's display name and/or attaches a context note for the
        accounting agent. Requires a write-scoped key.
      parameters:
        - $ref: '#/components/parameters/UploadFilenamePath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                customer_id:
                  type: string
                  description: Target company. Optional for API keys.
                display_name:
                  type: string
                context_note:
                  type: string
      responses:
        '200':
          description: Updated file metadata.
          content:
            application/json:
              schema:
                type: object
                properties:
                  customer_id:
                    type: string
                  file:
                    type: object
                    additionalProperties: true
                  event_id:
                    type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    delete:
      operationId: deleteUpload
      tags:
        - Documents
      summary: Delete an uploaded file
      description: Deletes one uploaded file. Requires a write-scoped key.
      parameters:
        - $ref: '#/components/parameters/UploadFilenamePath'
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: File deleted.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /bank-connections:
    get:
      operationId: listBankConnections
      tags:
        - Connections
      summary: List bank connections
      description: >-
        Lists the company's bank connections with setup metadata. Establishing a
        new bank connection uses a browser-interactive consent flow available
        only in the portal UI.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Bank connections and setup metadata.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /stripe-connections:
    get:
      operationId: listStripeConnections
      tags:
        - Connections
      summary: List Stripe connections
      description: Lists the company's Stripe connections with setup metadata.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Stripe connections and setup metadata.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /stripe-connections/{connection_id}/sync:
    post:
      operationId: syncStripeConnection
      tags:
        - Connections
      summary: Trigger a Stripe sync
      description: >-
        Pulls data from the Stripe connection, optionally constrained to a date
        window or a full backfill. Requires a write-scoped key.
      parameters:
        - $ref: '#/components/parameters/ConnectionIdPath'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConnectionSyncInput'
      responses:
        '200':
          description: Sync result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionSyncResult'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /procountor-connections:
    get:
      operationId: listProcountorConnections
      tags:
        - Connections
      summary: List Procountor connections
      description: Lists the company's Procountor connections with setup metadata.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Procountor connections and setup metadata.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /procountor-connections/{connection_id}/sync:
    post:
      operationId: syncProcountorConnection
      tags:
        - Connections
      summary: Trigger a Procountor sync
      description: >-
        Pulls data from the Procountor connection, optionally constrained to a
        date window or a full backfill. Requires a write-scoped key.
      parameters:
        - $ref: '#/components/parameters/ConnectionIdPath'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConnectionSyncInput'
      responses:
        '200':
          description: Sync result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionSyncResult'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /fennoa-connections:
    get:
      operationId: listFennoaConnections
      tags:
        - Connections
      summary: List Fennoa connections
      description: Lists the company's Fennoa connections with setup metadata.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Fennoa connections and setup metadata.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /fennoa-connections/{connection_id}/sync:
    post:
      operationId: syncFennoaConnection
      tags:
        - Connections
      summary: Trigger a Fennoa sync
      description: >-
        Pulls data from the Fennoa connection, optionally constrained to a date
        window or a full backfill. Requires a write-scoped key.
      parameters:
        - $ref: '#/components/parameters/ConnectionIdPath'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConnectionSyncInput'
      responses:
        '200':
          description: Sync result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionSyncResult'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /inbox-connections:
    get:
      operationId: listInboxConnections
      tags:
        - Connections
      summary: List inbox (email) connections
      description: >-
        Lists the company's inbox connections (e.g. Gmail) with setup metadata.
        Establishing one uses a browser-interactive OAuth flow available only in
        the portal UI.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Inbox connections and setup metadata.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /gsheet-connections:
    get:
      operationId: listGsheetConnections
      tags:
        - Connections
      summary: List Google Sheets connections
      description: >-
        Lists the company's Google Sheets connections with setup metadata.
        Establishing one uses a browser-interactive OAuth flow available only in
        the portal UI.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Google Sheets connections and setup metadata.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /service-connections/requests:
    post:
      operationId: requestServiceConnection
      tags:
        - Connections
      summary: Request a service connection change
      description: >-
        Files an intent about a service connection (e.g. asking LAC to set one
        up or change it). Accepted asynchronously and dispatched as an event.
        Requires a write-scoped key.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - connection_key
                - request_type
              properties:
                customer_id:
                  type: string
                  description: Target company. Optional for API keys.
                connection_key:
                  type: string
                  description: Which service connection the request concerns.
                request_type:
                  type: string
                  description: The kind of request being made.
                note:
                  type: string
                metadata:
                  type: object
                  additionalProperties: true
      responses:
        '202':
          description: Request accepted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServiceConnectionAccepted'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /service-connections/confirmations:
    post:
      operationId: confirmServiceConnection
      tags:
        - Connections
      summary: Confirm or retract a service-connection step
      description: >-
        Confirms (or retracts, via `action: retract`) a pending step in a
        service-connection setup. Accepted asynchronously. Requires a
        write-scoped key.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - connection_key
                - confirmation_type
              properties:
                customer_id:
                  type: string
                  description: Target company. Optional for API keys.
                connection_key:
                  type: string
                  description: Which service connection the confirmation concerns.
                confirmation_type:
                  type: string
                  description: The step being confirmed.
                note:
                  type: string
                action:
                  type: string
                  enum:
                    - confirm
                    - retract
                  default: confirm
      responses:
        '202':
          description: Confirmation accepted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServiceConnectionAccepted'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /imports:
    get:
      operationId: listImportRuns
      tags:
        - Imports
      summary: List import runs
      description: >-
        Lists historical import runs (e.g. books migrated from a previous
        accounting system) with their statuses.
      parameters:
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Import runs.
          content:
            application/json:
              schema:
                type: object
                properties:
                  customer_id:
                    type: string
                  runs:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /imports/{run_id}:
    get:
      operationId: getImportRun
      tags:
        - Imports
      summary: Get an import run
      description: Returns one import run with its artifacts.
      parameters:
        - $ref: '#/components/parameters/ImportRunIdPath'
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Import run and artifacts.
          content:
            application/json:
              schema:
                type: object
                properties:
                  run:
                    type: object
                    additionalProperties: true
                  artifacts:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /import-runs/{run_id}/raw-package:
    get:
      operationId: downloadImportRawPackage
      tags:
        - Imports
      summary: Download an import run's raw package
      description: Downloads the raw source package that was imported, as file bytes.
      parameters:
        - $ref: '#/components/parameters/ImportRunIdPath'
        - $ref: '#/components/parameters/CustomerIdQuery'
      responses:
        '200':
          description: Raw package bytes.
          content:
            application/octet-stream: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /api-keys:
    get:
      operationId: listApiKeys
      tags:
        - API keys
      summary: List API keys (admin, interactive session only)
      description: >-
        Lists the company's portal API keys (metadata only — the secret is shown
        once at creation). Requires an interactive admin session; API-key
        bearers receive 403.
      security:
        - firebaseToken: []
      responses:
        '200':
          description: API keys.
          content:
            application/json:
              schema:
                type: object
                properties:
                  api_keys:
                    type: array
                    items:
                      $ref: '#/components/schemas/ApiKey'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      operationId: createApiKey
      tags:
        - API keys
      summary: Create an API key (admin, interactive session only)
      description: >-
        Mints a new portal API key with scope `read` or `write`. The `api_key`
        secret (`lac_sk_...`) is returned once and never again — store it
        securely. Requires an interactive admin session; API-key bearers receive
        403.
      security:
        - firebaseToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - scope
              properties:
                name:
                  type: string
                  maxLength: 120
                  description: Human-readable key name (max 120 characters).
                scope:
                  type: string
                  enum:
                    - read
                    - write
                  description: >-
                    `read` keys are refused on all mutating methods; `write`
                    keys act as the operator role.
      responses:
        '201':
          description: Key created. The secret is returned only in this response.
          content:
            application/json:
              schema:
                type: object
                properties:
                  api_key:
                    type: string
                    description: The bearer secret, `lac_sk_...`. Shown only once.
                  record:
                    $ref: '#/components/schemas/ApiKey'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /api-keys/{key_id}:
    delete:
      operationId: revokeApiKey
      tags:
        - API keys
      summary: Revoke an API key (admin, interactive session only)
      description: >-
        Revokes a portal API key. Revoked keys are refused immediately. Requires
        an interactive admin session; API-key bearers receive 403.
      security:
        - firebaseToken: []
      parameters:
        - name: key_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Key revoked.
          content:
            application/json:
              schema:
                type: object
                properties:
                  revoked:
                    type: boolean
                  id:
                    type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: lac_sk_...
      description: >-
        Portal API key minted in portal Settings → API access. Scope `read` or
        `write`; pinned to one company. Send as `Authorization: Bearer
        lac_sk_...`.
    firebaseToken:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >-
        Firebase ID token from an interactive portal session. Required for
        admin/approver-only operations (payroll approval, company settings,
        API-key management).
  parameters:
    CustomerIdQuery:
      name: customer_id
      in: query
      required: false
      description: >-
        Target company. Optional — API keys are pinned to one company, and
        single-company sessions default to it.
      schema:
        type: string
    RangeQuery:
      name: range
      in: query
      required: false
      description: >-
        Period selector: `YYYY-MM`, `YYYY-Qn`, `YYYY`, `all`, `this_month`, or
        `last_6_months`.
      schema:
        type: string
    InvoiceIdPath:
      name: invoice_id
      in: path
      required: true
      schema:
        type: string
    UploadFilenamePath:
      name: filename
      in: path
      required: true
      description: The stored filename of the upload.
      schema:
        type: string
    ConnectionIdPath:
      name: connection_id
      in: path
      required: true
      schema:
        type: string
    ImportRunIdPath:
      name: run_id
      in: path
      required: true
      schema:
        type: string
  responses:
    Unauthorized:
      description: Missing or invalid bearer token.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: >-
        Insufficient scope or role — e.g. a read-scoped key on a mutating
        method, or an API key on an admin/approver-only endpoint.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    Error:
      type: object
      description: Standard error envelope.
      properties:
        error:
          type: string
          description: Machine-readable error message.
        detail:
          type: string
          description: Human-readable detail.
    CustomerIdBody:
      type: object
      description: Optional company selector for write requests.
      properties:
        customer_id:
          type: string
          description: Target company. Optional for API keys.
    EventAccepted:
      type: object
      description: Acknowledgement of an asynchronously dispatched request.
      properties:
        event_id:
          type: string
          description: Identifier of the dispatched event.
    Party:
      type: object
      description: >-
        An invoicing counterparty (the customer you invoice), with e-invoice
        routing and per-party defaults. Rates in basis points (25.5% = 2550).
      required:
        - name
      properties:
        id:
          type: string
          description: Party id. Include on upsert to update an existing party.
        name:
          type: string
        business_id:
          type: string
          description: Finnish business ID (Y-tunnus), e.g. `1234567-8`.
        vat_number:
          type: string
          description: EU VAT number, e.g. `FI12345678`.
        address_line1:
          type: string
        postal_code:
          type: string
        city:
          type: string
        country_code:
          type: string
          description: ISO 3166-1 alpha-2, e.g. `FI`.
        email:
          type: string
        einvoice_address:
          type: string
          description: E-invoice (verkkolasku) address, e.g. an OVT identifier.
        einvoice_operator:
          type: string
          description: E-invoice operator/intermediator code.
        default_payment_terms_days:
          type: integer
        default_vat_code:
          type: string
        default_vat_rate_basis_points:
          type: integer
          description: Default VAT rate in basis points (25.5% = 2550).
        default_invoice_language:
          type: string
    InvoiceLine:
      type: object
      description: >-
        One invoice line. Money in integer cents; VAT rate in basis points;
        quantity is a decimal string.
      required:
        - description
        - quantity
        - unit_price_cents
        - net_cents
        - vat_cents
        - gross_cents
      properties:
        description:
          type: string
        quantity:
          type: string
          description: Decimal quantity as a string, e.g. `"2.5"`.
        unit_price_cents:
          type: integer
          description: Unit price in integer cents.
        vat_rate_basis_points:
          type:
            - integer
            - 'null'
          description: VAT rate in basis points (25.5% = 2550); null when not applicable.
        vat_code:
          type: string
          description: VAT treatment code.
        net_cents:
          type: integer
          description: Line net amount in integer cents.
        vat_cents:
          type: integer
          description: Line VAT amount in integer cents.
        gross_cents:
          type: integer
          description: Line gross amount in integer cents.
    InvoiceDraftInput:
      type: object
      description: >-
        Input for creating or updating a sales-invoice draft. All money in
        integer cents; dates `YYYY-MM-DD`.
      required:
        - customer
        - lines
      properties:
        customer_id:
          type: string
          description: Target company. Optional for API keys.
        draft_id:
          type: string
          description: Existing draft to update; omit to create a new draft.
        customer:
          $ref: '#/components/schemas/Party'
        lines:
          type: array
          items:
            $ref: '#/components/schemas/InvoiceLine'
        issue_date:
          type: string
          description: '`YYYY-MM-DD`.'
        delivery_date:
          type: string
          description: '`YYYY-MM-DD`.'
        due_date:
          type: string
          description: '`YYYY-MM-DD`.'
        seller_iban:
          type: string
        seller_vat_id:
          type: string
        terms_days:
          type: integer
          description: Payment terms in days.
        note:
          type: string
        language:
          type: string
        delivery_channel:
          type: string
          enum:
            - maventa_einvoice
            - email_pdf
            - pdf_download
          description: How the invoice is delivered.
        email_subject:
          type: string
          description: Subject line when `delivery_channel` is `email_pdf`.
        email_message:
          type: string
          description: Email body when `delivery_channel` is `email_pdf`.
        net_cents:
          type: integer
          description: Invoice total net in integer cents.
        vat_cents:
          type: integer
          description: Invoice total VAT in integer cents.
        gross_cents:
          type: integer
          description: Invoice total gross in integer cents.
    PortalInvoice:
      type: object
      description: >-
        One sales invoice or draft. Amounts are integer cents; interior fields
        beyond the stable ones vary by status.
      properties:
        id:
          type: string
        status:
          type: string
        delivery_channel:
          type: string
        customer:
          $ref: '#/components/schemas/Party'
        lines:
          type: array
          items:
            $ref: '#/components/schemas/InvoiceLine'
      additionalProperties: true
    PortalInvoices:
      type: object
      description: Invoice list payload.
      properties:
        customer_id:
          type: string
        invoices:
          type: array
          items:
            $ref: '#/components/schemas/PortalInvoice'
      additionalProperties: true
    PayrollEmployeeInput:
      type: object
      description: >-
        Employee create/update input. Salary and rate fields are integer cents;
        withholding and pension rates are basis points (10000 = 100%). Identify
        an existing employee with `employee_id` or `employee_key` when updating.
      properties:
        customer_id:
          type: string
          description: Target company. Optional for API keys.
        period:
          type: string
          description: Payroll period, `YYYY-MM`.
        employee_id:
          type: string
        employee_key:
          type: string
        name:
          type: string
        tax_identifier:
          type: string
          description: Finnish personal identity code (henkilötunnus).
        iban:
          type: string
        bic:
          type: string
        start_date:
          type: string
          description: Employment start, `YYYY-MM-DD`.
        end_date:
          type: string
          description: Employment end, `YYYY-MM-DD`.
        contract_id:
          type: string
        employment_type:
          type: string
        monthly_salary_cents:
          type: integer
          description: Monthly salary in integer cents.
        hourly_rate_cents:
          type: integer
          description: Hourly rate in integer cents.
        weekly_hours:
          type: number
        withholding_basis_points:
          type: integer
          description: Base withholding rate in basis points.
        pension_basis_points:
          type: integer
          description: Employee pension rate in basis points.
        tax_card_additional_basis_points:
          type: integer
          description: Additional withholding rate in basis points.
        tax_card_income_ceiling_cents:
          type: integer
          description: Tax-card income ceiling in integer cents.
        tax_card_effective_from:
          type: string
          description: Tax card effective-from date, `YYYY-MM-DD`.
    PayrollWorkspaceData:
      type: object
      description: >-
        The payroll workspace for a period: employees, employer settings, and
        runs. Amounts are integer cents; rates are basis points. Interior shapes
        beyond the top level vary and are returned as-is.
      additionalProperties: true
    ApiKey:
      type: object
      description: Portal API key metadata (never contains the secret).
      properties:
        id:
          type: string
        name:
          type: string
        scope:
          type: string
          enum:
            - read
            - write
        created_by:
          type: string
        created_at:
          type: string
        last_used_at:
          type:
            - string
            - 'null'
        revoked_at:
          type:
            - string
            - 'null'
    CompanyProfile:
      type: object
      description: The company's registered details and accounting configuration.
      properties:
        customer_id:
          type: string
        legal_name:
          type: string
        business_id:
          type: string
          description: Finnish business ID (Y-tunnus).
        vat_number:
          type: string
        country_code:
          type: string
        currency:
          type: string
        fiscal_year_start_month:
          type: integer
          description: Month the fiscal year starts (1-12).
        vat_period_months:
          type: integer
          description: VAT reporting period length in months.
        vat_period_source:
          type: string
          enum:
            - dated
            - column
            - default
          description: Where the VAT period setting comes from.
        company_form:
          type: string
        statement_format:
          type: string
        editable:
          type: boolean
          description: Whether the caller may change settings (false for API keys).
    ChatMessage:
      type: object
      description: One message in the conversation with the accounting agent.
      additionalProperties: true
    PortalSheet:
      type: object
      description: >-
        Spreadsheet-style read model over the general ledger. Row and card
        interiors are rendered structures and returned as-is; amounts inside are
        integer cents.
      properties:
        customer:
          type: object
          additionalProperties: true
        active_tab:
          type: string
          enum:
            - books
            - vat
        sheet_status:
          type: object
          additionalProperties: true
        selected_range:
          type: string
        available_ranges:
          type: array
          items:
            type: object
            additionalProperties: true
        summary_cards:
          type: array
          items:
            type: object
            additionalProperties: true
        columns:
          type: array
          items:
            type: object
            additionalProperties: true
        rows:
          type: array
          items:
            type: object
            additionalProperties: true
        row_window:
          type: object
          description: Pagination window, present when the response is paginated.
          additionalProperties: true
        sort:
          type: string
    StatementsPayload:
      type: object
      description: >-
        Financial statement read models for the selected range. Statement line
        interiors are returned as-is; amounts are integer cents.
      additionalProperties: true
    StatementsDrill:
      type: object
      description: >-
        Account-level drill-down behind statement lines. Interiors are returned
        as-is; amounts are integer cents.
      additionalProperties: true
    AnalyticsLedgerData:
      type: object
      description: >-
        Aggregated ledger analytics for charting. Interiors are returned as-is;
        amounts are integer cents.
      additionalProperties: true
    PortalOverview:
      type: object
      description: One-call summary of the company's current state.
      properties:
        period:
          type: object
          additionalProperties: true
        metrics:
          type: object
          additionalProperties: true
        attention_items:
          type: array
          items:
            type: object
            additionalProperties: true
        readiness:
          type: object
          additionalProperties: true
        connections:
          type: object
          additionalProperties: true
        imports:
          type: object
          additionalProperties: true
        intake:
          type: object
          additionalProperties: true
      additionalProperties: true
    PortalOutputPackage:
      type: object
      description: >-
        A produced output package (e.g. a VAT return draft or close report) and
        its downloadable file roles (`excel`, `pdf`, `json`, `instructions`).
      additionalProperties: true
    PortalUploadResponse:
      type: object
      description: Result of a document upload, including per-file outcomes.
      additionalProperties: true
    ConnectionList:
      type: object
      description: >-
        Provider-specific connection list with setup metadata. The exact shape
        varies by provider and is returned as-is.
      additionalProperties: true
    ConnectionSyncInput:
      type: object
      description: Options for a provider sync.
      properties:
        customer_id:
          type: string
          description: Target company. Optional for API keys.
        date_from:
          type: string
          description: Start of the sync window, `YYYY-MM-DD`.
        date_to:
          type: string
          description: End of the sync window, `YYYY-MM-DD`.
        backfill:
          type: boolean
          description: Pull the full available history.
    ConnectionSyncResult:
      type: object
      description: Result of a provider sync run.
      properties:
        customer_id:
          type: string
        result:
          type: object
          additionalProperties: true
    ServiceConnectionAccepted:
      type: object
      description: Acknowledgement of a service-connection request or confirmation.
      properties:
        connection:
          type: object
          additionalProperties: true
        connections:
          type: array
          items:
            type: object
            additionalProperties: true
        event_id:
          type: string
