openapi: 3.1.0
info:
  title: API do Financeiro Inteligente
  version: 1.0.0
  description: |-
    Public REST API for Kobana Financeiro Inteligente. Authenticated via Bearer JWT (HS512) — send `Authorization: Bearer <token>` on every request.

    **Using this spec in Postman / Insomnia:** import this spec URL (`/api/v1/openapi.yaml` or `/api/v1/openapi.json`) to get a ready-to-use collection. Re-importing always reflects the current API — no separately-maintained collection to fall out of date.
  license:
    name: Proprietary — Kobana
    url: https://kobana.com.br
servers:
  - url: https://api.finance.kobana.com.br/v1
    description: Production
  - url: https://api.finance.sandbox.kobana.com.br/v1
    description: Sandbox
components:
  schemas:
    ApiV1Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              description: Stable error code from the Kobana vocabulary (api.md §6). Do not parse the message — switch on this.
              example: invalid_token
            message:
              type: string
              description: Human-readable, English. Never includes user-supplied data verbatim.
              example: Missing Authorization header.
            details:
              description: Optional, error-specific payload (e.g. `{ required, granted }` on `insufficient_scope`).
          required:
            - code
            - message
          description: Error envelope. Always present on 4xx and 5xx responses.
      required:
        - error
    TransactionV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 8c8b4c4f-2e0e-4f6e-9b8a-2a1c2b3d4e5f
        company_id:
          type: string
          format: uuid
        financial_account_id:
          type: string
          format: uuid
        description:
          type: string
          description: Short label written by the user — distinct from `bank_description`.
          example: Pagamento de fornecedor
        bank_description:
          type:
            - string
            - "null"
          description: Raw description from the bank statement, when imported.
        document:
          type:
            - string
            - "null"
          description: Document number (NF, boleto, comprovante) that originated this movement.
        document_date:
          type:
            - string
            - "null"
          format: date-time
          description: Issue date of the source document (NF, boleto). Distinct from `occurred_at` (settlement) and `competence_date` (accounting period).
          example: 2026-05-20T00:00:00.000Z
        amount:
          type: string
          description: Decimal as a string with 2 fractional digits. Always positive — direction is carried by `type`.
          example: "150.00"
        occurred_at:
          type: string
          format: date-time
          description: When the movement hit the account.
          example: 2026-05-24T00:00:00.000Z
        competence_date:
          type:
            - string
            - "null"
          format: date-time
          description: Accounting period this movement belongs to. Falls back to `occurred_at` when null.
        type:
          type: string
          enum:
            - debit
            - credit
          description: "`debit` = money out, `credit` = money in."
        category_id:
          type:
            - string
            - "null"
          format: uuid
        person_id:
          type:
            - string
            - "null"
          format: uuid
        cost_center_id:
          type:
            - string
            - "null"
          format: uuid
        payment_method_kind:
          type:
            - string
            - "null"
          enum: &a1
            - bank_billet
            - ted
            - pix
            - credit_card
            - debit_card
            - cash
            - check
            - direct_debit
            - other
            - none
        classified:
          type: boolean
        notes:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
      required:
        - id
        - company_id
        - financial_account_id
        - description
        - bank_description
        - document
        - document_date
        - amount
        - occurred_at
        - competence_date
        - type
        - category_id
        - person_id
        - cost_center_id
        - payment_method_kind
        - classified
        - notes
        - created_at
    TransactionListMeta:
      type: object
      properties:
        next_cursor:
          type:
            - string
            - "null"
          description: Opaque cursor. Pass back as `?cursor=…` to fetch the next page. `null` when there is no next page.
      required:
        - next_cursor
    TransactionListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/TransactionV1"
        meta:
          $ref: "#/components/schemas/TransactionListMeta"
      required:
        - data
        - meta
    TransactionCreateRequest:
      type: object
      properties:
        company_id:
          type: string
          format: uuid
          description: Owning company UUID. Must belong to the tenant and match the account's `company_id` (or the account must be workspace-level).
        financial_account_id:
          type: string
          format: uuid
          description: Settlement account UUID. Must belong to the tenant.
        description:
          type: string
          minLength: 1
          maxLength: 500
        bank_description:
          type:
            - string
            - "null"
          maxLength: 500
        document:
          type:
            - string
            - "null"
          maxLength: 100
        document_date:
          type:
            - string
            - "null"
          format: date-time
          description: Issue date of the source document (ISO 8601). Pass `null` (PATCH) to clear.
        amount:
          type: string
          pattern: ^\d+\.\d{1,2}$
          description: Decimal string with 1–2 fractional digits. Always positive — direction is carried by `type`.
          example: "1500.00"
        occurred_at:
          type: string
          format: date-time
          description: When the movement hit the account (ISO 8601).
        competence_date:
          type:
            - string
            - "null"
          format: date-time
          description: Accounting period this movement belongs to. Defaults to `occurred_at` when omitted.
        type:
          type: string
          enum: &a2
            - debit
            - credit
          description: "`debit` = money out, `credit` = money in. Drives the polarity for find-or-create on `*_name` fallbacks."
        category_id:
          type:
            - string
            - "null"
          format: uuid
        category_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `category_id`. Find-or-create scoped to (workspace, company) with polarity derived from `type`. Sending both `category_id` and `category_name` returns 400.
        person_id:
          type:
            - string
            - "null"
          format: uuid
        person_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `person_id`. Find-or-create with `customer` polarity for credits / `supplier` for debits. Mutually exclusive with `person_id`.
        cost_center_id:
          type:
            - string
            - "null"
          format: uuid
        cost_center_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `cost_center_id`. Find-or-create with `revenue` polarity for credits / `cost` for debits. Mutually exclusive with `cost_center_id`.
        payment_method_kind:
          type:
            - string
            - "null"
          enum: *a1
        classified:
          type: boolean
          description: Defaults to `false`. Setting `true` marks the row as already-categorised in the UI.
        notes:
          type:
            - string
            - "null"
        is_pending:
          type: boolean
          description: Defaults to `false`. When `true`, the row is treated as a forecast — excluded from realised-balance and dashboard income/expense aggregates.
      required:
        - company_id
        - financial_account_id
        - description
        - amount
        - occurred_at
        - type
    TransactionUpdateRequest:
      type: object
      properties:
        description:
          type: string
          minLength: 1
          maxLength: 500
        bank_description:
          type:
            - string
            - "null"
          maxLength: 500
        document:
          type:
            - string
            - "null"
          maxLength: 100
        document_date:
          type:
            - string
            - "null"
          format: date-time
          description: Issue date of the source document (ISO 8601). Pass `null` (PATCH) to clear.
        amount:
          type: string
          pattern: ^\d+\.\d{1,2}$
          description: Decimal string with 1–2 fractional digits. Always positive — direction is carried by `type`.
          example: "1500.00"
        occurred_at:
          type: string
          format: date-time
          description: When the movement hit the account (ISO 8601).
        competence_date:
          type:
            - string
            - "null"
          format: date-time
          description: Accounting period this movement belongs to. Defaults to `occurred_at` when omitted.
        type:
          type: string
          enum: *a2
          description: "`debit` = money out, `credit` = money in. Drives the polarity for find-or-create on `*_name` fallbacks."
        category_id:
          type:
            - string
            - "null"
          format: uuid
        category_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `category_id`. Find-or-create scoped to (workspace, company) with polarity derived from `type`. Sending both `category_id` and `category_name` returns 400.
        person_id:
          type:
            - string
            - "null"
          format: uuid
        person_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `person_id`. Find-or-create with `customer` polarity for credits / `supplier` for debits. Mutually exclusive with `person_id`.
        cost_center_id:
          type:
            - string
            - "null"
          format: uuid
        cost_center_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `cost_center_id`. Find-or-create with `revenue` polarity for credits / `cost` for debits. Mutually exclusive with `cost_center_id`.
        payment_method_kind:
          type:
            - string
            - "null"
          enum: *a1
        classified:
          type: boolean
          description: Defaults to `false`. Setting `true` marks the row as already-categorised in the UI.
        notes:
          type:
            - string
            - "null"
        is_pending:
          type: boolean
          description: Defaults to `false`. When `true`, the row is treated as a forecast — excluded from realised-balance and dashboard income/expense aggregates.
    TaxV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: b3d9a5f8-2c1e-4a90-9f23-7e6b1c4d8a02
        name:
          type: string
          description: Tax kind label (e.g. IRRF, INSS, ISS). Unique per workspace.
          example: IRRF
        default_rate:
          type:
            - string
            - "null"
          description: Suggested default rate as a decimal string with up to 2 fractional digits, expressed as percent (e.g. `"1.50"` = 1.5%). Form-side convenience — the charge stores its own BRL amount, not derived from this rate.
          example: "1.50"
        default_withholding:
          type: boolean
          description: When true, new tax charges default to `withholding=true` (imposto retido). Most withheld BR taxes (IRRF, INSS) lean true.
        active:
          type: boolean
          description: When false, the tax is hidden from new charges (soft-deactivation). Existing charges referencing it are preserved.
        created_at:
          type: string
          format: date-time
      required:
        - id
        - name
        - default_rate
        - default_withholding
        - active
        - created_at
    TaxListMeta:
      type: object
      properties:
        next_cursor:
          type:
            - string
            - "null"
          description: Opaque cursor. Pass back as `?cursor=…` to fetch the next page. `null` when there is no next page.
      required:
        - next_cursor
    TaxListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/TaxV1"
        meta:
          $ref: "#/components/schemas/TaxListMeta"
      required:
        - data
        - meta
    TaxCreateRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
          description: Tax label. Unique per workspace.
          example: IRRF
        default_rate:
          type:
            - string
            - "null"
          pattern: ^\d+(\.\d{1,2})?$
          description: Suggested default rate as a percent string (0–100, up to 2 decimals). `null`/omitted = no default.
          example: "1.50"
        default_withholding:
          type: boolean
          description: Defaults to `true` (imposto retido).
        active:
          type: boolean
          description: Defaults to `true`.
      required:
        - name
    TaxUpdateRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
          description: Tax label. Unique per workspace.
          example: IRRF
        default_rate:
          type:
            - string
            - "null"
          pattern: ^\d+(\.\d{1,2})?$
          description: Suggested default rate as a percent string (0–100, up to 2 decimals). `null`/omitted = no default.
          example: "1.50"
        default_withholding:
          type: boolean
          description: Defaults to `true` (imposto retido).
        active:
          type: boolean
          description: Defaults to `true`.
    CompanyV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 62057dab-0f98-4020-ae1e-2c0803251b1e
        name:
          type: string
          description: Legal name as registered.
          example: ACME Comércio Ltda
        cnpj:
          type: string
          description: 14-digit numeric string (no formatting / no dots / no dashes). Use the standard BR CNPJ check-digit algorithm to validate.
          example: "12345678000190"
        type:
          type: string
          enum:
            - matriz
            - filial
          description: "`matriz` is the head office; `filial` is a branch. Unique per workspace, scoped by `cnpj`."
        active:
          type: boolean
          description: When false, the company is soft-deactivated. Existing transactions/people/etc. referencing it are preserved.
        created_at:
          type: string
          format: date-time
      required:
        - id
        - name
        - cnpj
        - type
        - active
        - created_at
    CompanyListMeta:
      type: object
      properties:
        next_cursor:
          type:
            - string
            - "null"
          description: Opaque cursor. Pass back as `?cursor=…` to fetch the next page. `null` when there is no next page.
      required:
        - next_cursor
    CompanyListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/CompanyV1"
        meta:
          $ref: "#/components/schemas/CompanyListMeta"
      required:
        - data
        - meta
    CompanyCreateRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 255
          description: Legal name as registered.
          example: ACME Comércio Ltda
        cnpj:
          type: string
          minLength: 1
          description: CNPJ — 14 digits. Formatting (dots / slash / dash) is accepted and stripped. Validated with the standard BR check-digit algorithm. **Required** — a missing, invalid, or document-less value returns 422 (there is no pessoa-física / document-less company in this API). Must be unique among active companies in the workspace (duplicate → 409). `type` (matriz/filial) is derived server-side from the branch digits.
          example: "12345678000190"
      required:
        - name
        - cnpj
    CompanyUpdateRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 255
        active:
          type: boolean
          description: Set `false` to soft-deactivate the company (existing references are preserved).
    CostCenterV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: f8d2a5e1-3c47-4b90-9a1b-2c8f7e3d4b95
        company_id:
          type: string
          format: uuid
          description: Owning company. Cost centers are company-scoped (unlike taxes which are workspace-scoped).
        name:
          type: string
          description: Cost center label. Unique constraint not enforced at DB level.
          example: Operacional
        type:
          type: string
          enum:
            - cost
            - revenue
          description: "`cost` (deducts from gross result) or `revenue` (adds to gross result). Drives the rateio polarity in payables/receivables."
        description:
          type:
            - string
            - "null"
          description: Free-text annotation. May be null.
        active:
          type: boolean
          description: When false, the cost center is soft-deactivated. Existing payables/receivables/transactions referencing it are preserved.
        created_at:
          type: string
          format: date-time
      required:
        - id
        - company_id
        - name
        - type
        - description
        - active
        - created_at
    CostCenterListMeta:
      type: object
      properties:
        next_cursor:
          type:
            - string
            - "null"
          description: Opaque cursor. Pass back as `?cursor=…` to fetch the next page. `null` when there is no next page.
      required:
        - next_cursor
    CostCenterListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/CostCenterV1"
        meta:
          $ref: "#/components/schemas/CostCenterListMeta"
      required:
        - data
        - meta
    CostCenterCreateRequest:
      type: object
      properties:
        company_id:
          type: string
          format: uuid
          description: Owning company UUID. Must belong to the tenant.
        name:
          type: string
          minLength: 1
          maxLength: 255
        type:
          type: string
          enum:
            - cost
            - revenue
          description: "`cost` (deducts from gross result) or `revenue` (adds). Drives the rateio polarity."
        description:
          type:
            - string
            - "null"
          maxLength: 500
        active:
          type: boolean
          description: Defaults to `true`.
      required:
        - company_id
        - name
        - type
    CostCenterUpdateRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 255
        type:
          type: string
          enum:
            - cost
            - revenue
        description:
          type:
            - string
            - "null"
          maxLength: 500
        active:
          type: boolean
    CategoryV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: f8d2a5e1-3c47-4b90-9a1b-2c8f7e3d4b95
        company_id:
          type: string
          format: uuid
          description: Owning company. Categories are company-scoped (siblings across companies do not share an `id`).
        name:
          type: string
          description: Category label.
          example: Aluguel
        parent_id:
          type:
            - string
            - "null"
          format: uuid
          description: Parent category. `null` for root-level categories. Filter `?level=1` to list roots, or `?parent_id=<uuid>` to list direct children of one parent.
        level:
          type: integer
          description: Pre-computed depth in the tree (root = 1, direct child = 2, …). Denormalised on the row — no recursive lookup needed on the client.
          example: 1
        type:
          type: string
          enum:
            - debit
            - credit
          description: "`debit` (cost — money out) or `credit` (revenue — money in). Matches the transaction sign convention."
        created_at:
          type: string
          format: date-time
      required:
        - id
        - company_id
        - name
        - parent_id
        - level
        - type
        - created_at
    CategoryListMeta:
      type: object
      properties:
        next_cursor:
          type:
            - string
            - "null"
          description: Opaque cursor. Pass back as `?cursor=…` to fetch the next page. `null` when there is no next page.
      required:
        - next_cursor
    CategoryListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/CategoryV1"
        meta:
          $ref: "#/components/schemas/CategoryListMeta"
      required:
        - data
        - meta
    CategoryCreateRequest:
      type: object
      properties:
        company_id:
          type: string
          format: uuid
          description: Owning company UUID. Must belong to the tenant.
        name:
          type: string
          minLength: 1
          maxLength: 255
        type:
          type: string
          enum:
            - debit
            - credit
          description: "`debit` (cost) or `credit` (revenue). A subcategory must match its parent's type."
        parent_id:
          type:
            - string
            - "null"
          format: uuid
          description: Parent category UUID. Omit (or `null`) for a root (`level` 1). When set, the server computes `level = parent.level + 1` (max 3) and requires `type` to match the parent.
      required:
        - company_id
        - name
        - type
    CategoryUpdateRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 255
        type:
          type: string
          enum:
            - debit
            - credit
          description: Changing a parent's type cascades to all descendants.
    PersonV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 1b0bee9d-3db5-4c64-a204-afc83f54d55e
        company_id:
          type: string
          format: uuid
        name:
          type: string
          description: Display name. Required.
          example: ACME Comércio Ltda
        legal_name:
          type:
            - string
            - "null"
          description: Razão social (juridical persons) — often blank for natural persons.
        nickname:
          type:
            - string
            - "null"
        kind:
          type:
            - string
            - "null"
          enum:
            - natural
            - juridical
          description: "`natural` (pessoa física) or `juridical` (pessoa jurídica). `null` for legacy rows where the kind was never set."
        document_type:
          type:
            - string
            - "null"
          enum:
            - cpf
            - cnpj
          description: Document type for `document_number`. `cpf` for natural, `cnpj` for juridical.
        document_number:
          type:
            - string
            - "null"
          description: Numeric-only string (no formatting / no dots / no dashes). 11 digits for CPF, 14 for CNPJ. Unique per (company, document_number) at the DB level.
          example: "12345678901"
        is_customer:
          type: boolean
          description: True when the person can receive a `receivables` charge.
        is_supplier:
          type: boolean
          description: True when the person can receive a `payables` charge.
        active:
          type: boolean
          description: When false, the person is soft-deactivated (hidden from new forms). Existing payables/receivables/transactions referencing it stay valid.
        emails:
          type: array
          items:
            type: string
          description: Email addresses as a JSON array. Empty when no contact email is known.
        phones:
          type: array
          items:
            type: string
          description: Phone numbers as a JSON array (E.164-ish strings). Empty when no contact phone is known.
        notes:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - company_id
        - name
        - legal_name
        - nickname
        - kind
        - document_type
        - document_number
        - is_customer
        - is_supplier
        - active
        - emails
        - phones
        - notes
        - created_at
        - updated_at
    PersonListMeta:
      type: object
      properties:
        next_cursor:
          type:
            - string
            - "null"
          description: Opaque cursor. Pass back as `?cursor=…` to fetch the next page. `null` when there is no next page.
      required:
        - next_cursor
    PersonListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PersonV1"
        meta:
          $ref: "#/components/schemas/PersonListMeta"
      required:
        - data
        - meta
    PersonCreateRequest:
      type: object
      properties:
        company_id:
          type: string
          format: uuid
          description: Owning company UUID. Must belong to the tenant.
        name:
          type: string
          minLength: 1
          maxLength: 255
        legal_name:
          type:
            - string
            - "null"
          maxLength: 255
          description: Razão social (juridical persons).
        nickname:
          type:
            - string
            - "null"
          maxLength: 255
        kind:
          type:
            - string
            - "null"
          enum: &a3
            - natural
            - juridical
          description: "`natural` (pessoa física) or `juridical` (pessoa jurídica)."
        document_type:
          type:
            - string
            - "null"
          enum: &a4
            - cpf
            - cnpj
          description: Document type for `document_number`. Inferred from the number length when omitted.
        document_number:
          type:
            - string
            - "null"
          maxLength: 20
          description: CPF (11 digits) or CNPJ (14 digits). Formatting is stripped; check digits are validated.
        is_customer:
          type: boolean
          description: Defaults to `false`. True ⇒ can receive a receivable.
        is_supplier:
          type: boolean
          description: Defaults to `false`. True ⇒ can receive a payable.
        active:
          type: boolean
          description: Defaults to `true`.
        notes:
          type:
            - string
            - "null"
          maxLength: 2000
      required:
        - company_id
        - name
    PersonUpdateRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 255
        legal_name:
          type:
            - string
            - "null"
          maxLength: 255
          description: Razão social (juridical persons).
        nickname:
          type:
            - string
            - "null"
          maxLength: 255
        kind:
          type:
            - string
            - "null"
          enum: *a3
          description: "`natural` (pessoa física) or `juridical` (pessoa jurídica)."
        document_type:
          type:
            - string
            - "null"
          enum: *a4
          description: Document type for `document_number`. Inferred from the number length when omitted.
        document_number:
          type:
            - string
            - "null"
          maxLength: 20
          description: CPF (11 digits) or CNPJ (14 digits). Formatting is stripped; check digits are validated.
        is_customer:
          type: boolean
          description: Defaults to `false`. True ⇒ can receive a receivable.
        is_supplier:
          type: boolean
          description: Defaults to `false`. True ⇒ can receive a payable.
        active:
          type: boolean
          description: Defaults to `true`.
        notes:
          type:
            - string
            - "null"
          maxLength: 2000
    AccountV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: fe023ef2-a70f-4a7c-a413-abdfe4977a19
        company_id:
          type:
            - string
            - "null"
          format: uuid
          description: Owning company. `null` for workspace-level accounts (no company link). Filter `?company_id=<uuid>` returns only accounts of that company.
        name:
          type: string
          description: Account label shown in the UI.
          example: Conta Corrente Itaú
        type:
          type: string
          enum:
            - checking
            - savings
            - cash
            - investment
            - credit
          description: Account kind. Drives the reconcile flow and the dashboard categorisation.
        bank_id:
          type:
            - string
            - "null"
          format: uuid
          description: Reference to a `banks` row when the account lives at a bank. `null` for cash / non-bank accounts.
        agency:
          type:
            - string
            - "null"
        agency_digit:
          type:
            - string
            - "null"
        account_number:
          type:
            - string
            - "null"
        account_digit:
          type:
            - string
            - "null"
        active:
          type: boolean
          description: When false, the account is soft-deactivated (hidden from new forms). Existing transactions stay valid.
        disable_reconcile:
          type: boolean
          description: When true, the manual reconciliation screen skips this account. Useful for non-bank accounts (cash, virtual envelopes).
        created_at:
          type: string
          format: date-time
      required:
        - id
        - company_id
        - name
        - type
        - bank_id
        - agency
        - agency_digit
        - account_number
        - account_digit
        - active
        - disable_reconcile
        - created_at
    AccountDetailV1:
      allOf:
        - $ref: "#/components/schemas/AccountV1"
        - type: object
          properties:
            balance:
              type: string
              description: Computed current balance in BRL as a signed 2-decimal string, derived from posted (non-pending) transactions. This is the live balance — not the creation seed. Detail-only.
              example: "1530.45"
          required:
            - balance
    AccountListMeta:
      type: object
      properties:
        next_cursor:
          type:
            - string
            - "null"
          description: Opaque cursor. Pass back as `?cursor=…` to fetch the next page. `null` when there is no next page.
      required:
        - next_cursor
    AccountListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/AccountV1"
        meta:
          $ref: "#/components/schemas/AccountListMeta"
      required:
        - data
        - meta
    AccountCreateRequest:
      type: object
      properties:
        company_id:
          type: string
          format: uuid
          description: "Owning company UUID — required (financial accounts must belong to a company). Must belong to the tenant. Workspace-level accounts (`company_id: null`) exist from imports but are not creatable via v1."
        name:
          type: string
          minLength: 1
          maxLength: 255
        type:
          type: string
          enum: &a5
            - checking
            - savings
            - cash
            - investment
            - credit
        bank_id:
          type:
            - string
            - "null"
          format: uuid
          description: Reference to a `banks` row. `null` for cash / non-bank accounts.
        agency:
          type:
            - string
            - "null"
          maxLength: 50
        agency_digit:
          type:
            - string
            - "null"
          maxLength: 5
        account_number:
          type:
            - string
            - "null"
          maxLength: 50
        account_digit:
          type:
            - string
            - "null"
          maxLength: 5
        balance:
          type: string
          pattern: ^-?\d+\.\d{1,2}$
          description: Opening balance seed (signed 2-decimal string). Defaults to `0.00`. NOT the live balance — that is computed from transactions.
          example: "0.00"
        disable_reconcile:
          type: boolean
      required:
        - company_id
        - name
        - type
    AccountUpdateRequest:
      type: object
      properties:
        company_id:
          type: string
          format: uuid
          description: "Owning company UUID — required (financial accounts must belong to a company). Must belong to the tenant. Workspace-level accounts (`company_id: null`) exist from imports but are not creatable via v1."
        name:
          type: string
          minLength: 1
          maxLength: 255
        type:
          type: string
          enum: *a5
        bank_id:
          type:
            - string
            - "null"
          format: uuid
          description: Reference to a `banks` row. `null` for cash / non-bank accounts.
        agency:
          type:
            - string
            - "null"
          maxLength: 50
        agency_digit:
          type:
            - string
            - "null"
          maxLength: 5
        account_number:
          type:
            - string
            - "null"
          maxLength: 50
        account_digit:
          type:
            - string
            - "null"
          maxLength: 5
        disable_reconcile:
          type: boolean
        active:
          type: boolean
          description: Set `false` to soft-deactivate (existing transactions stay valid).
    PayableV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 8c8b4c4f-2e0e-4f6e-9b8a-2a1c2b3d4e5f
        company_id:
          type: string
          format: uuid
        description:
          type: string
          description: Short label written by the user.
          example: Aluguel — escritório matriz
        amount:
          type: string
          description: Decimal as a string with 2 fractional digits. Positive — the polarity (cost) is implicit in the resource.
          example: "1500.00"
        interest_amount:
          type:
            - string
            - "null"
          description: Late-payment interest paid at settlement, as a 2-decimal string. `null` when not applicable.
        discount_amount:
          type:
            - string
            - "null"
          description: Discount obtained at settlement, as a 2-decimal string. `null` when not applicable.
        ticket_amount:
          type:
            - string
            - "null"
          description: '"Valor liquidado" — actual amount that left the bank. May differ from `nominal_amount` due to bank charges. `null` when not applicable.'
        nominal_amount:
          type: string
          description: "Computed: `amount + (interest_amount ?? 0) - (discount_amount ?? 0)`. Reproduced per request — not persisted."
          example: "1525.00"
        due_date:
          type: string
          format: date-time
          description: When this payable is due. Filterable with range operators (`[gte]`, `[lte]`).
        payment_date:
          type:
            - string
            - "null"
          format: date-time
          description: When this payable was actually paid. `null` while pending. Reflects `status=paid` 1:1.
        competence_date:
          type: string
          format: date-time
          description: Accounting period this charge belongs to (independent of the cash-flow `due_date`).
        type:
          type: string
          enum:
            - normal
            - recurring
            - installment
          description: Billing kind. `recurring` and `installment` link sibling rows via `group_id`.
        status:
          type: string
          enum:
            - pending
            - paid
            - cancelled
          description: 'Raw status. Derive overdue client-side: `status === "pending" AND due_date < today`.'
        person_id:
          type:
            - string
            - "null"
          format: uuid
        person_name:
          type:
            - string
            - "null"
          description: Denormalised display name of the related person. `null` when `person_id` is null.
        category_id:
          type:
            - string
            - "null"
          format: uuid
        category_name:
          type:
            - string
            - "null"
          description: Denormalised name of the related category. `null` when `category_id` is null.
        cost_center_id:
          type:
            - string
            - "null"
          format: uuid
          description: Primary cost center on the row. Splits (rateio) live in `cost_center_allocations[]` on the detail endpoint.
        cost_center_name:
          type:
            - string
            - "null"
          description: Denormalised name of the primary cost center. `null` when `cost_center_id` is null.
        financial_account_id:
          type:
            - string
            - "null"
          format: uuid
          description: Settlement account once paid. `null` while pending or for non-account-bound payments.
        expected_account_id:
          type:
            - string
            - "null"
          format: uuid
          description: Projected settlement account, used by the forecast widgets pre-payment.
        payment_method_kind:
          type:
            - string
            - "null"
          enum: &a6
            - bank_billet
            - utility
            - tribute
            - ted
            - pix
            - credit_card
            - debit_card
            - cash
            - check
            - direct_debit
            - other
            - none
          description: "`utility` (concessionária) and `tribute` are payable-only — they never appear on receivables/transactions."
        operation_kind:
          type:
            - string
            - "null"
          enum:
            - payment
            - transfer
          description: Kobana operation axis (Fase 8.1). `payment` settles an existing document (boleto/tribute/utility/Pix copy-paste); `transfer` sends to a payee you identify (TED/Pix key or bank data). NULL for non-Kobana methods (cash/card/cheque). Payable-only.
        document:
          type:
            - string
            - "null"
          description: Free-form reference of the source document (e.g. `NF-12345`, `BOL-001`). Populated by the Fintera / CSV importer and matched by the listing `search` and `documentContains` filters. Distinct from `document_number`.
        document_number:
          type:
            - string
            - "null"
          description: Structured document number — the bare identifier (e.g. `12345`). Mirrors the "Nº de documento" field in the app UI and is the canonical field for manually-entered / edited document numbers. Distinct from the free-form `document`.
        document_date:
          type:
            - string
            - "null"
          format: date-time
        period:
          type:
            - string
            - "null"
          description: Group key for recurring/installment series (free-form string used internally to group siblings).
        installment_number:
          type:
            - integer
            - "null"
          description: Position within an installment series. `null` for `type=normal`.
        total_installments:
          type:
            - integer
            - "null"
          description: Total positions in the installment series. `null` for `type=normal`.
        parcels_amount:
          type:
            - string
            - "null"
          description: Aggregate value of the whole installment series ("Total parcelado"), as a 2-decimal string. Mirrored from Fintera. `null` for non-installment rows. Each parcela carries the series total — its own slice is `amount`.
        group_id:
          type:
            - string
            - "null"
          format: uuid
          description: Links sibling rows of a recurring/installment series. `null` for `type=normal`.
        transaction_id:
          type:
            - string
            - "null"
          format: uuid
          description: Settled-payment transaction once `status=paid`. `null` while pending.
        ir_relevant:
          type:
            - boolean
            - "null"
          description: Tax-relevance flag. `null` = unclassified.
        notes:
          type:
            - string
            - "null"
        is_reconciled:
          type: boolean
          description: True when matched against a real bank transaction — a 1:1 manual link (`transaction_id` to a non-auto-created transaction) or membership in an n:m reconciliation group. Settling via the normal pay flow (which generates an auto-created transaction) does NOT set this.
        tax_charges_count:
          type: integer
          minimum: 0
          description: Number of `tax_charges` rows attached. Full list on the detail endpoint.
        cost_center_allocations_count:
          type: integer
          minimum: 0
          description: Number of cost-center allocations (rateio). Full list on the detail endpoint.
        attachments_count:
          type: integer
          minimum: 0
          description: Number of attachments. Full metadata via `/api/v1/attachments`.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
          description: Last-write timestamp (ISO 8601). Auto-bumped on every update.
      required:
        - id
        - company_id
        - description
        - amount
        - interest_amount
        - discount_amount
        - ticket_amount
        - nominal_amount
        - due_date
        - payment_date
        - competence_date
        - type
        - status
        - person_id
        - person_name
        - category_id
        - category_name
        - cost_center_id
        - cost_center_name
        - financial_account_id
        - expected_account_id
        - payment_method_kind
        - operation_kind
        - document
        - document_number
        - document_date
        - period
        - installment_number
        - total_installments
        - parcels_amount
        - group_id
        - transaction_id
        - ir_relevant
        - notes
        - is_reconciled
        - tax_charges_count
        - cost_center_allocations_count
        - attachments_count
        - created_at
        - updated_at
    PayableTaxDataV1:
      type: object
      properties:
        tax_code:
          type: string
          description: DARF (código da receita) · GPS (código de pagamento).
        reference_number:
          type: string
          description: DARF · GRU.
        taxpayer_type:
          type: string
          description: DARF · GPS.
        taxpayer_number:
          type: string
        taxpayer_name:
          type: string
        calculation_date:
          type: string
          description: DARF. `yyyy-mm-dd`.
        competence_date:
          type: string
          description: GPS. `yyyy-mm-dd`.
        expire_at:
          type: string
          description: DARF · GRU · GPS. `yyyy-mm-dd`.
        fine_amount:
          type: number
        interest_amount:
          type: number
        gross_income_amount:
          type: number
          description: DARF.
        total_amount:
          type: number
          description: GRU.
        discount_amount:
          type: number
        other_deduction_amount:
          type: number
        other_addition_amount:
          type: number
        other_entities_amount:
          type: number
          description: GPS.
        monetary_update_amount:
          type: number
        contributor_code:
          type: string
          description: FGTS.
        fgts_code:
          type: string
        collection_code:
          type: string
        pis_pasep_number:
          type: string
        jam_percentage:
          type: number
        connectivity_seal:
          type: string
    PayableCostCenterAllocationV1:
      type: object
      properties:
        cost_center_id:
          type: string
          format: uuid
        cost_center_name:
          type:
            - string
            - "null"
          description: Denormalised name of the cost center. `null` if the join is unavailable.
        percentage:
          type:
            - string
            - "null"
          description: Share of the total, 0–100 with 2 decimals, as a string. `null` when the row was created amount-only.
        amount:
          type:
            - string
            - "null"
          description: Allocated amount in BRL with 2 decimals, as a string. `null` when the row was created percentage-only.
      required:
        - cost_center_id
        - cost_center_name
        - percentage
        - amount
    PayableTaxChargeV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
        tax_id:
          type: string
          format: uuid
        tax_name:
          type:
            - string
            - "null"
          description: Denormalised name of the tax. `null` if the join is unavailable.
        amount:
          type: string
          description: Charge amount in BRL with 2 decimals, as a string.
        withholding:
          type: boolean
          description: "`true` = amount is withheld (deducted); `false` = informational."
      required:
        - id
        - tax_id
        - tax_name
        - amount
        - withholding
    PayableReconciliationV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
        source:
          type: string
          description: 'Origin of the reconciliation group: `"manual"` (user-driven) or `"fintera"` (imported).'
        transaction_ids:
          type: array
          items:
            type: string
            format: uuid
          description: IDs of the bank transactions in the n:m group.
        billing_ids:
          type: array
          items:
            type: string
            format: uuid
          description: IDs of the billings (payables/receivables) in the group, including this payable.
      required:
        - id
        - source
        - transaction_ids
        - billing_ids
    PayableDetailV1:
      allOf:
        - $ref: "#/components/schemas/PayableV1"
        - type: object
          properties:
            barcode:
              type:
                - string
                - "null"
              description: Boleto / tributo / concessionária barcode (linha digitável). Register-only.
            pix_copia_e_cola:
              type:
                - string
                - "null"
              description: PIX copia-e-cola (QR payload).
            pix_key:
              type:
                - string
                - "null"
              description: Beneficiary PIX key (key-based transfer).
            pix_key_type:
              type:
                - string
                - "null"
              enum: &a7
                - cpf
                - cnpj
                - email
                - phone
                - random
            bank_code:
              type:
                - string
                - "null"
              description: Beneficiary bank COMPE. String — leading zeros are significant (`001`).
            bank_ispb:
              type:
                - string
                - "null"
              description: Beneficiary bank ISPB (8 digits, string).
            agency:
              type:
                - string
                - "null"
            agency_digit:
              type:
                - string
                - "null"
            account_number:
              type:
                - string
                - "null"
            account_digit:
              type:
                - string
                - "null"
            transfer_purpose:
              type:
                - string
                - "null"
              description: Purpose of a TED / account PIX transfer.
            tax_kind:
              type:
                - string
                - "null"
              enum: &a8
                - itbi
                - icms
                - iss
                - iptu
                - fgts
                - dare
                - darf
                - gru
                - gps
              description: Tribute kind when `payment_method_kind = tribute`. The barcode goes in `barcode`.
            tax_data:
              allOf:
                - $ref: "#/components/schemas/PayableTaxDataV1"
                - type:
                    - object
                    - "null"
                  description: Structured guia fields, the alternative to `barcode`. `null` when the barcode was used.
            beneficiary_name:
              type:
                - string
                - "null"
              description: Transfer beneficiary name. May differ from the payable's `person` (the bill owner isn't always the payee).
            beneficiary_document:
              type:
                - string
                - "null"
              description: Beneficiary CPF/CNPJ. Register-relaxed (no mask enforced).
            scheduled_to:
              type:
                - string
                - "null"
              format: date-time
              description: Scheduled payment date (ISO 8601). Suggested default is `due_date`.
            kobana_payment_uid:
              type:
                - string
                - "null"
              description: "Execution-state mirror: linked Kobana payment `uid`. Read-only — populated by the execution flow, never via this API."
            kobana_status:
              type:
                - string
                - "null"
              description: Execution-state mirror of the Kobana payment status enum. Read-only.
            kobana_status_updated_at:
              type:
                - string
                - "null"
              format: date-time
              description: When `kobana_status` was last mirrored (ISO 8601). Read-only.
            cost_center_allocations:
              type: array
              items:
                $ref: "#/components/schemas/PayableCostCenterAllocationV1"
              description: Rateio rows (cost-center allocations). Empty when the payable has no split.
            tax_charges:
              type: array
              items:
                $ref: "#/components/schemas/PayableTaxChargeV1"
              description: Withholding/tax charges attached to the payable. Empty when none.
            reconciliation:
              allOf:
                - $ref: "#/components/schemas/PayableReconciliationV1"
                - type:
                    - object
                    - "null"
                  description: The reconciliation group this payable belongs to, or `null` when not reconciled.
          required:
            - barcode
            - pix_copia_e_cola
            - pix_key
            - pix_key_type
            - bank_code
            - bank_ispb
            - agency
            - agency_digit
            - account_number
            - account_digit
            - transfer_purpose
            - tax_kind
            - tax_data
            - beneficiary_name
            - beneficiary_document
            - scheduled_to
            - kobana_payment_uid
            - kobana_status
            - kobana_status_updated_at
            - cost_center_allocations
            - tax_charges
            - reconciliation
    PayableListMeta:
      type: object
      properties:
        next_cursor:
          type:
            - string
            - "null"
          description: Opaque cursor. Pass back as `?cursor=…` to fetch the next page. `null` when there is no next page.
      required:
        - next_cursor
    PayableListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PayableV1"
        meta:
          $ref: "#/components/schemas/PayableListMeta"
      required:
        - data
        - meta
    PayableCostCenterSplitInput:
      type: object
      properties:
        cost_center_id:
          type: string
          format: uuid
        percentage:
          type: number
          minimum: 0
          maximum: 100
          description: Share of the total (0–100). Provide `percentage` and/or `amount`; the server normalises the pair.
        amount:
          type: number
          minimum: 0
          description: Allocated amount in BRL. Provide `percentage` and/or `amount`.
      required:
        - cost_center_id
    PayableTaxChargeInput:
      type: object
      properties:
        tax_id:
          type: string
          format: uuid
        amount:
          type: number
          exclusiveMinimum: 0
        withholding:
          type: boolean
          description: Defaults to `true` (withheld). `false` marks it informational.
      required:
        - tax_id
        - amount
    PayableCreateRequest:
      type: object
      properties:
        company_id:
          type: string
          format: uuid
          description: Owning company UUID. Must belong to the tenant.
        description:
          type: string
          minLength: 1
          maxLength: 500
        amount:
          type: string
          pattern: ^\d+\.\d{1,2}$
          description: Decimal string with 1–2 fractional digits. Always positive — cost polarity is implicit.
          example: "1500.00"
        due_date:
          type: string
          format: date-time
          description: When this payable is due (ISO 8601).
        competence_date:
          type: string
          format: date-time
          description: Accounting period (ISO 8601). Defaults to `due_date` when omitted.
        type:
          type: string
          enum: &a9
            - normal
            - recurring
            - installment
          description: Defaults to `normal`. `recurring` requires `recurrence_period`; `installment` requires `total_installments` (≥ 2).
        person_id:
          type:
            - string
            - "null"
          format: uuid
        person_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `person_id`. Find-or-create with `supplier` polarity, scoped to (workspace, company). Mutually exclusive with `person_id`.
        category_id:
          type:
            - string
            - "null"
          format: uuid
        category_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `category_id`. Find-or-create with `debit` polarity. Mutually exclusive with `category_id`.
        cost_center_id:
          type:
            - string
            - "null"
          format: uuid
        cost_center_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `cost_center_id`. Find-or-create with `cost` polarity. Mutually exclusive with `cost_center_id`.
        payment_method_kind:
          type:
            - string
            - "null"
          enum: *a6
        operation_kind:
          type:
            - string
            - "null"
          enum: &a10
            - payment
            - transfer
        financial_account_id:
          type:
            - string
            - "null"
          format: uuid
          description: Optional pre-set settlement account. Must belong to the tenant and match `company_id`.
        expected_account_id:
          type:
            - string
            - "null"
          format: uuid
          description: Projected settlement account (forecast). Independent of `financial_account_id`.
        document:
          type:
            - string
            - "null"
          maxLength: 100
          description: Free-form reference of the source document (e.g. `NF-12345`, `BOL-001`); what the `search` / `documentContains` filters match. Distinct from `document_number`.
        document_date:
          type:
            - string
            - "null"
          format: date-time
        document_number:
          type:
            - string
            - "null"
          maxLength: 100
          description: Structured document number — the bare identifier (e.g. `12345`). Mirrors the "Nº de documento" field in the app UI; the canonical field for manually-entered document numbers. Distinct from the free-form `document`.
        notes:
          type:
            - string
            - "null"
          maxLength: 2000
        ir_relevant:
          type:
            - boolean
            - "null"
        recurrence_period:
          type: string
          maxLength: 50
          description: Required for `type=recurring` (`weekly` | `biweekly` | `monthly` | `quarterly` | `yearly`).
        total_installments:
          type: integer
          minimum: 2
          description: Required for `type=installment` — number of parcelas (≥ 2).
        cost_centers:
          type: array
          items:
            $ref: "#/components/schemas/PayableCostCenterSplitInput"
          description: Rateio. Replace-all set; the server validates Σ against `amount` (0.01 tolerance). Spread across installments.
        tax_charges:
          type: array
          items:
            $ref: "#/components/schemas/PayableTaxChargeInput"
          description: Withholding/tax charges. Replace-all set. Attached to the first record of a series.
        barcode:
          type:
            - string
            - "null"
          maxLength: 255
        pix_copia_e_cola:
          type:
            - string
            - "null"
        pix_key:
          type:
            - string
            - "null"
          maxLength: 255
        pix_key_type:
          type:
            - string
            - "null"
          enum: *a7
        bank_code:
          type:
            - string
            - "null"
          maxLength: 30
          description: Beneficiary bank COMPE. String — leading zeros significant.
        bank_ispb:
          type:
            - string
            - "null"
          maxLength: 8
        agency:
          type:
            - string
            - "null"
          maxLength: 50
        agency_digit:
          type:
            - string
            - "null"
          maxLength: 5
        account_number:
          type:
            - string
            - "null"
          maxLength: 50
        account_digit:
          type:
            - string
            - "null"
          maxLength: 5
        transfer_purpose:
          type:
            - string
            - "null"
          maxLength: 100
        tax_kind:
          type:
            - string
            - "null"
          enum: *a8
          description: Tribute kind. Set with `payment_method_kind = tribute`; the barcode goes in `barcode`.
        tax_data:
          allOf:
            - $ref: "#/components/schemas/PayableTaxDataV1"
            - type:
                - object
                - "null"
              description: Structured guia fields, the alternative to `barcode`.
        beneficiary_name:
          type:
            - string
            - "null"
          maxLength: 255
          description: Transfer beneficiary name. May differ from the payable's `person`.
        beneficiary_document:
          type:
            - string
            - "null"
          maxLength: 20
          description: Beneficiary CPF/CNPJ.
        scheduled_to:
          type:
            - string
            - "null"
          format: date-time
          description: Scheduled payment date (ISO 8601). Suggested default is `due_date`.
      required:
        - company_id
        - description
        - amount
        - due_date
    PayableUpdateRequest:
      type: object
      properties:
        description:
          type: string
          minLength: 1
          maxLength: 500
        amount:
          type: string
          pattern: ^\d+\.\d{1,2}$
          description: Decimal string with 1–2 fractional digits. Always positive — cost polarity is implicit.
          example: "1500.00"
        due_date:
          type: string
          format: date-time
          description: When this payable is due (ISO 8601).
        competence_date:
          type: string
          format: date-time
          description: Accounting period (ISO 8601). Defaults to `due_date` when omitted.
        type:
          type: string
          enum: *a9
          description: Defaults to `normal`. `recurring` requires `recurrence_period`; `installment` requires `total_installments` (≥ 2).
        person_id:
          type:
            - string
            - "null"
          format: uuid
        person_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `person_id`. Find-or-create with `supplier` polarity, scoped to (workspace, company). Mutually exclusive with `person_id`.
        category_id:
          type:
            - string
            - "null"
          format: uuid
        category_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `category_id`. Find-or-create with `debit` polarity. Mutually exclusive with `category_id`.
        cost_center_id:
          type:
            - string
            - "null"
          format: uuid
        cost_center_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `cost_center_id`. Find-or-create with `cost` polarity. Mutually exclusive with `cost_center_id`.
        payment_method_kind:
          type:
            - string
            - "null"
          enum: *a6
        operation_kind:
          type:
            - string
            - "null"
          enum: *a10
        financial_account_id:
          type:
            - string
            - "null"
          format: uuid
          description: Optional pre-set settlement account. Must belong to the tenant and match `company_id`.
        expected_account_id:
          type:
            - string
            - "null"
          format: uuid
          description: Projected settlement account (forecast). Independent of `financial_account_id`.
        document:
          type:
            - string
            - "null"
          maxLength: 100
          description: Free-form reference of the source document (e.g. `NF-12345`, `BOL-001`); what the `search` / `documentContains` filters match. Distinct from `document_number`.
        document_date:
          type:
            - string
            - "null"
          format: date-time
        document_number:
          type:
            - string
            - "null"
          maxLength: 100
          description: Structured document number — the bare identifier (e.g. `12345`). Mirrors the "Nº de documento" field in the app UI; the canonical field for manually-entered document numbers. Distinct from the free-form `document`.
        notes:
          type:
            - string
            - "null"
          maxLength: 2000
        ir_relevant:
          type:
            - boolean
            - "null"
        recurrence_period:
          type: string
          maxLength: 50
          description: Required for `type=recurring` (`weekly` | `biweekly` | `monthly` | `quarterly` | `yearly`).
        total_installments:
          type: integer
          minimum: 2
          description: Required for `type=installment` — number of parcelas (≥ 2).
        cost_centers:
          type: array
          items:
            $ref: "#/components/schemas/PayableCostCenterSplitInput"
          description: Rateio. Replace-all set; the server validates Σ against `amount` (0.01 tolerance). Spread across installments.
        tax_charges:
          type: array
          items:
            $ref: "#/components/schemas/PayableTaxChargeInput"
          description: Withholding/tax charges. Replace-all set. Attached to the first record of a series.
        barcode:
          type:
            - string
            - "null"
          maxLength: 255
        pix_copia_e_cola:
          type:
            - string
            - "null"
        pix_key:
          type:
            - string
            - "null"
          maxLength: 255
        pix_key_type:
          type:
            - string
            - "null"
          enum: *a7
        bank_code:
          type:
            - string
            - "null"
          maxLength: 30
          description: Beneficiary bank COMPE. String — leading zeros significant.
        bank_ispb:
          type:
            - string
            - "null"
          maxLength: 8
        agency:
          type:
            - string
            - "null"
          maxLength: 50
        agency_digit:
          type:
            - string
            - "null"
          maxLength: 5
        account_number:
          type:
            - string
            - "null"
          maxLength: 50
        account_digit:
          type:
            - string
            - "null"
          maxLength: 5
        transfer_purpose:
          type:
            - string
            - "null"
          maxLength: 100
        tax_kind:
          type:
            - string
            - "null"
          enum: *a8
          description: Tribute kind. Set with `payment_method_kind = tribute`; the barcode goes in `barcode`.
        tax_data:
          allOf:
            - $ref: "#/components/schemas/PayableTaxDataV1"
            - type:
                - object
                - "null"
              description: Structured guia fields, the alternative to `barcode`.
        beneficiary_name:
          type:
            - string
            - "null"
          maxLength: 255
          description: Transfer beneficiary name. May differ from the payable's `person`.
        beneficiary_document:
          type:
            - string
            - "null"
          maxLength: 20
          description: Beneficiary CPF/CNPJ.
        scheduled_to:
          type:
            - string
            - "null"
          format: date-time
          description: Scheduled payment date (ISO 8601). Suggested default is `due_date`.
        status:
          type: string
          enum:
            - pending
            - paid
            - cancelled
          description: Settle (`paid`), revert (`pending`) or cancel (`cancelled`). `draft` is never accepted.
        payment_date:
          type:
            - string
            - "null"
          format: date-time
          description: Settlement date (ISO 8601). Required when moving to `status=paid`.
        interest_amount:
          type:
            - number
            - "null"
          minimum: 0
          description: Late-payment interest paid at settlement (BRL).
        discount_amount:
          type:
            - number
            - "null"
          minimum: 0
          description: Discount obtained at settlement (BRL).
        ticket_amount:
          type:
            - number
            - "null"
          minimum: 0
          description: '"Valor liquidado" — the actual amount that left the bank (BRL).'
    ReceivableV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 8c8b4c4f-2e0e-4f6e-9b8a-2a1c2b3d4e5f
        company_id:
          type: string
          format: uuid
        description:
          type: string
          description: Short label written by the user.
          example: "NF #1234 — venda janeiro"
        amount:
          type: string
          description: Headline value as a decimal string with 2 fractional digits. Positive.
          example: "1500.00"
        interest_amount:
          type:
            - string
            - "null"
          description: Late-payment interest collected at receive time, as a 2-decimal string. `null` when not applicable.
        discount_amount:
          type:
            - string
            - "null"
          description: Discount granted at receive time, as a 2-decimal string. `null` when not applicable.
        ticket_amount:
          type:
            - string
            - "null"
          description: '"Valor liquidado" — actual amount that hit the bank. May differ from `nominal_amount` due to processor fees. `null` when not applicable.'
        nominal_amount:
          type: string
          description: "Computed: `amount + (interest_amount ?? 0) - (discount_amount ?? 0)`. Reproduced per request — not persisted."
          example: "1525.00"
        due_date:
          type: string
          format: date-time
        payment_date:
          type:
            - string
            - "null"
          format: date-time
          description: When this receivable was actually received. Column name kept as `payment_date` to match the schema; semantically "received at". `null` while pending. Reflects `status=received` 1:1.
        competence_date:
          type: string
          format: date-time
        type:
          type: string
          enum:
            - normal
            - recurring
            - installment
        status:
          type: string
          enum:
            - pending
            - received
            - cancelled
          description: 'Raw status. Derive overdue client-side: `status === "pending" AND due_date < today`.'
        person_id:
          type:
            - string
            - "null"
          format: uuid
        person_name:
          type:
            - string
            - "null"
          description: Denormalised display name of the related person. `null` when `person_id` is null.
        category_id:
          type:
            - string
            - "null"
          format: uuid
        category_name:
          type:
            - string
            - "null"
        cost_center_id:
          type:
            - string
            - "null"
          format: uuid
          description: Primary cost center on the row. Splits (rateio) live in `cost_center_allocations[]` on the detail endpoint.
        cost_center_name:
          type:
            - string
            - "null"
        financial_account_id:
          type:
            - string
            - "null"
          format: uuid
        expected_account_id:
          type:
            - string
            - "null"
          format: uuid
          description: Projected settlement account pre-receive, used by forecast widgets.
        payment_method_kind:
          type:
            - string
            - "null"
          enum: *a1
        document:
          type:
            - string
            - "null"
          description: Free-form reference of the source document (e.g. `NF-12345`, `BOL-001`). Populated by the Fintera / CSV importer and matched by the listing `search` and `documentContains` filters. Distinct from `document_number`.
        document_number:
          type:
            - string
            - "null"
          description: Structured document number — the bare identifier (e.g. `12345`). Mirrors the "Nº de documento" field in the app UI and is the canonical field for manually-entered / edited document numbers. Distinct from the free-form `document`.
        document_date:
          type:
            - string
            - "null"
          format: date-time
        period:
          type:
            - string
            - "null"
        installment_number:
          type:
            - integer
            - "null"
        total_installments:
          type:
            - integer
            - "null"
        parcels_amount:
          type:
            - string
            - "null"
          description: Aggregate value of the whole installment series ("Total parcelado"), as a 2-decimal string. Mirrored from Fintera. `null` for non-installment rows. Each parcela carries the series total — its own slice is `amount`.
        group_id:
          type:
            - string
            - "null"
          format: uuid
        transaction_id:
          type:
            - string
            - "null"
          format: uuid
          description: Settled-receipt transaction once `status=received`. `null` while pending.
        ir_relevant:
          type:
            - boolean
            - "null"
        notes:
          type:
            - string
            - "null"
        is_reconciled:
          type: boolean
          description: True when matched against a real bank transaction — a 1:1 manual link (`transaction_id` to a non-auto-created transaction) or membership in an n:m reconciliation group. Receiving via the normal flow (auto-created transaction) does NOT set this.
        tax_charges_count:
          type: integer
          minimum: 0
        cost_center_allocations_count:
          type: integer
          minimum: 0
        attachments_count:
          type: integer
          minimum: 0
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
          description: Last-write timestamp (ISO 8601). Auto-bumped on every update.
      required:
        - id
        - company_id
        - description
        - amount
        - interest_amount
        - discount_amount
        - ticket_amount
        - nominal_amount
        - due_date
        - payment_date
        - competence_date
        - type
        - status
        - person_id
        - person_name
        - category_id
        - category_name
        - cost_center_id
        - cost_center_name
        - financial_account_id
        - expected_account_id
        - payment_method_kind
        - document
        - document_number
        - document_date
        - period
        - installment_number
        - total_installments
        - parcels_amount
        - group_id
        - transaction_id
        - ir_relevant
        - notes
        - is_reconciled
        - tax_charges_count
        - cost_center_allocations_count
        - attachments_count
        - created_at
        - updated_at
    ReceivableCostCenterAllocationV1:
      type: object
      properties:
        cost_center_id:
          type: string
          format: uuid
        cost_center_name:
          type:
            - string
            - "null"
        percentage:
          type:
            - string
            - "null"
          description: Share of the total, 0–100 with 2 decimals, as a string. `null` when created amount-only.
        amount:
          type:
            - string
            - "null"
          description: Allocated amount in BRL with 2 decimals, as a string. `null` when created percentage-only.
      required:
        - cost_center_id
        - cost_center_name
        - percentage
        - amount
    ReceivableTaxChargeV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
        tax_id:
          type: string
          format: uuid
        tax_name:
          type:
            - string
            - "null"
        amount:
          type: string
        withholding:
          type: boolean
      required:
        - id
        - tax_id
        - tax_name
        - amount
        - withholding
    ReceivableReconciliationV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
        source:
          type: string
          description: '`"manual"` (user-driven) or `"fintera"` (imported).'
        transaction_ids:
          type: array
          items:
            type: string
            format: uuid
        billing_ids:
          type: array
          items:
            type: string
            format: uuid
      required:
        - id
        - source
        - transaction_ids
        - billing_ids
    ReceivableDetailV1:
      allOf:
        - $ref: "#/components/schemas/ReceivableV1"
        - type: object
          properties:
            cost_center_allocations:
              type: array
              items:
                $ref: "#/components/schemas/ReceivableCostCenterAllocationV1"
              description: Rateio rows (cost-center allocations). Empty when no split.
            tax_charges:
              type: array
              items:
                $ref: "#/components/schemas/ReceivableTaxChargeV1"
              description: Withholding/tax charges attached to the receivable. Empty when none.
            reconciliation:
              allOf:
                - $ref: "#/components/schemas/ReceivableReconciliationV1"
                - type:
                    - object
                    - "null"
                  description: The reconciliation group this receivable belongs to, or `null` when not reconciled.
          required:
            - cost_center_allocations
            - tax_charges
            - reconciliation
    ReceivableListMeta:
      type: object
      properties:
        next_cursor:
          type:
            - string
            - "null"
          description: Opaque cursor. Pass back as `?cursor=…` to fetch the next page. `null` when there is no next page.
      required:
        - next_cursor
    ReceivableListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ReceivableV1"
        meta:
          $ref: "#/components/schemas/ReceivableListMeta"
      required:
        - data
        - meta
    ReceivableCostCenterSplitInput:
      type: object
      properties:
        cost_center_id:
          type: string
          format: uuid
        percentage:
          type: number
          minimum: 0
          maximum: 100
          description: Share of the total (0–100). Provide `percentage` and/or `amount`.
        amount:
          type: number
          minimum: 0
          description: Allocated amount in BRL. Provide `percentage` and/or `amount`.
      required:
        - cost_center_id
    ReceivableTaxChargeInput:
      type: object
      properties:
        tax_id:
          type: string
          format: uuid
        amount:
          type: number
          exclusiveMinimum: 0
        withholding:
          type: boolean
          description: Defaults to `true` (withheld).
      required:
        - tax_id
        - amount
    ReceivableCreateRequest:
      type: object
      properties:
        company_id:
          type: string
          format: uuid
          description: Owning company UUID. Must belong to the tenant.
        description:
          type: string
          minLength: 1
          maxLength: 500
        amount:
          type: string
          pattern: ^\d+\.\d{1,2}$
          description: Decimal string with 1–2 fractional digits. Always positive.
          example: "1500.00"
        due_date:
          type: string
          format: date-time
        competence_date:
          type: string
          format: date-time
          description: Accounting period (ISO 8601). Defaults to `due_date` when omitted.
        type:
          type: string
          enum: &a11
            - normal
            - recurring
            - installment
          description: Defaults to `normal`. `recurring` requires `recurrence_period`; `installment` requires `total_installments` (≥ 2).
        person_id:
          type:
            - string
            - "null"
          format: uuid
        person_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `person_id`. Find-or-create with `customer` polarity, scoped to (workspace, company). Mutually exclusive with `person_id`.
        category_id:
          type:
            - string
            - "null"
          format: uuid
        category_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `category_id`. Find-or-create with `credit` polarity. Mutually exclusive with `category_id`.
        cost_center_id:
          type:
            - string
            - "null"
          format: uuid
        cost_center_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `cost_center_id`. Find-or-create with `revenue` polarity. Mutually exclusive with `cost_center_id`.
        payment_method_kind:
          type:
            - string
            - "null"
          enum: *a1
        financial_account_id:
          type:
            - string
            - "null"
          format: uuid
          description: Optional pre-set receipt account. Must belong to the tenant and match `company_id`.
        expected_account_id:
          type:
            - string
            - "null"
          format: uuid
          description: Projected receipt account (forecast). Independent of `financial_account_id`.
        document:
          type:
            - string
            - "null"
          maxLength: 100
          description: Free-form reference of the source document (e.g. `NF-12345`, `BOL-001`); what the `search` / `documentContains` filters match. Distinct from `document_number`.
        document_date:
          type:
            - string
            - "null"
          format: date-time
        document_number:
          type:
            - string
            - "null"
          maxLength: 100
          description: Structured document number — the bare identifier (e.g. `12345`). Mirrors the "Nº de documento" field in the app UI; the canonical field for manually-entered document numbers. Distinct from the free-form `document`.
        notes:
          type:
            - string
            - "null"
          maxLength: 2000
        ir_relevant:
          type:
            - boolean
            - "null"
        recurrence_period:
          type: string
          maxLength: 50
          description: Required for `type=recurring` (`weekly` | `biweekly` | `monthly` | `quarterly` | `yearly`).
        total_installments:
          type: integer
          minimum: 2
          description: Required for `type=installment` (≥ 2).
        cost_centers:
          type: array
          items:
            $ref: "#/components/schemas/ReceivableCostCenterSplitInput"
          description: Rateio. Replace-all set; Σ validated against `amount` (0.01 tolerance).
        tax_charges:
          type: array
          items:
            $ref: "#/components/schemas/ReceivableTaxChargeInput"
          description: Withholding/tax charges. Replace-all set.
      required:
        - company_id
        - description
        - amount
        - due_date
    ReceivableUpdateRequest:
      type: object
      properties:
        description:
          type: string
          minLength: 1
          maxLength: 500
        amount:
          type: string
          pattern: ^\d+\.\d{1,2}$
          description: Decimal string with 1–2 fractional digits. Always positive.
          example: "1500.00"
        due_date:
          type: string
          format: date-time
        competence_date:
          type: string
          format: date-time
          description: Accounting period (ISO 8601). Defaults to `due_date` when omitted.
        type:
          type: string
          enum: *a11
          description: Defaults to `normal`. `recurring` requires `recurrence_period`; `installment` requires `total_installments` (≥ 2).
        person_id:
          type:
            - string
            - "null"
          format: uuid
        person_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `person_id`. Find-or-create with `customer` polarity, scoped to (workspace, company). Mutually exclusive with `person_id`.
        category_id:
          type:
            - string
            - "null"
          format: uuid
        category_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `category_id`. Find-or-create with `credit` polarity. Mutually exclusive with `category_id`.
        cost_center_id:
          type:
            - string
            - "null"
          format: uuid
        cost_center_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `cost_center_id`. Find-or-create with `revenue` polarity. Mutually exclusive with `cost_center_id`.
        payment_method_kind:
          type:
            - string
            - "null"
          enum: *a1
        financial_account_id:
          type:
            - string
            - "null"
          format: uuid
          description: Optional pre-set receipt account. Must belong to the tenant and match `company_id`.
        expected_account_id:
          type:
            - string
            - "null"
          format: uuid
          description: Projected receipt account (forecast). Independent of `financial_account_id`.
        document:
          type:
            - string
            - "null"
          maxLength: 100
          description: Free-form reference of the source document (e.g. `NF-12345`, `BOL-001`); what the `search` / `documentContains` filters match. Distinct from `document_number`.
        document_date:
          type:
            - string
            - "null"
          format: date-time
        document_number:
          type:
            - string
            - "null"
          maxLength: 100
          description: Structured document number — the bare identifier (e.g. `12345`). Mirrors the "Nº de documento" field in the app UI; the canonical field for manually-entered document numbers. Distinct from the free-form `document`.
        notes:
          type:
            - string
            - "null"
          maxLength: 2000
        ir_relevant:
          type:
            - boolean
            - "null"
        recurrence_period:
          type: string
          maxLength: 50
          description: Required for `type=recurring` (`weekly` | `biweekly` | `monthly` | `quarterly` | `yearly`).
        total_installments:
          type: integer
          minimum: 2
          description: Required for `type=installment` (≥ 2).
        cost_centers:
          type: array
          items:
            $ref: "#/components/schemas/ReceivableCostCenterSplitInput"
          description: Rateio. Replace-all set; Σ validated against `amount` (0.01 tolerance).
        tax_charges:
          type: array
          items:
            $ref: "#/components/schemas/ReceivableTaxChargeInput"
          description: Withholding/tax charges. Replace-all set.
        status:
          type: string
          enum:
            - pending
            - received
            - cancelled
          description: Settle (`received`), revert (`pending`) or cancel (`cancelled`). `draft` is never accepted.
        payment_date:
          type:
            - string
            - "null"
          format: date-time
          description: Receipt date (ISO 8601). Required when moving to `status=received`.
        interest_amount:
          type:
            - number
            - "null"
          minimum: 0
          description: Late-payment interest collected at receive time (BRL).
        discount_amount:
          type:
            - number
            - "null"
          minimum: 0
          description: Discount granted at receive time (BRL).
        ticket_amount:
          type:
            - number
            - "null"
          minimum: 0
          description: '"Valor liquidado" — the actual amount that hit the bank (BRL).'
    AttachmentV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 8c8b4c4f-2e0e-4f6e-9b8a-2a1c2b3d4e5f
        file_name:
          type: string
          description: Original file name at upload time.
          example: nota-fiscal-1234.pdf
        title:
          type:
            - string
            - "null"
          description: Optional user-supplied label that overrides `file_name` in UIs. `null` when unset.
        mime_type:
          type: string
          description: MIME type as captured at upload time.
          example: application/pdf
        file_size:
          type: integer
          minimum: 0
          description: Size in bytes. Capped at the upload-time `BILLING_IMPORT_MAX_FILE_BYTES` limit (10 MB by default).
          example: 184320
        uploaded_by:
          type: string
          description: Who uploaded the file. Free-form (no FK to a users table) — the in-app uploader records the user email; uploads via the public API record `api:<sub>` (the token's `sub`/account public id).
        uploaded_at:
          type: string
          format: date-time
        download_url:
          type: string
          description: "Absolute URL to fetch the file bytes. Same Bearer JWT required (scope `finance.attachments`). Streams with `Content-Disposition: inline` for PDF/images, `attachment` otherwise."
          example: https://finance.kobana.example/api/v1/attachments/8c8b4c4f-2e0e-4f6e-9b8a-2a1c2b3d4e5f/download
        linked_payables_count:
          type: integer
          minimum: 0
          description: How many payables reference this attachment.
        linked_receivables_count:
          type: integer
          minimum: 0
          description: How many receivables reference this attachment.
        linked_transactions_count:
          type: integer
          minimum: 0
          description: How many transactions reference this attachment.
      required:
        - id
        - file_name
        - title
        - mime_type
        - file_size
        - uploaded_by
        - uploaded_at
        - download_url
        - linked_payables_count
        - linked_receivables_count
        - linked_transactions_count
    AttachmentListMeta:
      type: object
      properties:
        next_cursor:
          type:
            - string
            - "null"
          description: Opaque cursor. Pass back as `?cursor=…` to fetch the next page. `null` when there is no next page.
      required:
        - next_cursor
    AttachmentListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/AttachmentV1"
        meta:
          $ref: "#/components/schemas/AttachmentListMeta"
      required:
        - data
        - meta
    AttachmentUploadRequest:
      type: object
      properties:
        file:
          type: string
          format: binary
          description: "The file bytes (multipart field `file`). Allowed types: pdf, png, jpg/jpeg, xml, ofx, csv, xlsx. Max 10 MB. For PDF/PNG/JPEG the declared MIME must match the payload's magic bytes (mismatch → 422)."
        title:
          type: string
          description: Optional label that overrides `file_name` in UIs (multipart field `title`).
          example: Nota fiscal de junho
      required:
        - file
    AttachmentLinkRequest:
      type: object
      properties:
        type:
          type: string
          enum:
            - payable
            - receivable
            - transaction
          description: Kind of parent record to (un)link the attachment to.
          example: payable
        id:
          type: string
          format: uuid
          description: Id of the parent payable / receivable / transaction within the same workspace.
          example: 8c8b4c4f-2e0e-4f6e-9b8a-2a1c2b3d4e5f
      required:
        - type
        - id
  parameters: {}
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: "Send `Authorization: Bearer <jwt>`. Token format: HS512, claims = { sub, jti, scope, iss, aud, exp }."
paths:
  /accounts:
    get:
      operationId: listAccounts
      summary: List financial accounts
      description: Returns a cursor-paginated page of financial accounts for the workspace tied to the token's `sub` claim. Accounts may belong to a specific company or be workspace-level (`company_id` nullable). **The current balance is NOT returned by this listing** — the stored `balance` column is a write-only seed; the real balance is derived from the transactions ledger and will be available on the detail endpoint. Requires scope `finance.accounts`.
      tags:
        - Accounts
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            description: Filter by owning company UUID. Accepts `[in]=uuid1,uuid2`. Workspace-level accounts (`company_id IS NULL`) are returned only when this filter is omitted.
            example: 62057dab-0f98-4020-ae1e-2c0803251b1e
          required: false
          name: company_id
          in: query
        - schema:
            type: string
            enum:
              - checking
              - savings
              - cash
              - investment
              - credit
            description: Account kind. Accepts `[in]=checking,savings`.
          required: false
          name: type
          in: query
        - schema:
            type: string
            format: uuid
            description: Filter by bank UUID. Accepts `[in]` for multi-bank lookups.
          required: false
          name: bank_id
          in: query
        - schema:
            type: string
            enum:
              - "true"
              - "false"
            description: Filter by active flag.
          required: false
          name: active
          in: query
        - schema:
            type: string
            description: Filter by exact account name. Accepts `[in]`.
          required: false
          name: name
          in: query
        - schema:
            type: string
            enum:
              - name
              - -name
              - created_at
              - -created_at
              - type
              - -type
            description: "Sort field. Prefix with `-` for descending. Default: `name` (asc)."
          required: false
          name: sort
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            description: Page size. Default 25, max 100.
            example: 25
          required: false
          name: limit
          in: query
        - schema:
            type: string
            description: Opaque cursor for the next page. Round-trip `meta.next_cursor` from the previous response.
          required: false
          name: cursor
          in: query
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: &a12
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Page of accounts plus pagination metadata. No `balance` field by design.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountListResponse"
        "400":
          description: Malformed query (unknown filter / operator, invalid limit, bad cursor).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token authenticated but lacks the `finance.accounts` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    post:
      operationId: createAccount
      summary: Create a financial account
      description: Creates a financial account. Requires scope `finance.accounts.write`. `workspace_id` is taken from the token. `company_id` is required (accounts must belong to a company; workspace-level accounts exist from imports but aren't creatable via v1). `balance` is an opening seed (default `0.00`) — it is NOT the live balance (that is computed from transactions and returned on the detail endpoint) and cannot be updated. Pass an `Idempotency-Key` header on retries.
      tags:
        - Accounts
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: "Opaque idempotency token. Same key + same body replays the cached response with `X-Idempotency-Replayed: true`; same key + different body returns 409. Scoped per token `sub`."
          required: false
          name: Idempotency-Key
          in: header
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AccountCreateRequest"
            examples:
              bank:
                summary: A bank account with an opening balance
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  name: Conta Corrente Itaú
                  type: checking
                  bank_id: 22222222-2222-2222-2222-222222222222
                  agency: "1234"
                  account_number: "56789"
                  account_digit: "0"
                  balance: "1500.00"
              cash:
                summary: A cash account
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  name: Caixa
                  type: cash
      responses:
        "201":
          description: Account created. The body is the detail shape (includes the computed `balance`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountDetailV1"
        "400":
          description: Malformed JSON body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.accounts.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "409":
          description: "`Idempotency-Key` reused with a different body."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure, or `company_id` not in the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /accounts/{id}:
    get:
      operationId: getAccount
      summary: Get a financial account by id
      description: "Returns the account plus the **computed `balance`** (live, derived from posted transactions — not the creation seed; #13). The listing still omits balance. Returns the same shape as the rows from the list endpoint. Requires scope `finance.accounts`. 404 covers both \"doesn't exist\" and \"not in the tenant\" — the API does not distinguish."
      tags:
        - Accounts
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Get a financial account by id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountDetailV1"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.accounts` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Account not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    patch:
      operationId: updateAccount
      summary: Update a financial account
      description: "Partial update. Requires scope `finance.accounts.write`. `balance` is never settable (the live balance is computed). Set `active: false` to soft-deactivate (existing transactions stay valid)."
      tags:
        - Accounts
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AccountUpdateRequest"
            examples:
              deactivate:
                summary: Soft-deactivate
                value:
                  active: false
              rename:
                summary: Rename + toggle reconcile
                value:
                  name: Conta Itaú (PJ)
                  disable_reconcile: true
      responses:
        "200":
          description: Updated account (detail shape with computed balance).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountDetailV1"
        "400":
          description: Malformed JSON body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.accounts.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Account not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure, or `company_id` not in the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    delete:
      operationId: deleteAccount
      summary: Delete a financial account
      description: "Deletes an account. Requires scope `finance.accounts.write`. Returns 422 (`reason: in_use`, with a `usage` breakdown) when the account has any transaction, payable or receivable — deleting would cascade-delete its transactions, so soft-deactivate via `PATCH { active: false }` instead. Calling DELETE twice returns 204 then 404."
      tags:
        - Accounts
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "204":
          description: Account deleted. No response body.
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.accounts.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Account not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: "The account is in use (`reason: in_use`)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /attachments:
    get:
      operationId: listAttachments
      summary: List attachments
      description: Returns a cursor-paginated page of attachment metadata for the workspace tied to the token's `sub` claim. Each row carries an absolute `download_url` pointing at the download endpoint; fetching the bytes requires the same Bearer JWT (scope `finance.attachments`). Aggregate counts (`linked_payables_count` etc.) expose how many parent rows reference each blob without forcing the client to call every parent endpoint. Default sort is `uploaded_at` descending (newest first). Requires scope `finance.attachments`.
      tags:
        - Attachments
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: Filter by exact MIME type. Accepts `[in]=application/pdf,image/png`.
            example: application/pdf
          required: false
          name: mime_type
          in: query
        - schema:
            type: string
            description: Filter by exact file name. Accepts `[in]`.
          required: false
          name: file_name
          in: query
        - schema:
            type: string
            description: Filter by uploader email. Accepts `[in]`.
          required: false
          name: uploaded_by
          in: query
        - schema:
            type: string
            description: "ISO date filter on upload time. Range operators: `[gte]`, `[lte]`, `[gt]`, `[lt]`."
          required: false
          name: uploaded_at
          in: query
        - schema:
            type: string
            format: uuid
            description: Return only attachments linked to this payable. Single UUID via eq.
          required: false
          name: linked_to_payable_id
          in: query
        - schema:
            type: string
            format: uuid
            description: Return only attachments linked to this receivable. Single UUID via eq.
          required: false
          name: linked_to_receivable_id
          in: query
        - schema:
            type: string
            format: uuid
            description: Return only attachments linked to this transaction. Single UUID via eq.
          required: false
          name: linked_to_transaction_id
          in: query
        - schema:
            type: string
            enum:
              - uploaded_at
              - -uploaded_at
              - file_name
              - -file_name
              - file_size
              - -file_size
            description: "Sort field. Prefix with `-` for descending. Default: `uploaded_at desc` (newest first)."
          required: false
          name: sort
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            description: Page size. Default 25, max 100.
          required: false
          name: limit
          in: query
        - schema:
            type: string
            description: Opaque cursor for the next page. Round-trip `meta.next_cursor` from the previous response.
          required: false
          name: cursor
          in: query
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Page of attachments plus pagination metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AttachmentListResponse"
        "400":
          description: Malformed query (unknown filter / operator, invalid limit, bad cursor).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token authenticated but lacks the `finance.attachments` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    post:
      operationId: uploadAttachment
      summary: Upload an attachment
      description: "Uploads a file to the workspace blob registry. Send as `multipart/form-data` with a `file` part and an optional `title`. Requires scope `finance.attachments.write`. Allowed types: pdf, png, jpg/jpeg, xml, ofx, csv, xlsx; max 10 MB. For PDF/PNG/JPEG the declared MIME must match the payload's magic bytes (mismatch → 422). The response is the same metadata shape as `GET /attachments/{id}`; link counts start at 0 — associate the file via `POST /attachments/{id}/links`. No `Idempotency-Key` support on upload."
      tags:
        - Attachments
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/AttachmentUploadRequest"
      responses:
        "201":
          description: Attachment uploaded. The `Location` header points at the new row.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AttachmentV1"
        "400":
          description: Body is not valid multipart/form-data.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.attachments.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Missing `file` part, file too large, disallowed MIME type, or magic bytes that don't match the declared type.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
      parameters:
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
  /attachments/{id}/download:
    get:
      operationId: downloadAttachment
      summary: Download attachment bytes
      description: "Streams the file bytes for the attachment back to the caller. Same Bearer JWT (scope `finance.attachments`) as the listing endpoint. `Content-Type` reflects the stored MIME; `Content-Disposition` is `inline` for PDF / images and `attachment` for other types. No caching — `Cache-Control: private, no-store`."
      tags:
        - Attachments
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: File bytes (binary stream). See `Content-Type` and `Content-Disposition` headers.
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token authenticated but lacks the `finance.attachments` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Attachment not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /attachments/{id}:
    get:
      operationId: getAttachment
      summary: Get attachment metadata by id
      description: Returns the metadata row including `download_url`. Fetching the bytes still requires `GET /attachments/{id}/download` with the same scope. Returns the same shape as the rows from the list endpoint. Requires scope `finance.attachments`. 404 covers both "doesn't exist" and "not in the tenant" — the API does not distinguish.
      tags:
        - Attachments
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Get attachment metadata by id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AttachmentV1"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.attachments` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Attachment not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    delete:
      operationId: deleteAttachment
      summary: Delete an attachment
      description: Deletes the attachment row and its stored bytes. Requires scope `finance.attachments.write`. Returns 409 (`conflict`, with per-entity link counts in `details`) when the file is still linked to any payable / receivable / transaction — unlink it first via `DELETE /attachments/{id}/links`. Calling DELETE twice returns 204 then 404.
      tags:
        - Attachments
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "204":
          description: Attachment deleted. No response body.
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.attachments.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Attachment not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "409":
          description: The attachment is still linked to one or more records (`details` carries the counts).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /attachments/{id}/links:
    post:
      operationId: linkAttachment
      summary: Link an attachment to a record
      description: Associates an existing attachment with a payable / receivable / transaction. The `{id}` path segment is the attachment; the body identifies the parent. Requires scope `finance.attachments.write`. Returns 409 when the association already exists, 404 when either the attachment or the parent is outside the tenant.
      tags:
        - Attachments
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AttachmentLinkRequest"
            examples:
              payable:
                summary: Link to a payable
                value:
                  type: payable
                  id: 8c8b4c4f-2e0e-4f6e-9b8a-2a1c2b3d4e5f
              transaction:
                summary: Link to a transaction
                value:
                  type: transaction
                  id: 3c8b4c4f-2e0e-4f6e-9b8a-2a1c2b3d4e5f
      responses:
        "201":
          description: Association created. Echoes `{ attachment_id, parent_type, parent_id }`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  attachment_id:
                    type: string
                    format: uuid
                  parent_type:
                    type: string
                    enum:
                      - payable
                      - receivable
                      - transaction
                  parent_id:
                    type: string
                    format: uuid
                required:
                  - attachment_id
                  - parent_type
                  - parent_id
        "400":
          description: Malformed JSON body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.attachments.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Attachment or parent record not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "409":
          description: The attachment is already linked to this record.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure (unknown `type`, non-uuid `id`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    delete:
      operationId: unlinkAttachment
      summary: Unlink an attachment from a record
      description: Removes a single association between the attachment (`{id}`) and the parent identified in the body. Requires scope `finance.attachments.write`. Returns 404 when no such association exists. The attachment row itself is left intact — delete it with `DELETE /attachments/{id}`.
      tags:
        - Attachments
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AttachmentLinkRequest"
            examples:
              payable:
                summary: Unlink from a payable
                value:
                  type: payable
                  id: 8c8b4c4f-2e0e-4f6e-9b8a-2a1c2b3d4e5f
      responses:
        "204":
          description: Association removed. No response body.
        "400":
          description: Malformed JSON body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.attachments.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: No association between this attachment and the given record.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure (unknown `type`, non-uuid `id`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /categories:
    get:
      operationId: listCategories
      summary: List categories
      description: Returns a cursor-paginated page of categories for the workspace tied to the token's `sub` claim. Company-scoped within the workspace; categories form a tree via `parent_id` but are returned flat — build the tree client-side using `parent_id` and `level`. Requires scope `finance.categories`.
      tags:
        - Categories
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            description: Filter by owning company UUID. Accepts `[in]=uuid1,uuid2` for OR matching.
            example: 62057dab-0f98-4020-ae1e-2c0803251b1e
          required: false
          name: company_id
          in: query
        - schema:
            type: string
            format: uuid
            description: Filter by direct parent UUID. To list root-level categories, use `?level=1` instead — the URL grammar does not support `parent_id=null`.
          required: false
          name: parent_id
          in: query
        - schema:
            type: string
            enum:
              - debit
              - credit
            description: "`debit` (cost) or `credit` (revenue). Accepts `[in]=debit,credit`."
          required: false
          name: type
          in: query
        - schema:
            type: integer
            minimum: 1
            description: "Filter by depth. `1` returns root-level categories. Range operators supported: `[gte]`, `[lte]`. Example: `?level[lte]=2` for roots + their direct children."
            example: 1
          required: false
          name: level
          in: query
        - schema:
            type: string
            description: Filter by exact name. Accepts `[in]`.
            example: Aluguel
          required: false
          name: name
          in: query
        - schema:
            type: string
            enum:
              - name
              - -name
              - created_at
              - -created_at
              - level
              - -level
            description: "Sort field. Prefix with `-` for descending. Default: `name` (asc)."
          required: false
          name: sort
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            description: Page size. Default 25, max 100.
            example: 25
          required: false
          name: limit
          in: query
        - schema:
            type: string
            description: Opaque cursor for the next page. Round-trip `meta.next_cursor` from the previous response.
          required: false
          name: cursor
          in: query
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Page of categories plus pagination metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CategoryListResponse"
        "400":
          description: Malformed query (unknown filter / operator, invalid limit, bad cursor).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token authenticated but lacks the `finance.categories` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    post:
      operationId: createCategory
      summary: Create a category
      description: Creates a category. Requires scope `finance.categories.write`. `workspace_id` is taken from the token. Omit `parent_id` for a root (`level` 1); when set, the server derives `level = parent.level + 1` (max depth 3) and requires `type` to match the parent. The response is flat (`parent_id` + `level`). Pass an `Idempotency-Key` header on retries.
      tags:
        - Categories
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: "Opaque idempotency token. Same key + same body replays the cached response with `X-Idempotency-Replayed: true`; same key + different body returns 409. Scoped per token `sub`."
          required: false
          name: Idempotency-Key
          in: header
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CategoryCreateRequest"
            examples:
              root:
                summary: Root category
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  name: Despesas Operacionais
                  type: debit
              child:
                summary: Subcategory under a debit parent
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  name: Aluguel
                  type: debit
                  parent_id: f8d2a5e1-3c47-4b90-9a1b-2c8f7e3d4b95
      responses:
        "201":
          description: Category created. The `Location` header points at the new row.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CategoryV1"
        "400":
          description: Malformed JSON body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.categories.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "409":
          description: "`Idempotency-Key` reused with a different body."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure; `company_id` or `parent_id` not in the tenant; subcategory `type` not matching the parent; or tree depth > 3.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /categories/{id}:
    get:
      operationId: getCategory
      summary: Get a category by id
      description: " Returns the same shape as the rows from the list endpoint. Requires scope `finance.categories`. 404 covers both \"doesn't exist\" and \"not in the tenant\" — the API does not distinguish."
      tags:
        - Categories
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Get a category by id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CategoryV1"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.categories` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Category not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    patch:
      operationId: updateCategory
      summary: Update a category
      description: Partial update of `name` and `type` only. Requires scope `finance.categories.write`. Re-parenting is not supported via v1. Changing a parent's `type` cascades to all descendants (internal behavior).
      tags:
        - Categories
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CategoryUpdateRequest"
            examples:
              rename:
                summary: Rename
                value:
                  name: Aluguel e Condomínio
      responses:
        "200":
          description: Updated category.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CategoryV1"
        "400":
          description: Malformed JSON body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.categories.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Category not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    delete:
      operationId: deleteCategory
      summary: Delete a category
      description: "Deletes a category. Requires scope `finance.categories.write`. Returns 422 (`reason: has_children`) while the category still has subcategories — delete the children first. For a leaf, references on transactions / payables / receivables are set to null and the row is removed. Calling DELETE twice returns 204 then 404."
      tags:
        - Categories
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "204":
          description: Category deleted. No response body.
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.categories.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Category not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: "The category still has subcategories (`reason: has_children`)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /companies:
    get:
      operationId: listCompanies
      summary: List companies
      description: Returns a cursor-paginated page of companies for the workspace tied to the token's `sub` claim. Requires scope `finance.companies`. Catalogal entity — defaults to alphabetical sort by `name`.
      tags:
        - Companies
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            enum:
              - "true"
              - "false"
            description: Filter by active flag. `true` returns only active; omit to return both.
          required: false
          name: active
          in: query
        - schema:
            type: string
            enum:
              - matriz
              - filial
            description: "`matriz` (head office) or `filial` (branch). Accepts `[in]=matriz,filial`."
          required: false
          name: type
          in: query
        - schema:
            type: string
            description: Filter by CNPJ (14-digit numeric string, no formatting). Accepts `[in]=cnpj1,cnpj2`.
            example: "12345678000190"
          required: false
          name: cnpj
          in: query
        - schema:
            type: string
            description: Filter by exact name. Accepts `[in]`.
            example: ACME Comércio Ltda
          required: false
          name: name
          in: query
        - schema:
            type: string
            enum:
              - name
              - -name
              - created_at
              - -created_at
            description: "Sort field. Prefix with `-` for descending. Default: `name` (asc)."
          required: false
          name: sort
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            description: Page size. Default 25, max 100.
            example: 25
          required: false
          name: limit
          in: query
        - schema:
            type: string
            description: Opaque cursor for the next page. Round-trip `meta.next_cursor` from the previous response.
          required: false
          name: cursor
          in: query
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Page of companies plus pagination metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompanyListResponse"
        "400":
          description: Malformed query (unknown filter / operator, invalid limit, bad cursor).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token authenticated but lacks the `finance.companies` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    post:
      operationId: createCompany
      summary: Create a company
      description: Creates a company. Requires scope `finance.companies.write`. **`cnpj` is required and validated** with the standard BR check-digit algorithm — a missing, invalid, or document-less value returns 422 (there is no pessoa-física / document-less company in this API; a migrating client whose source row has no CNPJ must supply one). The CNPJ must be unique among active companies in the workspace (duplicate → 409). `type` (matriz/filial) is derived server-side from the CNPJ branch digits and is never read from the request. `active` starts `true`. Pass an `Idempotency-Key` header on retries.
      tags:
        - Companies
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: "Opaque idempotency token. Same key + same body replays the cached response with `X-Idempotency-Replayed: true`; same key + different body returns 409. Scoped per token `sub`."
          required: false
          name: Idempotency-Key
          in: header
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CompanyCreateRequest"
            examples:
              matriz:
                summary: Head office (matriz)
                value:
                  name: ACME Comércio Ltda
                  cnpj: "12345678000190"
              formatted:
                summary: Formatted CNPJ (accepted, stripped)
                value:
                  name: ACME Filial SP
                  cnpj: 12.345.678/0002-71
      responses:
        "201":
          description: Company created. The `Location` header points at the new row; `type` is derived from the CNPJ.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompanyV1"
        "400":
          description: Malformed JSON body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.companies.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "409":
          description: A company with the same CNPJ already exists (active), or `Idempotency-Key` reuse with a different body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure, or a missing / invalid / document-less `cnpj`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /companies/{id}:
    get:
      operationId: getCompany
      summary: Get a company by id
      description: " Returns the same shape as the rows from the list endpoint. Requires scope `finance.companies`. 404 covers both \"doesn't exist\" and \"not in the tenant\" — the API does not distinguish."
      tags:
        - Companies
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Get a company by id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompanyV1"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.companies` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Company not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    patch:
      operationId: updateCompany
      summary: Update a company
      description: "Partial update of `name` and `active`. Requires scope `finance.companies.write`. The CNPJ (and the `type` it derives) is immutable via the public API — send `active: false` to soft-deactivate."
      tags:
        - Companies
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CompanyUpdateRequest"
            examples:
              rename:
                summary: Rename
                value:
                  name: ACME Comércio S.A.
              deactivate:
                summary: Soft-deactivate
                value:
                  active: false
      responses:
        "200":
          description: Updated company.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompanyV1"
        "400":
          description: Malformed JSON body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.companies.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Company not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    delete:
      operationId: deleteCompany
      summary: Delete a company
      description: "Hard-deletes a company. Requires scope `finance.companies.write`. Blocked with 409 (`details` carries the per-table counts `{ transactions, payables, receivables, people }`) when the company still has financial records or active people — remove or reassign those first, or soft-deactivate via `PATCH { active: false }`. Config tables (categories, cost centers, financial accounts, payment methods, soft-deleted people) cascade automatically. Calling DELETE twice returns 204 then 404."
      tags:
        - Companies
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "204":
          description: Company deleted. No response body.
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.companies.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Company not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "409":
          description: The company has financial records or active people (`details` carries the counts).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /cost_centers:
    get:
      operationId: listCostCenters
      summary: List cost centers
      description: Returns a cursor-paginated page of cost centers for the workspace tied to the token's `sub` claim. Company-scoped within the workspace — pass `?company_id=…` to narrow. Requires scope `finance.cost_centers`.
      tags:
        - CostCenters
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            description: Filter by owning company UUID. Accepts `[in]=uuid1,uuid2` for OR matching.
            example: 62057dab-0f98-4020-ae1e-2c0803251b1e
          required: false
          name: company_id
          in: query
        - schema:
            type: string
            enum:
              - "true"
              - "false"
            description: Filter by active flag. `true` returns only active; omit to return both.
          required: false
          name: active
          in: query
        - schema:
            type: string
            enum:
              - cost
              - revenue
            description: "`cost` (deducts) or `revenue` (adds). Accepts `[in]=cost,revenue`."
          required: false
          name: type
          in: query
        - schema:
            type: string
            description: Filter by exact name. Accepts `[in]`.
            example: Operacional
          required: false
          name: name
          in: query
        - schema:
            type: string
            enum:
              - name
              - -name
              - created_at
              - -created_at
            description: "Sort field. Prefix with `-` for descending. Default: `name` (asc)."
          required: false
          name: sort
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            description: Page size. Default 25, max 100.
            example: 25
          required: false
          name: limit
          in: query
        - schema:
            type: string
            description: Opaque cursor for the next page. Round-trip `meta.next_cursor` from the previous response.
          required: false
          name: cursor
          in: query
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Page of cost centers plus pagination metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CostCenterListResponse"
        "400":
          description: Malformed query (unknown filter / operator, invalid limit, bad cursor).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token authenticated but lacks the `finance.cost_centers` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    post:
      operationId: createCostCenter
      summary: Create a cost center
      description: Creates a cost center. Requires scope `finance.cost_centers.write`. `workspace_id` is taken from the token. `type` (`cost` / `revenue`) drives the rateio polarity. Pass an `Idempotency-Key` header on retries.
      tags:
        - CostCenters
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: "Opaque idempotency token. Same key + same body replays the cached response with `X-Idempotency-Replayed: true`; same key + different body returns 409. Scoped per token `sub`."
          required: false
          name: Idempotency-Key
          in: header
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CostCenterCreateRequest"
            examples:
              cost:
                summary: A cost center
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  name: Operacional
                  type: cost
              revenue:
                summary: A revenue center with a description
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  name: Comercial
                  type: revenue
                  description: Time de vendas
      responses:
        "201":
          description: Cost center created. The `Location` header points at the new row.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CostCenterV1"
        "400":
          description: Malformed JSON body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.cost_centers.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "409":
          description: "`Idempotency-Key` reused with a different body."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure, or `company_id` not in the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /cost_centers/{id}:
    get:
      operationId: getCostCenter
      summary: Get a cost center by id
      description: " Returns the same shape as the rows from the list endpoint. Requires scope `finance.cost_centers`. 404 covers both \"doesn't exist\" and \"not in the tenant\" — the API does not distinguish."
      tags:
        - CostCenters
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Get a cost center by id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CostCenterV1"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.cost_centers` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Cost center not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    patch:
      operationId: updateCostCenter
      summary: Update a cost center
      description: "Partial update of `name`, `type`, `description`, `active`. Requires scope `finance.cost_centers.write`. Send `description: null` to clear it, or `active: false` to soft-deactivate (existing references are preserved)."
      tags:
        - CostCenters
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CostCenterUpdateRequest"
            examples:
              deactivate:
                summary: Soft-deactivate
                value:
                  active: false
              rename:
                summary: Rename + describe
                value:
                  name: Operacional (matriz)
                  description: Centro principal
      responses:
        "200":
          description: Updated cost center.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CostCenterV1"
        "400":
          description: Malformed JSON body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.cost_centers.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Cost center not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    delete:
      operationId: deleteCostCenter
      summary: Delete a cost center
      description: "Deletes a cost center. Requires scope `finance.cost_centers.write`. Returns 422 (`reason: in_use`, with a `usage` breakdown) when the cost center is referenced by a rateio allocation, billing or transaction — soft-deactivate via `PATCH { active: false }` instead. Calling DELETE twice returns 204 then 404."
      tags:
        - CostCenters
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "204":
          description: Cost center deleted. No response body.
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.cost_centers.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Cost center not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: "The cost center is in use (`reason: in_use`)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /payables:
    get:
      operationId: listPayables
      summary: List payables
      description: Returns a cursor-paginated page of payables (accounts payable) for the workspace tied to the token's `sub` claim. Each row includes denormalised display names (`person_name`, `category_name`, `cost_center_name`) and aggregate counts (`tax_charges_count`, `cost_center_allocations_count`, `attachments_count`) — full child collections live in the per-resource detail endpoints. Each row also carries the settlement amounts (`interest_amount`, `discount_amount`, `ticket_amount`) and a derived `nominal_amount = amount + interest - discount`. Status is the raw DB enum (`pending` / `paid` / `cancelled`); derive overdue client-side via `status === "pending" AND due_date < today`. Default sort is `due_date` ascending. Requires scope `finance.payables`.
      tags:
        - Payables
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            description: Filter by owning company UUID. Accepts `[in]`.
          required: false
          name: company_id
          in: query
        - schema:
            type: string
            enum:
              - pending
              - paid
              - cancelled
            description: Raw status. To find overdue payables, combine `status=pending&due_date[lt]=<today ISO>`. Accepts `[in]=pending,cancelled`.
          required: false
          name: status
          in: query
        - schema:
            type: string
            enum:
              - normal
              - recurring
              - installment
            description: Billing kind. Accepts `[in]`.
          required: false
          name: type
          in: query
        - schema:
            type: string
            format: uuid
            description: Filter by person UUID. Accepts `[in]`.
          required: false
          name: person_id
          in: query
        - schema:
            type: string
            format: uuid
            description: Filter by category UUID. Accepts `[in]`.
          required: false
          name: category_id
          in: query
        - schema:
            type: string
            format: uuid
            description: Filter by primary cost-center UUID (not the rateio). Accepts `[in]`.
          required: false
          name: cost_center_id
          in: query
        - schema:
            type: string
            format: uuid
            description: Filter by settlement account UUID. Accepts `[in]`.
          required: false
          name: financial_account_id
          in: query
        - schema:
            type: string
            enum:
              - bank_billet
              - utility
              - tribute
              - ted
              - pix
              - credit_card
              - debit_card
              - cash
              - check
              - direct_debit
              - other
              - none
            description: Payment method enum (`utility`/`tribute` are payable-only). Accepts `[in]`.
          required: false
          name: payment_method_kind
          in: query
        - schema:
            type: string
            description: "ISO date filter. Range operators: `[gte]`, `[lte]`, `[gt]`, `[lt]`. Example: `?due_date[gte]=2026-01-01&due_date[lte]=2026-12-31`."
          required: false
          name: due_date
          in: query
        - schema:
            type: string
            description: ISO date filter on the actual payment date. Range operators supported.
          required: false
          name: payment_date
          in: query
        - schema:
            type: string
            description: ISO date filter on the accounting period. Range operators supported.
          required: false
          name: competence_date
          in: query
        - schema:
            type: string
            description: Decimal filter on amount (BRL). Range operators supported.
          required: false
          name: amount
          in: query
        - schema:
            type: string
            description: Filter by exact document number. Accepts `[in]`.
          required: false
          name: document_number
          in: query
        - schema:
            type: string
            enum:
              - "true"
              - "false"
            description: Filter by tax-relevance flag.
          required: false
          name: ir_relevant
          in: query
        - schema:
            type: string
            format: uuid
            description: Filter siblings of a recurring/installment series.
          required: false
          name: group_id
          in: query
        - schema:
            type: string
            enum:
              - due_date
              - -due_date
              - payment_date
              - -payment_date
              - competence_date
              - -competence_date
              - created_at
              - -created_at
              - amount
              - -amount
            description: "Sort field. Prefix with `-` for descending. Default: `due_date` (asc — next-due first)."
          required: false
          name: sort
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            description: Page size. Default 25, max 100.
          required: false
          name: limit
          in: query
        - schema:
            type: string
            description: Opaque cursor for the next page. Round-trip `meta.next_cursor` from the previous response.
          required: false
          name: cursor
          in: query
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Page of payables plus pagination metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayableListResponse"
              examples:
                twoRows:
                  summary: One pending + one paid, denormalised names and aggregate counts populated
                  value:
                    data:
                      - id: 8c8b4c4f-2e0e-4f6e-9b8a-2a1c2b3d4e5f
                        company_id: 11111111-1111-1111-1111-111111111111
                        description: Aluguel — escritório matriz
                        amount: "3200.00"
                        interest_amount: null
                        discount_amount: null
                        ticket_amount: null
                        nominal_amount: "3200.00"
                        due_date: 2026-06-05T00:00:00.000Z
                        payment_date: null
                        competence_date: 2026-06-01T00:00:00.000Z
                        type: recurring
                        status: pending
                        person_id: 55555555-5555-5555-5555-555555555555
                        person_name: Imobiliária Central
                        category_id: 66666666-6666-6666-6666-666666666666
                        category_name: Aluguel
                        cost_center_id: null
                        cost_center_name: null
                        financial_account_id: null
                        expected_account_id: 22222222-2222-2222-2222-222222222222
                        payment_method_kind: bank_billet
                        operation_kind: payment
                        document: null
                        document_number: ALUG-2026-06
                        document_date: 2026-05-25T00:00:00.000Z
                        period: monthly:aluguel-matriz
                        installment_number: null
                        total_installments: null
                        parcels_amount: null
                        group_id: 77777777-7777-7777-7777-777777777777
                        transaction_id: null
                        ir_relevant: false
                        notes: null
                        is_reconciled: false
                        tax_charges_count: 0
                        cost_center_allocations_count: 0
                        attachments_count: 1
                        created_at: 2026-05-01T08:00:00.000Z
                        updated_at: 2026-05-01T08:00:00.000Z
                      - id: 9d9b5d5f-3f1f-5f7f-ac9b-3b2c3c4d5e6f
                        company_id: 11111111-1111-1111-1111-111111111111
                        description: Conta de energia — abril
                        amount: "420.50"
                        interest_amount: "5.50"
                        discount_amount: null
                        ticket_amount: "426.00"
                        nominal_amount: "426.00"
                        due_date: 2026-05-10T00:00:00.000Z
                        payment_date: 2026-05-09T00:00:00.000Z
                        competence_date: 2026-04-01T00:00:00.000Z
                        type: normal
                        status: paid
                        person_id: 88888888-8888-8888-8888-888888888888
                        person_name: ENEL Distribuição
                        category_id: 99999999-9999-9999-9999-999999999999
                        category_name: Utilidades — Energia
                        cost_center_id: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
                        cost_center_name: Administrativo
                        financial_account_id: 22222222-2222-2222-2222-222222222222
                        expected_account_id: null
                        payment_method_kind: pix
                        operation_kind: transfer
                        document: null
                        document_number: ENEL-202604
                        document_date: 2026-04-25T00:00:00.000Z
                        period: null
                        installment_number: null
                        total_installments: null
                        parcels_amount: null
                        group_id: null
                        transaction_id: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb
                        ir_relevant: false
                        notes: null
                        is_reconciled: true
                        tax_charges_count: 0
                        cost_center_allocations_count: 1
                        attachments_count: 0
                        created_at: 2026-04-26T08:00:00.000Z
                        updated_at: 2026-05-09T10:00:00.000Z
                    meta:
                      next_cursor: eyJpZCI6IjlkOWI1ZDVmLTNmMWYtNWY3Zi1hYzliLTNiMmMzYzRkNWU2ZiJ9
        "400":
          description: Malformed query (unknown filter / operator, invalid limit, bad cursor).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token authenticated but lacks the `finance.payables` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    post:
      operationId: createPayable
      summary: Create a payable
      description: Creates a single payable (or an installment/recurring series). Requires scope `finance.payables.write`. `workspace_id` is always taken from the token. A person is required — send `person_id` or `person_name`. `*_name` (person / category / cost_center) is find-or-create; sending both `*_id` and `*_name` for the same FK returns 400. `cost_centers[]` (rateio) and `tax_charges[]` are accepted; the rateio Σ is validated against `amount`. Pass an `Idempotency-Key` header on retries.
      tags:
        - Payables
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: "Opaque idempotency token. Same key + same body replays the cached response with `X-Idempotency-Replayed: true`; same key + different body returns 409. Scoped per token `sub`."
          required: false
          name: Idempotency-Key
          in: header
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PayableCreateRequest"
            examples:
              byId:
                summary: Simple payable with explicit FKs
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  description: Aluguel — escritório matriz
                  amount: "3200.00"
                  due_date: 2026-07-05T00:00:00.000Z
                  person_id: 55555555-5555-5555-5555-555555555555
                  category_id: 66666666-6666-6666-6666-666666666666
                  payment_method_kind: bank_billet
              withRateioAndTax:
                summary: Find-or-create person, 2-way rateio + a withholding charge
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  description: Serviço de consultoria — NF 4567
                  amount: "10000.00"
                  due_date: 2026-07-15T00:00:00.000Z
                  competence_date: 2026-07-01T00:00:00.000Z
                  person_name: Consultoria Acme LTDA
                  category_name: Serviços — Consultoria
                  cost_centers:
                    - cost_center_id: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
                      percentage: 60
                    - cost_center_id: cccccccc-cccc-cccc-cccc-cccccccccccc
                      percentage: 40
                  tax_charges:
                    - tax_id: dddddddd-dddd-dddd-dddd-dddddddddddd
                      amount: 150
                      withholding: true
              boletoWithBarcode:
                summary: Register a boleto with its barcode (payment coordinate)
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  description: Fornecedor — NF 8899
                  amount: "1840.00"
                  due_date: 2026-07-20T00:00:00.000Z
                  person_name: Distribuidora Beta
                  payment_method_kind: bank_billet
                  barcode: "23793380296000000000000000000000000000000000"
              tributeWithGuide:
                summary: Register a tribute (DARF) with structured guia data instead of a barcode
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  description: DARF — IRPJ 2T2026
                  amount: "5200.00"
                  due_date: 2026-07-31T00:00:00.000Z
                  person_name: Receita Federal
                  payment_method_kind: tribute
                  tax_kind: darf
                  tax_data:
                    tax_code: "2362"
                    calculation_date: 2026-06-30
                    expire_at: 2026-07-31
                    gross_income_amount: 5200
      responses:
        "201":
          description: "Payable created. The `Location` header points at the new row. The body is the full detail shape (arrays + reconciliation). `X-Idempotency-Replayed: true` marks a replayed response."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayableDetailV1"
        "400":
          description: Malformed JSON, or `*_id` and `*_name` sent together for the same FK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.payables.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "409":
          description: "`Idempotency-Key` reused with a different body."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: "Schema validation failure, or domain violation: missing person, company / account not in tenant, account does not match company, or rateio Σ ≠ amount."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /payables/{id}:
    get:
      operationId: getPayable
      summary: Get a payable by id
      description: "List-row shape plus the expanded child arrays the list only counts: `cost_center_allocations[]` (rateio) and `tax_charges[]`, plus the `reconciliation` group (`null` when not reconciled). Returns the same shape as the rows from the list endpoint. Requires scope `finance.payables`. 404 covers both \"doesn't exist\" and \"not in the tenant\" — the API does not distinguish."
      tags:
        - Payables
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Get a payable by id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayableDetailV1"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.payables` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Payable not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    patch:
      operationId: updatePayable
      summary: Update or settle a payable
      description: 'Partial update. Only fields present in the body are written; unknown / response-only fields (`id`, `created_at`, `transaction_id`, denorm names, counts, `is_reconciled`) are silently dropped. Requires scope `finance.payables.write`. **Settle**: send `status:"paid"` with `payment_date` and `financial_account_id` (or rely on a previously-set account) — a debit payment transaction is generated automatically. The settlement amounts (`interest_amount` / `discount_amount` / `ticket_amount`) are persisted on the row. Set `status` back to `pending` to revert, or `cancelled` to cancel. `draft` is never accepted.'
      tags:
        - Payables
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PayableUpdateRequest"
            examples:
              settle:
                summary: Settle (status → paid) with late-payment interest
                value:
                  status: paid
                  payment_date: 2026-07-04T00:00:00.000Z
                  financial_account_id: 22222222-2222-2222-2222-222222222222
                  interest_amount: 25
                  ticket_amount: 3425
              editFields:
                summary: Edit a couple of fields
                value:
                  description: Aluguel — matriz (reajustado)
                  amount: "3400.00"
      responses:
        "200":
          description: Updated payable (full detail shape).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayableDetailV1"
        "400":
          description: Malformed JSON, or `*_id` and `*_name` sent together for the same FK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.payables.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Payable not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: "Schema validation failure, or domain violation: settling without an account, FK out of scope, or rateio Σ ≠ amount."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    delete:
      operationId: deletePayable
      summary: Delete a payable
      description: Hard-deletes the payable. Requires scope `finance.payables.write`. Dissolves any reconciliation the payable is part of and removes the auto-created payment transaction (manually-linked transactions are preserved). Calling DELETE twice on the same id returns 204 then 404 (Stripe-style). Deletes a single row — series-aware delete is not yet exposed.
      tags:
        - Payables
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "204":
          description: Payable deleted. No response body.
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.payables.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Payable not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /people:
    get:
      operationId: listPeople
      summary: List people (customers / suppliers)
      description: Returns a cursor-paginated page of people for the workspace tied to the token's `sub` claim. Soft-deleted rows are never returned. Company-scoped within the workspace — `?company_id=…` narrows. Sensitive fields (bank/PIX info, addresses, custom_data, birthday) are intentionally excluded from this endpoint. Requires scope `finance.people`.
      tags:
        - People
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            description: Filter by owning company UUID. Accepts `[in]=uuid1,uuid2` for OR matching.
            example: 62057dab-0f98-4020-ae1e-2c0803251b1e
          required: false
          name: company_id
          in: query
        - schema:
            type: string
            enum:
              - natural
              - juridical
            description: "`natural` (pessoa física) or `juridical` (pessoa jurídica). Accepts `[in]=natural,juridical`."
          required: false
          name: kind
          in: query
        - schema:
            type: string
            enum:
              - "true"
              - "false"
            description: Filter by `is_customer` flag.
          required: false
          name: is_customer
          in: query
        - schema:
            type: string
            enum:
              - "true"
              - "false"
            description: Filter by `is_supplier` flag.
          required: false
          name: is_supplier
          in: query
        - schema:
            type: string
            enum:
              - "true"
              - "false"
            description: Filter by active flag.
          required: false
          name: active
          in: query
        - schema:
            type: string
            description: Filter by CPF/CNPJ (numeric-only string, no formatting). Accepts `[in]=11111111111,22222222222` for batch lookup.
            example: "12345678901"
          required: false
          name: document_number
          in: query
        - schema:
            type: string
            description: Filter by exact name. Accepts `[in]`.
          required: false
          name: name
          in: query
        - schema:
            type: string
            enum:
              - name
              - -name
              - created_at
              - -created_at
              - updated_at
              - -updated_at
            description: "Sort field. Prefix with `-` for descending. Default: `name` (asc)."
          required: false
          name: sort
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            description: Page size. Default 25, max 100.
            example: 25
          required: false
          name: limit
          in: query
        - schema:
            type: string
            description: Opaque cursor for the next page. Round-trip `meta.next_cursor` from the previous response.
          required: false
          name: cursor
          in: query
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Page of people plus pagination metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PersonListResponse"
        "400":
          description: Malformed query (unknown filter / operator, invalid limit, bad cursor).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token authenticated but lacks the `finance.people` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    post:
      operationId: createPerson
      summary: Create a person (customer / supplier)
      description: Creates a person. Requires scope `finance.people.write`. `workspace_id` is always taken from the token. Set `is_customer` / `is_supplier` to make the row referenceable by receivables / payables. `document_number` (CPF 11 digits / CNPJ 14 digits) is validated and stored numeric-only; `document_type` is inferred from the length when omitted. Contact details (`emails` / `phones`), addresses, and bank/PIX coordinates are not yet writable via v1. Pass an `Idempotency-Key` header on retries.
      tags:
        - People
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: "Opaque idempotency token. Same key + same body replays the cached response with `X-Idempotency-Replayed: true`; same key + different body returns 409. Scoped per token `sub`."
          required: false
          name: Idempotency-Key
          in: header
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PersonCreateRequest"
            examples:
              supplier:
                summary: Juridical supplier with a CNPJ
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  name: ACME Comércio Ltda
                  kind: juridical
                  document_type: cnpj
                  document_number: 11.444.777/0001-61
                  is_supplier: true
              customer:
                summary: Natural customer (document_type inferred from length)
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  name: João da Silva
                  kind: natural
                  document_number: "39053344705"
                  is_customer: true
      responses:
        "201":
          description: Person created. The `Location` header points at the new row.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PersonV1"
        "400":
          description: Malformed JSON body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.people.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "409":
          description: A person with the same `document_number` already exists for the company, or `Idempotency-Key` reuse with a different body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure, invalid CPF/CNPJ check digits, or `company_id` not in the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /people/{id}:
    get:
      operationId: getPerson
      summary: Get a person by id
      description: Soft-deleted rows return 404 — same `deleted_at IS NULL` filter as the listing. Returns the same shape as the rows from the list endpoint. Requires scope `finance.people`. 404 covers both "doesn't exist" and "not in the tenant" — the API does not distinguish.
      tags:
        - People
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Get a person by id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PersonV1"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.people` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Person not found within the tenant (or soft-deleted).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    patch:
      operationId: updatePerson
      summary: Update a person
      description: Partial update. Only fields present in the body are written; non-writable fields (`emails`, `phones`, bank/PIX, `id`, `workspace_id`, `company_id`, `created_at`) are dropped. Requires scope `finance.people.write`. Sending `null` for an optional string field is a no-op (clearing to null is not supported via v1).
      tags:
        - People
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PersonUpdateRequest"
            examples:
              promote:
                summary: Promote a supplier to also be a customer
                value:
                  is_customer: true
              rename:
                summary: Edit name + notes
                value:
                  name: ACME Comércio e Serviços Ltda
                  notes: Razão social atualizada
      responses:
        "200":
          description: Updated person.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PersonV1"
        "400":
          description: Malformed JSON body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.people.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Person not found within the tenant (or soft-deleted).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure or invalid CPF/CNPJ check digits.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    delete:
      operationId: deletePerson
      summary: Delete a person
      description: Soft-deletes the person (sets `deleted_at`). Requires scope `finance.people.write`. Existing payables / receivables / transactions referencing the person stay valid — the row just stops surfacing in listings and lookups. Calling DELETE twice returns 204 then 404 (Stripe-style).
      tags:
        - People
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "204":
          description: Person soft-deleted. No response body.
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.people.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Person not found within the tenant (or already deleted).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /receivables:
    get:
      operationId: listReceivables
      summary: List receivables
      description: Returns a cursor-paginated page of receivables (accounts receivable) for the workspace tied to the token's `sub` claim. Mirror of `/payables` with the receive-side settlement amounts (`interest_amount`, `discount_amount`, `ticket_amount`) and a derived `nominal_amount = amount + interest - discount`. Status is the raw DB enum (`pending` / `received` / `cancelled`); derive overdue client-side. Default sort is `due_date` ascending. Requires scope `finance.receivables`.
      tags:
        - Receivables
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            description: Filter by owning company UUID. Accepts `[in]`.
          required: false
          name: company_id
          in: query
        - schema:
            type: string
            enum:
              - pending
              - received
              - cancelled
            description: Raw status. To find overdue receivables, combine `status=pending&due_date[lt]=<today ISO>`. Accepts `[in]`.
          required: false
          name: status
          in: query
        - schema:
            type: string
            enum:
              - normal
              - recurring
              - installment
            description: Billing kind. Accepts `[in]`.
          required: false
          name: type
          in: query
        - schema:
            type: string
            format: uuid
            description: Filter by person UUID. Accepts `[in]`.
          required: false
          name: person_id
          in: query
        - schema:
            type: string
            format: uuid
            description: Filter by category UUID. Accepts `[in]`.
          required: false
          name: category_id
          in: query
        - schema:
            type: string
            format: uuid
            description: Filter by primary cost-center UUID. Accepts `[in]`.
          required: false
          name: cost_center_id
          in: query
        - schema:
            type: string
            format: uuid
            description: Filter by settlement account UUID. Accepts `[in]`.
          required: false
          name: financial_account_id
          in: query
        - schema:
            type: string
            enum:
              - bank_billet
              - ted
              - pix
              - credit_card
              - debit_card
              - cash
              - check
              - direct_debit
              - other
              - none
            description: Payment method enum. Accepts `[in]`.
          required: false
          name: payment_method_kind
          in: query
        - schema:
            type: string
            description: "ISO date filter. Range operators: `[gte]`, `[lte]`, `[gt]`, `[lt]`."
          required: false
          name: due_date
          in: query
        - schema:
            type: string
            description: ISO date filter on the actual receive date (column is `payment_date` to match the DB schema). Range operators supported.
          required: false
          name: payment_date
          in: query
        - schema:
            type: string
            description: ISO date filter on the accounting period. Range operators supported.
          required: false
          name: competence_date
          in: query
        - schema:
            type: string
            description: Decimal filter on amount (BRL). Range operators supported.
          required: false
          name: amount
          in: query
        - schema:
            type: string
            description: Filter by exact document number. Accepts `[in]`.
          required: false
          name: document_number
          in: query
        - schema:
            type: string
            enum:
              - "true"
              - "false"
            description: Filter by tax-relevance flag.
          required: false
          name: ir_relevant
          in: query
        - schema:
            type: string
            format: uuid
            description: Filter siblings of a recurring/installment series.
          required: false
          name: group_id
          in: query
        - schema:
            type: string
            enum:
              - due_date
              - -due_date
              - payment_date
              - -payment_date
              - competence_date
              - -competence_date
              - created_at
              - -created_at
              - amount
              - -amount
            description: "Sort field. Prefix with `-` for descending. Default: `due_date` (asc — next-due first)."
          required: false
          name: sort
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            description: Page size. Default 25, max 100.
          required: false
          name: limit
          in: query
        - schema:
            type: string
            description: Opaque cursor for the next page. Round-trip `meta.next_cursor` from the previous response.
          required: false
          name: cursor
          in: query
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Page of receivables plus pagination metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReceivableListResponse"
              examples:
                twoRows:
                  summary: One pending + one received — note nominal_amount = amount + interest - discount
                  value:
                    data:
                      - id: 8c8b4c4f-2e0e-4f6e-9b8a-2a1c2b3d4e5f
                        company_id: 11111111-1111-1111-1111-111111111111
                        description: "NF #1234 — venda janeiro"
                        amount: "1500.00"
                        interest_amount: null
                        discount_amount: null
                        ticket_amount: null
                        nominal_amount: "1500.00"
                        due_date: 2026-06-10T00:00:00.000Z
                        payment_date: null
                        competence_date: 2026-05-01T00:00:00.000Z
                        type: normal
                        status: pending
                        person_id: 44444444-4444-4444-4444-444444444444
                        person_name: Cliente XYZ Comércio LTDA
                        category_id: null
                        category_name: null
                        cost_center_id: null
                        cost_center_name: null
                        financial_account_id: null
                        expected_account_id: 22222222-2222-2222-2222-222222222222
                        payment_method_kind: bank_billet
                        document: null
                        document_number: NF-1234
                        document_date: 2026-05-25T00:00:00.000Z
                        period: null
                        installment_number: null
                        total_installments: null
                        parcels_amount: null
                        group_id: null
                        transaction_id: null
                        ir_relevant: true
                        notes: null
                        is_reconciled: false
                        tax_charges_count: 2
                        cost_center_allocations_count: 0
                        attachments_count: 1
                        created_at: 2026-05-25T08:00:00.000Z
                        updated_at: 2026-05-25T08:00:00.000Z
                      - id: 9d9b5d5f-3f1f-5f7f-ac9b-3b2c3c4d5e6f
                        company_id: 11111111-1111-1111-1111-111111111111
                        description: "NF #1100 — consultoria abril"
                        amount: "2000.00"
                        interest_amount: "25.00"
                        discount_amount: null
                        ticket_amount: "2024.20"
                        nominal_amount: "2025.00"
                        due_date: 2026-04-30T00:00:00.000Z
                        payment_date: 2026-05-02T00:00:00.000Z
                        competence_date: 2026-04-01T00:00:00.000Z
                        type: normal
                        status: received
                        person_id: 55555555-5555-5555-5555-555555555555
                        person_name: Consultoria ABC
                        category_id: 66666666-6666-6666-6666-666666666666
                        category_name: Receita — Serviços
                        cost_center_id: null
                        cost_center_name: null
                        financial_account_id: 22222222-2222-2222-2222-222222222222
                        expected_account_id: null
                        payment_method_kind: pix
                        document: null
                        document_number: NF-1100
                        document_date: 2026-04-20T00:00:00.000Z
                        period: null
                        installment_number: null
                        total_installments: null
                        parcels_amount: null
                        group_id: null
                        transaction_id: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb
                        ir_relevant: true
                        notes: null
                        is_reconciled: true
                        tax_charges_count: 1
                        cost_center_allocations_count: 0
                        attachments_count: 0
                        created_at: 2026-04-20T08:00:00.000Z
                        updated_at: 2026-05-02T14:00:00.000Z
                    meta:
                      next_cursor: null
        "400":
          description: Malformed query (unknown filter / operator, invalid limit, bad cursor).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token authenticated but lacks the `finance.receivables` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    post:
      operationId: createReceivable
      summary: Create a receivable
      description: Creates a single receivable (or an installment/recurring series). Requires scope `finance.receivables.write`. `workspace_id` is always taken from the token. A person is required — send `person_id` or `person_name`. `*_name` (person / category / cost_center) is find-or-create (customer/credit/revenue polarity); sending both `*_id` and `*_name` for the same FK returns 400. `cost_centers[]` (rateio) and `tax_charges[]` are accepted. Pass an `Idempotency-Key` header on retries.
      tags:
        - Receivables
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: "Opaque idempotency token. Same key + same body replays the cached response with `X-Idempotency-Replayed: true`; same key + different body returns 409. Scoped per token `sub`."
          required: false
          name: Idempotency-Key
          in: header
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReceivableCreateRequest"
            examples:
              byId:
                summary: Simple receivable with explicit FKs
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  description: "NF #1234 — venda janeiro"
                  amount: "1500.00"
                  due_date: 2026-07-10T00:00:00.000Z
                  person_id: 44444444-4444-4444-4444-444444444444
                  payment_method_kind: bank_billet
              byName:
                summary: Find-or-create customer + a withholding charge
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  description: Consultoria — abril
                  amount: "2000.00"
                  due_date: 2026-07-15T00:00:00.000Z
                  person_name: Cliente XYZ Comércio LTDA
                  category_name: Receita — Serviços
                  tax_charges:
                    - tax_id: dddddddd-dddd-dddd-dddd-dddddddddddd
                      amount: 30
      responses:
        "201":
          description: "Receivable created. The `Location` header points at the new row; the body is the full detail shape. `X-Idempotency-Replayed: true` marks a replayed response."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReceivableDetailV1"
        "400":
          description: Malformed JSON, or `*_id` and `*_name` sent together for the same FK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.receivables.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "409":
          description: "`Idempotency-Key` reused with a different body."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: "Schema validation failure, or domain violation: missing person, company / account not in tenant, account does not match company, or rateio Σ ≠ amount."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /receivables/{id}:
    get:
      operationId: getReceivable
      summary: Get a receivable by id
      description: Mirror of the payable detail — denorm names + counts + `nominal_amount`, plus the expanded `cost_center_allocations[]` / `tax_charges[]` arrays and the `reconciliation` group (`null` when not reconciled). Returns the same shape as the rows from the list endpoint. Requires scope `finance.receivables`. 404 covers both "doesn't exist" and "not in the tenant" — the API does not distinguish.
      tags:
        - Receivables
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Get a receivable by id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReceivableDetailV1"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.receivables` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Receivable not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    patch:
      operationId: updateReceivable
      summary: Update or settle a receivable
      description: 'Partial update. Only fields present in the body are written; unknown / response-only fields are silently dropped. Requires scope `finance.receivables.write`. **Settle (receive)**: send `status:"received"` with `payment_date` and `financial_account_id` (or a previously-set account) — an auto credit transaction is generated; the receive-time `interest_amount` / `discount_amount` / `ticket_amount` are persisted. Set `status` back to `pending` to revert, or `cancelled` to cancel. `draft` is never accepted.'
      tags:
        - Receivables
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReceivableUpdateRequest"
            examples:
              settle:
                summary: Receive with interest + ticket
                value:
                  status: received
                  payment_date: 2026-07-12T00:00:00.000Z
                  financial_account_id: 22222222-2222-2222-2222-222222222222
                  interest_amount: 25
                  ticket_amount: 1524.2
              editFields:
                summary: Edit a couple of fields
                value:
                  description: "NF #1234 (reemitida)"
                  amount: "1600.00"
      responses:
        "200":
          description: Updated receivable (full detail shape).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReceivableDetailV1"
        "400":
          description: Malformed JSON, or `*_id` and `*_name` sent together for the same FK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.receivables.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Receivable not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: "Schema validation failure, or domain violation: receiving without an account, FK out of scope, or rateio Σ ≠ amount."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    delete:
      operationId: deleteReceivable
      summary: Delete a receivable
      description: Hard-deletes the receivable. Requires scope `finance.receivables.write`. Dissolves any reconciliation and removes the auto-created receipt transaction (manually-linked transactions are preserved). Calling DELETE twice returns 204 then 404 (Stripe-style). Single-row — series-aware delete is not yet exposed.
      tags:
        - Receivables
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "204":
          description: Receivable deleted. No response body.
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.receivables.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Receivable not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /taxes:
    get:
      operationId: listTaxes
      summary: List tax kinds
      description: Returns a cursor-paginated page of tax kinds for the workspace tied to the token's `sub` claim. Requires scope `finance.taxes`. Catalogal entity — defaults to alphabetical sort by `name`.
      tags:
        - Taxes
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            enum:
              - "true"
              - "false"
            description: Filter by active flag. `true` returns only active (default UX preference); omit to return both.
          required: false
          name: active
          in: query
        - schema:
            type: string
            enum:
              - "true"
              - "false"
            description: Filter by `default_withholding` flag (whether new charges default to imposto retido).
          required: false
          name: default_withholding
          in: query
        - schema:
            type: string
            description: Filter by exact name (unique per workspace). Accepts `[in]=IRRF,INSS` for OR matching.
            example: IRRF
          required: false
          name: name
          in: query
        - schema:
            type: string
            enum:
              - name
              - -name
              - created_at
              - -created_at
            description: "Sort field. Prefix with `-` for descending. Default: `name` (asc)."
          required: false
          name: sort
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            description: Page size. Default 25, max 100.
            example: 25
          required: false
          name: limit
          in: query
        - schema:
            type: string
            description: Opaque cursor for the next page. Round-trip `meta.next_cursor` from the previous response.
          required: false
          name: cursor
          in: query
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Page of tax kinds plus pagination metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaxListResponse"
        "400":
          description: Malformed query (unknown filter / operator, invalid limit, bad cursor).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token authenticated but lacks the `finance.taxes` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    post:
      operationId: createTax
      summary: Create a tax kind
      description: Creates a tax kind. Requires scope `finance.taxes.write`. Workspace-scoped (no company). `name` is unique per workspace (duplicate → 409). `default_rate` is a percent string (0–100). Pass an `Idempotency-Key` header on retries.
      tags:
        - Taxes
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: "Opaque idempotency token. Same key + same body replays the cached response with `X-Idempotency-Replayed: true`; same key + different body returns 409. Scoped per token `sub`."
          required: false
          name: Idempotency-Key
          in: header
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TaxCreateRequest"
            examples:
              irrf:
                summary: IRRF (withheld) with a default rate
                value:
                  name: IRRF
                  default_rate: "1.50"
                  default_withholding: true
              iss:
                summary: ISS (no default rate)
                value:
                  name: ISS
                  default_withholding: false
      responses:
        "201":
          description: Tax created. The `Location` header points at the new row.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaxV1"
        "400":
          description: Malformed JSON body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.taxes.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "409":
          description: A tax with the same `name` already exists, or `Idempotency-Key` reuse with a different body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure, or `default_rate` out of the 0–100 range.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /taxes/{id}:
    get:
      operationId: getTax
      summary: Get a tax kind by id
      description: " Returns the same shape as the rows from the list endpoint. Requires scope `finance.taxes`. 404 covers both \"doesn't exist\" and \"not in the tenant\" — the API does not distinguish."
      tags:
        - Taxes
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Get a tax kind by id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaxV1"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.taxes` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Tax not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    patch:
      operationId: updateTax
      summary: Update a tax kind
      description: "Partial update of `name`, `default_rate`, `default_withholding`, `active`. Requires scope `finance.taxes.write`. Send `default_rate: null` to clear it, or `active: false` to soft-deactivate."
      tags:
        - Taxes
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TaxUpdateRequest"
            examples:
              rate:
                summary: Adjust the default rate
                value:
                  default_rate: "1.00"
              deactivate:
                summary: Soft-deactivate
                value:
                  active: false
      responses:
        "200":
          description: Updated tax.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaxV1"
        "400":
          description: Malformed JSON body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.taxes.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Tax not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "409":
          description: A tax with the same `name` already exists.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure, or `default_rate` out of range.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    delete:
      operationId: deleteTax
      summary: Delete a tax kind
      description: "Deletes a tax. Requires scope `finance.taxes.write`. Returns 422 (`reason: in_use`, with the referencing payable/receivable ids) when the tax still has charges — soft-deactivate via `PATCH { active: false }` instead. Calling DELETE twice returns 204 then 404."
      tags:
        - Taxes
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "204":
          description: Tax deleted. No response body.
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.taxes.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Tax not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: "The tax has charges (`reason: in_use`)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /transactions:
    get:
      operationId: listTransactions
      summary: List transactions
      description: Returns a cursor-paginated page of transactions for the workspace tied to the token's `sub` claim. Requires scope `finance.transactions`.
      tags:
        - Transactions
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            description: Filter by company UUID. Accepts `[in]=uuid1,uuid2` for OR matching.
            example: 11111111-1111-1111-1111-111111111111
          required: false
          name: company_id
          in: query
        - schema:
            type: string
            enum:
              - debit
              - credit
            description: "`debit` (money out) or `credit` (money in). Accepts `[in]=debit,credit`."
          required: false
          name: type
          in: query
        - schema:
            type: string
            format: uuid
            description: Filter by financial account UUID. Accepts `[in]`.
          required: false
          name: financial_account_id
          in: query
        - schema:
            type: string
            format: uuid
            description: Filter by person UUID. Accepts `[in]`.
          required: false
          name: person_id
          in: query
        - schema:
            type: string
            format: uuid
            description: Filter by category UUID. Accepts `[in]`.
          required: false
          name: category_id
          in: query
        - schema:
            type: string
            format: uuid
            description: Filter by cost-center UUID. Accepts `[in]`.
          required: false
          name: cost_center_id
          in: query
        - schema:
            type: string
            description: "ISO date filter on the movement date. Range operators supported: `[gte]`, `[lte]`, `[gt]`, `[lt]`. Example: `?occurred_at[gte]=2026-01-01&occurred_at[lte]=2026-12-31`."
            example: 2026-01-01
          required: false
          name: occurred_at
          in: query
        - schema:
            type: string
            description: "Decimal filter on amount (BRL). Range operators supported: `[gte]`, `[lte]`, `[gt]`, `[lt]`."
            example: "100.00"
          required: false
          name: amount
          in: query
        - schema:
            type: string
            enum:
              - occurred_at
              - -occurred_at
              - created_at
              - -created_at
              - amount
              - -amount
            description: "Sort field. Prefix with `-` for descending. Default: `-occurred_at`."
          required: false
          name: sort
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            description: Page size. Default 25, max 100.
            example: 25
          required: false
          name: limit
          in: query
        - schema:
            type: string
            description: Opaque cursor for the next page. Round-trip `meta.next_cursor` from the previous response.
          required: false
          name: cursor
          in: query
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Page of transactions plus pagination metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionListResponse"
              examples:
                twoRows:
                  summary: One debit + one credit, with a next-page cursor
                  value:
                    data:
                      - id: 8c8b4c4f-2e0e-4f6e-9b8a-2a1c2b3d4e5f
                        company_id: 11111111-1111-1111-1111-111111111111
                        financial_account_id: 22222222-2222-2222-2222-222222222222
                        description: Pagamento de fornecedor — NF 1234
                        bank_description: TED ACME LTDA
                        document: "1234"
                        document_date: 2026-05-20T00:00:00.000Z
                        amount: "1500.00"
                        occurred_at: 2026-05-24T00:00:00.000Z
                        competence_date: 2026-05-01T00:00:00.000Z
                        type: debit
                        category_id: 33333333-3333-3333-3333-333333333333
                        person_id: 44444444-4444-4444-4444-444444444444
                        cost_center_id: null
                        payment_method_kind: ted
                        classified: true
                        notes: null
                        created_at: 2026-05-24T10:32:18.412Z
                      - id: 9d9b5d5f-3f1f-5f7f-ac9b-3b2c3c4d5e6f
                        company_id: 11111111-1111-1111-1111-111111111111
                        financial_account_id: 22222222-2222-2222-2222-222222222222
                        description: Recebimento cliente XYZ
                        bank_description: null
                        document: null
                        document_date: null
                        amount: "850.00"
                        occurred_at: 2026-05-23T00:00:00.000Z
                        competence_date: 2026-05-01T00:00:00.000Z
                        type: credit
                        category_id: null
                        person_id: null
                        cost_center_id: null
                        payment_method_kind: pix
                        classified: false
                        notes: null
                        created_at: 2026-05-23T14:05:00.000Z
                    meta:
                      next_cursor: eyJpZCI6IjlkOWI1ZDVmLTNmMWYtNWY3Zi1hYzliLTNiMmMzYzRkNWU2ZiJ9
                empty:
                  summary: No matches — empty page
                  value:
                    data: []
                    meta:
                      next_cursor: null
        "400":
          description: Malformed query (unknown filter / operator, invalid limit, bad cursor).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
              examples:
                unknownFilter:
                  summary: Filter not in the allowlist
                  value:
                    error:
                      code: bad_request
                      message: "Unknown filter: foo"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token authenticated but lacks the `finance.transactions` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    post:
      operationId: createTransaction
      summary: Create a transaction
      description: Creates a single transaction. Requires scope `finance.transactions.write`. `workspace_id` is always taken from the token, never from the body. `auto_created` and `transfer_id` are always server-controlled — values in the request are ignored. `*_name` accepted as alternative to `*_id` (person / category / cost_center) for CSV-importer ergonomics; sending both for the same FK returns 400. Pass an `Idempotency-Key` header on retries to avoid duplicate inserts.
      tags:
        - Transactions
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: "Opaque idempotency token (recommended on POST). Same key + same body returns the cached response with `X-Idempotency-Replayed: true`. Same key + different body returns 409 conflict. Scoped per token `sub`."
          required: false
          name: Idempotency-Key
          in: header
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TransactionCreateRequest"
            examples:
              byId:
                summary: Debit (saída) with explicit *_id references
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  financial_account_id: 22222222-2222-2222-2222-222222222222
                  description: Pagamento de fornecedor — NF 1234
                  amount: "1500.00"
                  occurred_at: 2026-05-24T00:00:00.000Z
                  type: debit
                  category_id: 33333333-3333-3333-3333-333333333333
                  person_id: 44444444-4444-4444-4444-444444444444
                  payment_method_kind: ted
              byName:
                summary: Credit (entrada) using *_name find-or-create
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  financial_account_id: 22222222-2222-2222-2222-222222222222
                  description: Recebimento cliente XYZ
                  amount: "850.00"
                  occurred_at: 2026-05-23T00:00:00.000Z
                  type: credit
                  category_name: Vendas — Serviços
                  person_name: XYZ Comércio LTDA
                  payment_method_kind: pix
      responses:
        "201":
          description: "Transaction created. The `Location` header points at the new row. `X-Idempotency-Replayed: true` indicates a cached response from a previous identical request."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionV1"
              examples:
                created:
                  summary: Created transaction (mirrors the request, plus server-set fields)
                  value:
                    id: 8c8b4c4f-2e0e-4f6e-9b8a-2a1c2b3d4e5f
                    company_id: 11111111-1111-1111-1111-111111111111
                    financial_account_id: 22222222-2222-2222-2222-222222222222
                    description: Pagamento de fornecedor — NF 1234
                    bank_description: null
                    document: null
                    document_date: null
                    amount: "1500.00"
                    occurred_at: 2026-05-24T00:00:00.000Z
                    competence_date: 2026-05-24T00:00:00.000Z
                    type: debit
                    category_id: 33333333-3333-3333-3333-333333333333
                    person_id: 44444444-4444-4444-4444-444444444444
                    cost_center_id: null
                    payment_method_kind: ted
                    classified: false
                    notes: null
                    created_at: 2026-05-24T10:32:18.412Z
        "400":
          description: Malformed JSON body, or `*_id` and `*_name` sent together for the same FK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
              examples:
                mutualExclusion:
                  summary: Sent both category_id and category_name
                  value:
                    error:
                      code: bad_request
                      message: category_id and category_name are mutually exclusive.
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.transactions.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
              examples:
                insufficientScope:
                  summary: Read-only token tried to write
                  value:
                    error:
                      code: insufficient_scope
                      message: Token is missing required scope.
                      details:
                        required: finance.transactions.write
                        granted:
                          - finance.transactions
        "409":
          description: "`Idempotency-Key` was reused with a different request body."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
              examples:
                idempotencyConflict:
                  summary: Same key, different body
                  value:
                    error:
                      code: conflict
                      message: Idempotency-Key reused with a different request body.
        "422":
          description: Schema validation failure, or domain consistency violation (company / account not in tenant, account does not match company, FK out of scope).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
              examples:
                companyMismatch:
                  summary: Account does not belong to the given company
                  value:
                    error:
                      code: unprocessable_entity
                      message: financial_account does not belong to the given company.
                invalidAmount:
                  summary: Amount fails schema regex
                  value:
                    error:
                      code: unprocessable_entity
                      message: amount must be a positive decimal string with up to 2 fractional digits.
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /transactions/{id}:
    patch:
      operationId: updateTransaction
      summary: Update a transaction
      description: Partial update. Only fields present in the body are written. Requires scope `finance.transactions.write`. Read-only fields in the body (`id`, `workspace_id`, `company_id`, `financial_account_id`, `auto_created`, `transfer_id`, `created_at`) are silently dropped — the response is the new persisted state.
      tags:
        - Transactions
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TransactionUpdateRequest"
      responses:
        "200":
          description: Updated transaction.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionV1"
        "400":
          description: Malformed JSON body, or `*_id` and `*_name` sent together for the same FK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.transactions.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Transaction not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure or FK out of scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    delete:
      operationId: deleteTransaction
      summary: Delete a transaction
      description: Hard-deletes the row. Requires scope `finance.transactions.write`. Deleting an auto-created row (one generated by paying / receiving a payable / receivable) returns 422 — cancel the source billing instead. Calling DELETE twice on the same id returns 204 then 404 (Stripe-style).
      tags:
        - Transactions
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "204":
          description: Transaction deleted. No response body.
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.transactions.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Transaction not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Auto-created transaction — cancel the parent payable / receivable instead.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    get:
      operationId: getTransaction
      summary: Get a transaction by id
      description: " Returns the same shape as the rows from the list endpoint. Requires scope `finance.transactions`. 404 covers both \"doesn't exist\" and \"not in the tenant\" — the API does not distinguish."
      tags:
        - Transactions
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - name: User-Agent
          in: header
          required: true
          description: Identifies your integration. Include your name and contact email so the Kobana team can reach you if your integration causes issues (RFC 7231 §5.5.3).
          schema: *a12
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Get a transaction by id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionV1"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.transactions` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Transaction not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
webhooks: {}
