openapi: 3.1.0
info:
  description: Accountflow's public API for external integrations.
  title: Accountflow Bridge API
  version: 1.0.0-rc1
servers:
- url: https://api.next.accountflow.com
  description: next
security:
- oauth2: []
tags:
- description: "Bank accounts, transactions, and bank↔GL reconciliation"
  name: Bank & Reconciliation
- description: VAT filing terms and their status
  name: VAT
- description: "Chart of accounts, GL lines, and trial balances"
  name: General Ledger
- description: "Signed event notifications — poke, then fetch"
  name: Webhooks
- description: The accounting clients (companies) within your reach
  name: Companies
- description: Document metadata; binaries via signed URLs
  name: Documents
- description: The organization's users
  name: Users
- description: Access policies and their user assignments
  name: Access Management
- description: "Asynchronous work: submit via a resource endpoint, poll here"
  name: Jobs
- description: Verify your integration's credentials and scope
  name: Identity
paths:
  /v1/companies:
    get:
      description: Cursor-paginated list of the companies you can read. Iterate with
        next_cursor; cursors are bound to your credentials and this endpoint's filters.
      operationId: listCompanies
      parameters:
      - description: Opaque cursor from a previous page's pagination.next_cursor.
        in: query
        name: cursor
        required: false
        schema:
          type: string
      - description: "Rows per page (1–200, default 50)."
        in: query
        name: page_size
        required: false
        schema:
          type: integer
          format: int32
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - companies:read
      summary: List companies
      tags:
      - Companies
  /v1/companies/{companyId}:
    delete:
      description: "Soft delete: the company disappears from the API (404) and can\
        \ be restored with POST /restore. Idempotent."
      operationId: deleteCompany
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DeleteCompanyRequest"
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - companies:write
      summary: Delete a company
      tags:
      - Companies
    get:
      operationId: getCompany
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - companies:read
      summary: Get a company
      tags:
      - Companies
    patch:
      description: "Patch semantics: absent fields stay unchanged."
      operationId: updateCompany
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateCompanyRequest"
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - companies:write
      summary: Update a company
      tags:
      - Companies
  /v1/companies/{companyId}/accounts:
    get:
      description: "The company's accounts for one accounting year. Omit `year` for\
        \ the current accounting year. Accounts are identified by their account number,\
        \ which is stable across years."
      operationId: listAccounts
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - description: Accounting year; omitted = the current accounting year.
        in: query
        name: year
        required: false
        schema:
          type: integer
          format: int32
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - ledger:read
      summary: List the chart of accounts
      tags:
      - General Ledger
  /v1/companies/{companyId}/accounts/{accountNumber}:
    get:
      description: "One account of the chart, addressed by its account number. Omit\
        \ `year` for the current accounting year."
      operationId: getAccount
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - description: The account number.
        example: 3000
        in: path
        name: accountNumber
        required: true
        schema:
          type: string
      - description: Accounting year; omitted = the current accounting year.
        in: query
        name: year
        required: false
        schema:
          type: integer
          format: int32
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - ledger:read
      summary: Get one account
      tags:
      - General Ledger
  /v1/companies/{companyId}/actions/disable:
    post:
      description: "Pauses the client: it stays readable with status 'disabled'. Idempotent."
      operationId: disableCompany
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - companies:write
      summary: Disable a company
      tags:
      - Companies
  /v1/companies/{companyId}/actions/enable:
    post:
      description: Idempotent.
      operationId: enableCompany
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - companies:write
      summary: Enable a company
      tags:
      - Companies
  /v1/companies/{companyId}/bank/accounts:
    get:
      operationId: listBankAccounts
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - bank:read
      summary: List the company's bank accounts
      tags:
      - Bank & Reconciliation
  /v1/companies/{companyId}/bank/accounts/{bankAccountId}/reconciliation-summary:
    get:
      description: "Twelve periods with group/entry counts, staleness, and the firm's\
        \ period-approval status on the mapped GL account."
      operationId: getReconciliationSummary
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: bankAccountId
        required: true
        schema:
          type: string
          format: uuid
      - in: query
        name: year
        required: true
        schema:
          type: integer
          format: int32
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - reconciliation:read
      summary: Per-period reconciliation summary
      tags:
      - Bank & Reconciliation
  /v1/companies/{companyId}/bank/accounts/{bankAccountId}/reconciliations:
    get:
      operationId: listReconciliations
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: bankAccountId
        required: true
        schema:
          type: string
          format: uuid
      - in: query
        name: year
        required: false
        schema:
          type: integer
          format: int32
      - in: query
        name: period
        required: false
        schema:
          type: integer
          format: int32
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - reconciliation:read
      summary: List reconciliation groups for a bank account
      tags:
      - Bank & Reconciliation
  /v1/companies/{companyId}/bank/accounts/{bankAccountId}/transactions:
    get:
      description: "Cursor-paginated, newest first. reconciliationStatus is computed\
        \ on every read: unreconciled | reconciled | stale."
      operationId: listBankTransactions
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: bankAccountId
        required: true
        schema:
          type: string
          format: uuid
      - in: query
        name: from
        required: false
        schema:
          type: string
          format: date
      - in: query
        name: to
        required: false
        schema:
          type: string
          format: date
      - description: unreconciled | reconciled | stale.
        in: query
        name: reconciliation_status
        required: false
        schema:
          type: string
      - in: query
        name: cursor
        required: false
        schema:
          type: string
      - in: query
        name: page_size
        required: false
        schema:
          type: integer
          format: int32
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - bank:read
      summary: List bank transactions
      tags:
      - Bank & Reconciliation
  /v1/companies/{companyId}/bank/reconciliations:
    post:
      description: "Creates one reconciliation group for a period. A GL line already\
        \ reconciled that year is a 409. Correction = delete and recreate — groups\
        \ are not editable."
      operationId: createReconciliation
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateReconciliationRequest"
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - reconciliation:write
      summary: Reconcile bank transactions against GL lines
      tags:
      - Bank & Reconciliation
  /v1/companies/{companyId}/documents:
    get:
      operationId: listDocuments
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - description: Opaque cursor from a previous page.
        in: query
        name: cursor
        required: false
        schema:
          type: string
      - description: "Rows per page (1–200, default 50)."
        in: query
        name: page_size
        required: false
        schema:
          type: integer
          format: int32
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - documents:read
      summary: List a company's documents
      tags:
      - Documents
    post:
      description: "Step 1 of 2: declare the document (JSON), receive a signed PUT\
        \ URL, upload the binary directly to storage, then POST /complete. The document\
        \ does not exist until completion."
      operationId: createDocumentUpload
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateUploadRequest"
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - documents:write
      summary: Start an upload
      tags:
      - Documents
  /v1/companies/{companyId}/general-ledger/lines:
    get:
      description: "Cursor-paginated GL lines for one accounting year, ordered by\
        \ period then line id. Zero-amount lines are included. Line ids are stable\
        \ across re-imports."
      operationId: listGeneralLedgerLines
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - description: Accounting year; omitted = the current accounting year.
        in: query
        name: year
        required: false
        schema:
          type: integer
          format: int32
      - description: Filter to one account number.
        example: 3000
        in: query
        name: account
        required: false
        schema:
          type: string
      - description: Filter to one fiscal period (1–12).
        in: query
        name: period
        required: false
        schema:
          type: integer
          format: int32
      - description: Opaque cursor from a previous page.
        in: query
        name: cursor
        required: false
        schema:
          type: string
      - description: "Rows per page (1–200, default 50)."
        in: query
        name: page_size
        required: false
        schema:
          type: integer
          format: int32
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - ledger:read
      summary: List general-ledger lines
      tags:
      - General Ledger
  /v1/companies/{companyId}/reconciliations/{reconciliationId}:
    delete:
      description: Dissolves the group; its transactions and GL lines become unreconciled
        again.
      operationId: deleteReconciliation
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: reconciliationId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - reconciliation:write
      summary: Unmatch a reconciliation group
      tags:
      - Bank & Reconciliation
    get:
      operationId: getReconciliation
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: reconciliationId
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - reconciliation:read
      summary: Get one reconciliation group with its entries
      tags:
      - Bank & Reconciliation
  /v1/companies/{companyId}/restore:
    post:
      description: Brings a soft-deleted company back in its prior state (active or
        disabled). Refused (409) when the deletion was part of an organization cascade.
      operationId: restoreCompany
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - companies:write
      summary: Restore a deleted company
      tags:
      - Companies
  /v1/companies/{companyId}/trial-balance:
    get:
      description: "Opening balance, labeled monthly movements, and closing balance\
        \ per account for one accounting year. The closing balance at period N is\
        \ openingBalance plus the movements of periods 1..N."
      operationId: getTrialBalance
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - description: Accounting year; omitted = the current accounting year.
        in: query
        name: year
        required: false
        schema:
          type: integer
          format: int32
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - ledger:read
      summary: Get the trial balance
      tags:
      - General Ledger
  /v1/companies/{companyId}/vat/terms:
    get:
      description: The company's VAT filing terms with their reconciliation status
        and totals (decimal NOK).
      operationId: listVatTerms
      parameters:
      - in: path
        name: companyId
        required: true
        schema:
          type: string
          format: uuid
      - description: Filter to one filing year.
        in: query
        name: year
        required: false
        schema:
          type: integer
          format: int32
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - vat:read
      summary: List VAT filing terms
      tags:
      - VAT
  /v1/documents/{documentId}:
    delete:
      description: "Soft delete: the document 404s until restored; the binary stays\
        \ in storage. Idempotent."
      operationId: deleteDocument
      parameters:
      - in: path
        name: documentId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - documents:write
      summary: Delete a document
      tags:
      - Documents
    get:
      operationId: getDocument
      parameters:
      - in: path
        name: documentId
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - documents:read
      summary: Get a document's metadata
      tags:
      - Documents
    patch:
      operationId: renameDocument
      parameters:
      - in: path
        name: documentId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RenameRequest"
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - documents:write
      summary: Rename a document
      tags:
      - Documents
  /v1/documents/{documentId}/complete:
    post:
      description: "Step 2 of 2: after the binary is uploaded, verifies the object\
        \ (size and content type must match the declaration) and creates the document.\
        \ Emits document.uploaded."
      operationId: completeDocumentUpload
      parameters:
      - in: path
        name: documentId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - documents:write
      summary: Complete an upload
      tags:
      - Documents
  /v1/documents/{documentId}/download-url:
    get:
      description: Returns a short-lived signed GET URL for the binary. Every issue
        is recorded in the document's access log.
      operationId: getDocumentDownloadUrl
      parameters:
      - in: path
        name: documentId
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - documents:read
      summary: Get a download URL
      tags:
      - Documents
  /v1/documents/{documentId}/restore:
    post:
      description: Idempotent.
      operationId: restoreDocument
      parameters:
      - in: path
        name: documentId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - documents:write
      summary: Restore a deleted document
      tags:
      - Documents
  /v1/jobs/{jobId}:
    get:
      description: "Visible only to the API client that submitted it. Poll until status\
        \ is succeeded, failed, or expired; job completion also produces a job.succeeded/job.failed\
        \ webhook event."
      operationId: getJob
      parameters:
      - in: path
        name: jobId
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2: []
      summary: Get a job's status
      tags:
      - Jobs
  /v1/organizations/{organizationId}/access-policies:
    get:
      operationId: listAccessPolicies
      parameters:
      - in: path
        name: organizationId
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - access:manage
      summary: List access policies
      tags:
      - Access Management
    post:
      description: "The policy is one document: name, default flag, and its rules.\
        \ Marking it default demotes the previous default."
      operationId: createAccessPolicy
      parameters:
      - in: path
        name: organizationId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePolicyRequest"
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - access:manage
      summary: Create an access policy
      tags:
      - Access Management
  /v1/organizations/{organizationId}/access-policies/{policyId}:
    get:
      operationId: getAccessPolicy
      parameters:
      - in: path
        name: organizationId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: policyId
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - access:manage
      summary: Get one access policy
      tags:
      - Access Management
    patch:
      description: "Patch semantics for the scalar fields; a rules array, when present,\
        \ replaces the whole rule set."
      operationId: updateAccessPolicy
      parameters:
      - in: path
        name: organizationId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: policyId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdatePolicyRequest"
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - access:manage
      summary: Update an access policy
      tags:
      - Access Management
  /v1/organizations/{organizationId}/access-policies/{policyId}/actions/archive:
    post:
      description: Archived policies grant nothing and stay for audit; they are never
        hard-deleted. Idempotent.
      operationId: archiveAccessPolicy
      parameters:
      - in: path
        name: organizationId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: policyId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - access:manage
      summary: Archive an access policy
      tags:
      - Access Management
  /v1/organizations/{organizationId}/access-policies/{policyId}/assignments:
    post:
      description: The user's effective access becomes the union of all assigned policies
        plus the organization's default policy.
      operationId: assignAccess
      parameters:
      - in: path
        name: organizationId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: policyId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AssignRequest"
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - access:manage
      summary: Assign a user to a policy
      tags:
      - Access Management
  /v1/organizations/{organizationId}/access-policies/{policyId}/assignments/{userId}:
    delete:
      operationId: revokeAccess
      parameters:
      - in: path
        name: organizationId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: policyId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: userId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - access:manage
      summary: Revoke a user's assignment
      tags:
      - Access Management
  /v1/organizations/{organizationId}/companies:
    post:
      description: "Registers a new accounting client under one of your organizations,\
        \ provisions its settings and first ledger, and grants access per the organization's\
        \ policies."
      operationId: createCompany
      parameters:
      - in: path
        name: organizationId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCompanyRequest"
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - companies:write
      summary: Create a company
      tags:
      - Companies
  /v1/organizations/{organizationId}/users:
    get:
      description: Members of the organization. Disabled users are listed with status
        'disabled'; deleted users are not represented.
      operationId: listOrgUsers
      parameters:
      - in: path
        name: organizationId
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - users:read
      summary: List the organization's users
      tags:
      - Users
  /v1/organizations/{organizationId}/users/{userId}:
    get:
      operationId: getUser
      parameters:
      - in: path
        name: organizationId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: userId
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - users:read
      summary: Get one user
      tags:
      - Users
  /v1/organizations/{organizationId}/webhooks:
    get:
      operationId: listWebhookEndpoints
      parameters:
      - in: path
        name: organizationId
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - webhooks:manage
      summary: List webhook endpoints
      tags:
      - Webhooks
    post:
      description: The response carries the signing secret EXACTLY ONCE. The endpoint
        starts in pending_verification and must echo the verification challenge (a
        webhook.verification POST) with a 2xx before events flow.
      operationId: createWebhookEndpoint
      parameters:
      - in: path
        name: organizationId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateWebhookRequest"
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - webhooks:manage
      summary: Register a webhook endpoint
      tags:
      - Webhooks
  /v1/organizations/{organizationId}/webhooks/{webhookId}:
    delete:
      operationId: deleteWebhookEndpoint
      parameters:
      - in: path
        name: organizationId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: webhookId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - webhooks:manage
      summary: Delete a webhook endpoint
      tags:
      - Webhooks
    get:
      operationId: getWebhookEndpoint
      parameters:
      - in: path
        name: organizationId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: webhookId
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - webhooks:manage
      summary: Get one webhook endpoint
      tags:
      - Webhooks
    patch:
      description: Patch semantics. Changing the URL puts the endpoint back into pending_verification
        until the new URL answers the challenge.
      operationId: updateWebhookEndpoint
      parameters:
      - in: path
        name: organizationId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: webhookId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateWebhookRequest"
        required: true
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - webhooks:manage
      summary: Update a webhook endpoint
      tags:
      - Webhooks
  /v1/organizations/{organizationId}/webhooks/{webhookId}/actions/rotate-secret:
    post:
      description: Returns the NEW secret exactly once; old signatures stop validating
        immediately.
      operationId: rotateWebhookSecret
      parameters:
      - in: path
        name: organizationId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: webhookId
        required: true
        schema:
          type: string
          format: uuid
      - in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - webhooks:manage
      summary: Rotate the signing secret
      tags:
      - Webhooks
  /v1/organizations/{organizationId}/webhooks/{webhookId}/deliveries:
    get:
      description: "The self-debugging surface: every attempt with status code, error,\
        \ and payload, newest first, cursor-paginated."
      operationId: listWebhookDeliveries
      parameters:
      - in: path
        name: organizationId
        required: true
        schema:
          type: string
          format: uuid
      - in: path
        name: webhookId
        required: true
        schema:
          type: string
          format: uuid
      - description: Opaque cursor from a previous page.
        in: query
        name: cursor
        required: false
        schema:
          type: string
      - description: "Rows per page (1–200, default 50)."
        in: query
        name: page_size
        required: false
        schema:
          type: integer
          format: int32
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
          description: OK
      security:
      - oauth2:
        - webhooks:manage
      summary: List delivery attempts
      tags:
      - Webhooks
  /v1/whoami:
    get:
      description: "Returns the authenticated API client's identity, mode, and organization\
        \ scope. Use it to verify credentials before calling anything else."
      operationId: getCallerIdentity
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WhoamiResponse"
          description: OK
      summary: Who am I
      tags:
      - Identity
components:
  schemas:
    AssignRequest:
      type: object
      properties:
        userId:
          type: string
          format: uuid
    AssociationInput:
      type: object
      properties:
        type:
          type: string
          example: transaction
        value:
          type:
          - string
          - "null"
          description: Required for 'transaction'; ignored for 'company'.
    CreateCompanyRequest:
      type: object
      properties:
        assetsEnabled:
          type:
          - boolean
          - "null"
        companyNumber:
          type: string
        companyType:
          type:
          - string
          - "null"
        countryCode:
          type:
          - string
          - "null"
        fiscalYearEnd:
          type:
          - string
          - "null"
          format: date
        fiscalYearStart:
          type:
          - string
          - "null"
          format: date
        mainAccountantUserId:
          type:
          - string
          - "null"
          format: uuid
        name:
          type: string
        obMonth:
          type:
          - integer
          - "null"
          format: int32
        ownerUserId:
          type: string
          format: uuid
        payrollEnabled:
          type:
          - boolean
          - "null"
        periodType:
          type:
          - string
          - "null"
        vatEnabled:
          type:
          - boolean
          - "null"
    CreatePolicyRequest:
      type: object
      properties:
        description:
          type:
          - string
          - "null"
        isDefault:
          type:
          - boolean
          - "null"
        name:
          type: string
        rules:
          type:
          - array
          - "null"
          items:
            $ref: "#/components/schemas/RuleInput"
    CreateReconciliationRequest:
      type: object
      properties:
        bankAccountId:
          type: string
          format: uuid
        lineIds:
          type:
          - array
          - "null"
          items:
            type: string
        note:
          type:
          - string
          - "null"
        period:
          type: integer
          format: int32
        periodYear:
          type: integer
          format: int32
        transactionIds:
          type:
          - array
          - "null"
          items:
            type: string
            format: uuid
    CreateUploadRequest:
      type: object
      properties:
        association:
          $ref: "#/components/schemas/AssociationInput"
        displayName:
          type: string
        mimeType:
          type: string
        sizeBytes:
          type: integer
          format: int64
    CreateWebhookRequest:
      type: object
      properties:
        description:
          type:
          - string
          - "null"
        eventTypes:
          type:
          - array
          - "null"
          items:
            type: string
        url:
          type: string
    DeleteCompanyRequest:
      type: object
      properties:
        reason:
          type:
          - string
          - "null"
    RenameRequest:
      type: object
      properties:
        displayName:
          type: string
    RuleInput:
      type: object
      description: One rule of a policy document.
      properties:
        accountIds:
          type:
          - array
          - "null"
          items:
            type: string
            format: uuid
        accountScope:
          type:
          - string
          - "null"
        companyId:
          type:
          - string
          - "null"
          format: uuid
          description: The company; null = every company in the organization.
        role:
          type: string
    UpdateCompanyRequest:
      type: object
      properties:
        companyNumber:
          type:
          - string
          - "null"
        companyType:
          type:
          - string
          - "null"
        countryCode:
          type:
          - string
          - "null"
        fiscalYearEnd:
          type:
          - string
          - "null"
          format: date
        fiscalYearStart:
          type:
          - string
          - "null"
          format: date
        name:
          type:
          - string
          - "null"
        periodType:
          type:
          - string
          - "null"
    UpdatePolicyRequest:
      type: object
      properties:
        description:
          type:
          - string
          - "null"
        isDefault:
          type:
          - boolean
          - "null"
        name:
          type:
          - string
          - "null"
        rules:
          type:
          - array
          - "null"
          items:
            $ref: "#/components/schemas/RuleInput"
    UpdateWebhookRequest:
      type: object
      properties:
        description:
          type:
          - string
          - "null"
        eventTypes:
          type:
          - array
          - "null"
          items:
            type: string
        url:
          type:
          - string
          - "null"
    WhoamiResponse:
      type: object
      description: The authenticated principal's identity and scope.
      properties:
        clientId:
          type: string
          description: The integration's client id (the token's azp).
          example: com.acme.erp-sync
        mode:
          type: string
          description: "How this principal acts: SYSTEM, USER (headless, as its linked\
            \ human), or INTERACTIVE (a person, e.g. via an MCP client)."
          example: SYSTEM
        organizationId:
          type:
          - string
          - "null"
          format: uuid
          description: The organization whose subtree this client reaches. Null for
            INTERACTIVE principals — a person's reach is their org memberships.
  securitySchemes:
    oauth2:
      description: OAuth 2.0 against the Accountflow Keycloak realm. Server-to-server
        integrations use client credentials (scopes granted at provisioning); interactive
        clients (e.g. MCP) use authorization code with PKCE and request scopes explicitly.
      flows:
        authorizationCode:
          authorizationUrl: https://auth.accountflow.com/realms/accountflow/protocol/openid-connect/auth
          scopes:
            bank:read: Read bank data
            companies:read: Read companies
            companies:write: Manage companies
            documents:read: Read documents
            documents:write: Manage documents
            ledger:read: "Read GL, accounts, trial balances"
            reconciliation:read: Read reconciliation status
            reconciliation:write: Work with reconciliations
            users:read: Read organization users
            vat:read: Read VAT status and returns
          tokenUrl: https://auth.accountflow.com/realms/accountflow/protocol/openid-connect/token
        clientCredentials:
          scopes:
            access:manage: Manage access policies
            bank:read: Read bank data
            companies:read: Read companies
            companies:write: Manage companies
            documents:read: Read documents
            documents:write: Manage documents
            ledger:read: "Read GL, accounts, trial balances"
            reconciliation:read: Read reconciliation status
            reconciliation:write: Work with reconciliations
            users:read: Read organization users
            vat:read: Read VAT status and returns
            webhooks:manage: Manage webhook endpoints
          tokenUrl: https://auth.accountflow.com/realms/accountflow/protocol/openid-connect/token
      type: oauth2
