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

# Attach evidence to one exact Books row

> Stores the original bytes, creates a canonical document association, and wakes Björn only after that association commits. Requires an interactive admin/operator/existing approver (or explicitly confirmed LAC staff write); API keys and connector OAuth tokens are read-only here. `resource_uri` and `resource_revision` are required; derived payslip rows remain read-only until they have a standalone resource owner.



## OpenAPI

````yaml /openapi.yaml post /sheet/attachments
openapi: 3.1.0
info:
  title: LAC Customer API
  version: '2026-07-15'
  description: >
    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,
      approve or return an invoice, or manage API keys. Payroll and company settings
      require an interactive admin or existing broad approver. Invoice decisions require the matching invoice
      review permission. Write-scoped keys can prepare draft partner
      seller profiles. An interactive company admin must review the evidence and activate
      a profile or change an active profile.
    - **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: 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: Onboarding
    description: Saved customer onboarding facts, source inventory, and opening statements.
  - 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. The one
      write here is a row declaration: your answer about a row (no receipt
      exists, private, a split, or a different booking), recorded as evidence
      for Björn.
  - name: Dimensions
    description: >-
      Project, cost-centre, department, and customer-defined reporting lenses
      over posted general-ledger lines. Provider catalogs are read-only; local
      axes and values are available for bank/file-only books.
  - 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: Work log
    description: >-
      Append-only work-hours log (date, minutes, note). Hours are records for
      invoicing and payroll evidence; they do not post to the books. Kilometres
      are not logged here — vehicle travel is submitted as an expense claim in
      the portal, where statutory reimbursement rates apply.
  - name: Filings
    description: Filing authorization, review requests, and produced output packages.
  - name: Invoicing
    description: >-
      Sales invoicing — drafts, partner seller profiles, e-invoice sending, PDF
      finalizing, credit notes, reminders, recurring templates, and the
      invoice-customer register. LAC sends buyer invoices on the e-invoice
      network only. A partner-profile invoice also sends its legal seller a
      bookkeeping PDF copy.
  - name: Payroll
    description: >-
      Payroll workspace — employees, tax cards, employer settings, draft runs,
      and payslips. Every payroll operation (reads included) requires an
      interactive admin or existing broad-approver session and is NOT available
      to API keys.
  - name: Payments
    description: >-
      Contextual review and named-human release for source-bound purchase,
      payroll, and reimbursement payments. There is no manual payment composer
      and no bank file: a batch that cannot be delivered directly to the bank
      fails closed rather than becoming the customer's manual task.
  - name: Connections
    description: >-
      Source-system connections (bank, settlement providers, commerce,
      accounting systems, inbox/Gmail, Google Sheets) and service-connection
      requests/confirmations. PayPal consent can start through this API and
      returns a URL that a company admin must open. Other browser-interactive
      consent flows remain portal-only.
  - 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.
  - name: Lens
    description: >-
      Typed, display-ready read models over the books: the transactions window
      (full row universe, three-state status, keyset pagination), the per-row
      booking view, data coverage, and the connections catalog.
  - name: Accounting
    description: >-
      The accountant's read models: general ledger, journal entries, context
      cards, the filings strip with previews, and the SQL explorer. Every
      endpoint here requires LAC staff access or the company admin role; other
      bearers receive 403.
paths:
  /sheet/attachments:
    post:
      tags:
        - Documents
      summary: Attach evidence to one exact Books row
      description: >-
        Stores the original bytes, creates a canonical document association, and
        wakes Björn only after that association commits. Requires an interactive
        admin/operator/existing approver (or explicitly confirmed LAC staff
        write); API keys and connector OAuth tokens are read-only here.
        `resource_uri` and `resource_revision` are required; derived payslip
        rows remain read-only until they have a standalone resource owner.
      operationId: uploadSheetRowAttachments
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - files
                - tab
                - row_id
                - resource_uri
                - resource_revision
                - submission_id
              properties:
                files:
                  type: array
                  items:
                    type: string
                    contentMediaType: application/octet-stream
                customer_id:
                  type: string
                tab:
                  type: string
                  enum:
                    - books
                    - vat
                range:
                  type: string
                row_id:
                  type: string
                  description: Display context only; never resolved as canonical identity.
                resource_uri:
                  type: string
                  format: uri
                  description: Server-authored `resource_ref.uri` from the selected row.
                resource_revision:
                  type: string
                  description: >-
                    Server-authored `resource_ref.revision` from the selected
                    row.
                context_text:
                  type: string
                submission_id:
                  type: string
                  minLength: 1
                  maxLength: 160
                  pattern: ^[A-Za-z0-9._:-]+$
      responses:
        '200':
          description: Evidence stored and attached.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PortalRowUploadResponse'
        '400':
          description: >-
            The multipart upload, submission identity, or canonical row target
            is invalid.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: The selected canonical row no longer exists.
        '409':
          description: >-
            The selected resource revision is stale or the submission identity
            names different input.
        '413':
          description: The upload exceeds the file-count or byte limit.
        '422':
          description: >-
            Attaching the evidence would exceed the bounded canonical evidence
            graph.
        '503':
          description: >-
            The tenant store, operation owner, or evidence archive is
            temporarily unavailable.
components:
  schemas:
    PortalRowUploadResponse:
      type: object
      additionalProperties: false
      required:
        - customer_id
        - ingest_job_id
        - file_count
        - event_id
        - saved_files
        - resource_ref
        - receipt
      properties:
        customer_id:
          type: string
        ingest_job_id:
          type: string
        file_count:
          type: integer
        event_id:
          type: string
        resource_ref:
          $ref: '#/components/schemas/CanonicalResourceRef'
        receipt:
          $ref: '#/components/schemas/OperationReceipt'
        saved_files:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - id
              - filename
              - original_filename
              - content_type
              - bytes
              - sha256
              - kind
            properties:
              id:
                type: string
              filename:
                type: string
              original_filename:
                type: string
              content_type:
                type: string
              bytes:
                type: integer
                format: int64
              sha256:
                type: string
                pattern: ^[0-9a-f]{64}$
              kind:
                type: string
    CanonicalResourceRef:
      type: object
      additionalProperties: false
      required:
        - uri
        - revision
      properties:
        uri:
          type: string
          format: uri
        revision:
          type: string
    OperationReceipt:
      type: object
      additionalProperties: false
      required:
        - uri
        - operation
        - tenant_id
        - actor
        - status
        - reason
        - input_hash
        - readback
        - started_at
        - completed_at
      properties:
        uri:
          type: string
          format: uri
        operation:
          $ref: '#/components/schemas/OperationRef'
        tenant_id:
          type: string
        actor:
          $ref: '#/components/schemas/PrincipalRef'
        status:
          type: string
          enum:
            - succeeded
        reason:
          type: string
          minLength: 1
          maxLength: 1024
        input_hash:
          type: string
          pattern: ^sha256:[0-9a-f]{64}$
        mutations:
          type: array
          items:
            $ref: '#/components/schemas/CanonicalResourceRef'
        evidence:
          type: array
          items:
            $ref: '#/components/schemas/EvidenceRef'
        readback:
          $ref: '#/components/schemas/OperationReadback'
        started_at:
          type: string
          format: date-time
        completed_at:
          type: string
          format: date-time
    Error:
      type: object
      description: Standard error envelope.
      properties:
        error:
          type: string
          description: Machine-readable error message.
        detail:
          type: string
          description: Human-readable detail.
    OperationRef:
      type: object
      additionalProperties: false
      required:
        - id
        - version
      properties:
        id:
          type: string
        version:
          type: integer
          minimum: 1
    PrincipalRef:
      type: object
      additionalProperties: false
      required:
        - kind
        - realm
        - subject
      properties:
        kind:
          type: string
          enum:
            - lac_internal
            - bjorn
            - customer
            - system
        realm:
          type: string
          minLength: 1
          maxLength: 128
        subject:
          type: string
          minLength: 1
          maxLength: 256
        tenant_id:
          type: string
    EvidenceRef:
      type: object
      additionalProperties: false
      required:
        - uri
        - digest
        - media_type
        - size
      properties:
        uri:
          type: string
          format: uri
        digest:
          type: string
          pattern: ^sha256:[0-9a-f]{64}$
        media_type:
          type: string
          minLength: 3
          maxLength: 255
        size:
          type: integer
          format: int64
          minimum: 0
    OperationReadback:
      type: object
      additionalProperties: false
      properties:
        canonical:
          type: array
          items:
            $ref: '#/components/schemas/CanonicalResourceRef'
        portal:
          type: array
          items:
            $ref: '#/components/schemas/ReadbackRef'
    ReadbackRef:
      type: object
      additionalProperties: false
      required:
        - uri
        - revision
      properties:
        uri:
          type: string
          format: uri
        revision:
          type: string
          minLength: 1
  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 interactive-session-only endpoint.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  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_...`.

````