openapi: 3.1.0
info:
  title: API do Financeiro Inteligente
  version: 1.0.0
  description: |-
    Hey dev, have fun! ✨

    > **Documentation index** — get the full index at [https://docs.finance.kobana.com.br/llms.txt](https://docs.finance.kobana.com.br/llms.txt). Use this file to discover every available page before you start exploring.

    ## Authentication

    The API uses **Bearer JWT (HS512)**. Send the token in the header of every request:

    ```
    Authorization: Bearer <token>
    ```

    For command-line testing, export the token: `export KOBANA_TOKEN=xxxxxxxx` and paste the commands from the docs straight into your terminal.

    ## Format

    The API accepts `JSON` only. Every request must use `Content-Type: application/json`. Every response uses `snake_case`.

    | Field Type | Format |
    | :--- | :--- |
    | **DateTime** | [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) format. Examples — Date: `2026-01-24`. Date and time: `2026-01-24T10:07:00.000Z` |
    | **Money** | Decimal as a string with 2 places (`"150.00"`). Always positive — the direction is given by the `type` field (`debit`/`credit`) or by context (payable/receivable). |
    | **UUID** | Resource identifiers in UUID v4 format |

    ## Conventions

    Conventions used throughout this documentation:

    | Convention | Description |
    | :--- | :--- |
    | **`:id`** | Path parameter to be replaced with the resource's UUID. |
    | **`#{variable}`** | A value from your account to be substituted in the example. |
    | **`...`** | Response content truncated for readability. |
    | **`$KOBANA_TOKEN`** | JWT access token. Export it with `export KOBANA_TOKEN=xxxxxxxx`. |

    ## Environments

    | Environment | Base URL |
    | :--- | :--- |
    | **Production** | `https://api.finance.kobana.com.br/v1` |
    | **Sandbox** | `https://api.finance.sandbox.kobana.com.br/v1` |

    ## Status Codes

    The API returns standard HTTP status codes:

    | | Code | Description |
    | :--- | :--- | :--- |
    | ✅ | **200 OK** | Successful request with a response body. |
    | ✅ | **201 Created** | Resource created successfully. |
    | ✅ | **204 No Content** | Successful request with no response body. |
    | ❌ | **400 Bad Request** | Invalid request, usually a malformed body. |
    | ❌ | **401 Unauthorized** | Missing, invalid, or expired token. |
    | ❌ | **403 Forbidden** | Insufficient scope for the operation. |
    | ❌ | **404 Not Found** | Resource not found or outside the workspace. |
    | ❌ | **422 Unprocessable Entity** | Valid request, but the submitted data is not. |
    | ❌ | **429 Too Many Requests** | Rate limit reached. |
    | ❌ | **500 Internal Server Error** | Internal processing error. Check the [server status](https://status.kobana.com.br). |

    ## Listing and Pagination

    Every listing endpoint uses **cursor-based pagination**:

    ```
    GET /v1/payables?cursor=<opaque_cursor>&limit=25
    ```

    The response includes `meta.next_cursor` — pass it as `?cursor=` on the next request to fetch the following page. When `meta.next_cursor` is `null`, there are no more pages.

    ## Idempotency

    Creation operations (`POST`) accept the `X-Idempotency-Key` header. Retries with the same key return the original result without creating duplicates — safe to retry on network failure.

    ```
    X-Idempotency-Key: <client-generated-uuid-v4>
    ```

    ## Request ID

    Every request has an associated identifier, available in the `Request-Id` response header. This value helps with debugging and auditing. The request log is kept for **30 days**. When opening a support ticket about a specific request, provide the `Request-Id` to speed up the investigation.

    ## Security

    The API uses **2048-bit SSL** certificates. Every request must go over **HTTPS** — calls to port 80 are redirected to 443.

    Clients must support `TLSv1.2` or `TLSv1.3` with one of the following ciphers: `TLS_AES_128_GCM_SHA256`, `TLS_AES_256_GCM_SHA384`, `TLS_CHACHA20_POLY1305_SHA256`, `ECDHE-RSA-AES128-GCM-SHA256`, `ECDHE-RSA-AES128-SHA256`, `ECDHE-RSA-AES256-GCM-SHA384`, `ECDHE-RSA-CHACHA20-POLY1305`, `ECDHE-RSA-AES256-SHA384`. `TLSv1` and `TLSv1.1` are not supported.

    ## HTTP Cache

    Use HTTP cache headers to reduce load and gain speed. Most responses include `ETag` and/or `Last-Modified` — store these values and send them back on subsequent requests via `If-None-Match` and `If-Modified-Since`. If the resource has not changed, the response is `304 Not Modified`, with no body and no reprocessing.

    ## Error Handling

    5xx errors indicate server failures: **500 Internal Server Error** (application unavailable), **502 Bad Gateway**, **503 Service Unavailable**, and **504 Gateway Timeout** (transient infrastructure failures). Your application should detect these codes and reschedule the request after a few minutes with exponential backoff. Server status: [https://status.kobana.com.br](https://status.kobana.com.br).

    ## OpenAPI Spec

    Import the spec into Postman or Insomnia for a ready-to-use collection that always reflects the current API:

    - **YAML:** `/api/v1/openapi.yaml`
    - **JSON:** `/api/v1/openapi.json`
  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
    TransactionClassificationCenterAllocationV1:
      type: object
      properties:
        classification_center_id:
          type: string
          format: uuid
        classification_center_name:
          type:
            - string
            - "null"
        percentage:
          type:
            - string
            - "null"
          description: Percentage of the transaction amount, as a decimal string. `null` when the split is amount-driven.
        amount:
          type:
            - string
            - "null"
          description: Absolute slice as a 2-decimal string. `null` when the split is percentage-driven.
      required:
        - classification_center_id
        - classification_center_name
        - percentage
        - amount
    FinancialTransactionV1:
      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
        classification_center_id:
          type:
            - string
            - "null"
          format: uuid
          description: Primary classification center (rows[0] of the rateio). Full split in `classification_center_allocations[]`.
        classification_center_allocations:
          type: array
          items:
            $ref: "#/components/schemas/TransactionClassificationCenterAllocationV1"
          description: The rateio (split) across classification centers. Empty when there is no split.
        payment_method_kind:
          type:
            - string
            - "null"
          enum:
            - 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
        updated_at:
          type: string
          format: date-time
          description: Last write. Filterable and sortable — it is the incremental-sync window. Rows predating the column carry their `created_at`, since the real last-write date was never recorded.
      required:
        - id
        - company_id
        - financial_account_id
        - description
        - bank_description
        - document
        - document_date
        - amount
        - occurred_at
        - competence_date
        - type
        - category_id
        - person_id
        - classification_center_id
        - classification_center_allocations
        - payment_method_kind
        - classified
        - notes
        - created_at
        - updated_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
    FinancialTransactionListMeta:
      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/FinancialTransactionV1"
        meta:
          $ref: "#/components/schemas/FinancialTransactionListMeta"
      required:
        - data
        - meta
    TransactionClassificationCenterSplitInput:
      type: object
      properties:
        classification_center_id:
          type: string
          format: uuid
        percentage:
          type:
            - number
            - "null"
          minimum: 0
          maximum: 100
        amount:
          type:
            - number
            - "null"
          minimum: 0
      required:
        - classification_center_id
    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{1,13}\.\d{1,2}$
          description: Decimal string with 1–2 fractional digits (up to 13 integer 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:
            - 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`.
        classification_center_id:
          type:
            - string
            - "null"
          format: uuid
        classification_center_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `classification_center_id`. Find-or-create with `revenue` polarity for credits / `cost` for debits — a center typed `both` matches either; creates with the narrow polarity when no match. Mutually exclusive with `classification_center_id`.
        classification_centers:
          type: array
          items:
            $ref: "#/components/schemas/TransactionClassificationCenterSplitInput"
          description: "Rateio (split) across classification centers. Replace-all: the array becomes the row's set; an empty array clears it; omit to leave it alone. Effective amounts must sum to `amount` (1-cent tolerance). Each center's polarity must match the transaction `type` (`both` matches either). When present, `classification_center_id` / `_name` are ignored and the legacy FK mirrors rows[0]."
        payment_method_kind:
          type:
            - string
            - "null"
          enum:
            - bank_billet
            - ted
            - pix
            - credit_card
            - debit_card
            - cash
            - check
            - direct_debit
            - other
            - none
        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{1,13}\.\d{1,2}$
          description: Decimal string with 1–2 fractional digits (up to 13 integer 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:
            - 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`.
        classification_center_id:
          type:
            - string
            - "null"
          format: uuid
        classification_center_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `classification_center_id`. Find-or-create with `revenue` polarity for credits / `cost` for debits — a center typed `both` matches either; creates with the narrow polarity when no match. Mutually exclusive with `classification_center_id`.
        classification_centers:
          type: array
          items:
            $ref: "#/components/schemas/TransactionClassificationCenterSplitInput"
          description: "Rateio (split) across classification centers. Replace-all: the array becomes the row's set; an empty array clears it; omit to leave it alone. Effective amounts must sum to `amount` (1-cent tolerance). Each center's polarity must match the transaction `type` (`both` matches either). When present, `classification_center_id` / `_name` are ignored and the legacy FK mirrors rows[0]."
        payment_method_kind:
          type:
            - string
            - "null"
          enum:
            - bank_billet
            - ted
            - pix
            - credit_card
            - debit_card
            - cash
            - check
            - direct_debit
            - other
            - none
        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{1,3}(\.\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{1,3}(\.\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-character CNPJ string (no formatting / no dots / no dashes). The first 12 characters are alphanumeric (A-Z uppercase + 0-9); the last 2 are numeric check digits. Numeric-only CNPJs (legacy format) and alphanumeric CNPJs (new RFB format) are both valid.
          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 characters. Formatting (dots / slash / dash) is accepted and stripped; letters are uppercased. Supports both numeric-only (legacy) and alphanumeric (new RFB format) CNPJs. Validated with the standard BR check-digit algorithm. **Required** — a missing, invalid, or document-less value returns 422. Must be unique among active companies in the workspace (duplicate → 409). `type` (matriz/filial) is derived server-side.
          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).
    ClassificationCenterV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: f8d2a5e1-3c47-4b90-9a1b-2c8f7e3d4b95
        company_id:
          type: string
          format: uuid
          description: Owning company. Classification centers are company-scoped (unlike taxes which are workspace-scoped).
        name:
          type: string
          description: Classification center label. Unique constraint not enforced at DB level.
          example: Operacional
        type:
          type: string
          enum:
            - cost
            - revenue
            - both
          description: "`cost` (deducts from gross result), `revenue` (adds to gross result), or `both` (usable on either side). Determines which operations the center is eligible for: payables accept `cost`/`both`, receivables accept `revenue`/`both`."
        description:
          type:
            - string
            - "null"
          description: Free-text annotation. May be null.
        active:
          type: boolean
          description: When false, the classification 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
    ClassificationCenterListMeta:
      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
    ClassificationCenterListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ClassificationCenterV1"
        meta:
          $ref: "#/components/schemas/ClassificationCenterListMeta"
      required:
        - data
        - meta
    ClassificationCenterCreateRequest:
      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
            - both
          description: "`cost` (deducts from gross result), `revenue` (adds), or `both` (usable on either side)."
        description:
          type:
            - string
            - "null"
          maxLength: 500
        active:
          type: boolean
          description: Defaults to `true`.
      required:
        - company_id
        - name
        - type
    ClassificationCenterUpdateRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 255
        type:
          type: string
          enum:
            - cost
            - revenue
            - both
        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:
            - natural
            - juridical
          description: "`natural` (pessoa física) or `juridical` (pessoa jurídica)."
        document_type:
          type:
            - string
            - "null"
          enum:
            - 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:
            - natural
            - juridical
          description: "`natural` (pessoa física) or `juridical` (pessoa jurídica)."
        document_type:
          type:
            - string
            - "null"
          enum:
            - 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
    FinancialAccountV1:
      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.
        bank:
          type:
            - object
            - "null"
          properties:
            code:
              type: string
              description: Public COMPE/BACEN code.
              example: "341"
            name:
              type: string
              example: Itaú Unibanco
          required:
            - code
            - name
          description: The account's bank denormalised as its public `{ code, name }`, so reads don't need a second call to `/banks` to translate `bank_id`. `null` when `bank_id` is null.
        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
        - bank
        - agency
        - agency_digit
        - account_number
        - account_digit
        - active
        - disable_reconcile
        - created_at
    FinancialAccountDetailV1:
      allOf:
        - $ref: "#/components/schemas/FinancialAccountV1"
        - 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
    FinancialAccountListMeta:
      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/FinancialAccountV1"
        meta:
          $ref: "#/components/schemas/FinancialAccountListMeta"
      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:
            - checking
            - savings
            - cash
            - investment
            - credit
        bank_id:
          type:
            - string
            - "null"
          format: uuid
          description: Reference to a `banks` row. `null` for cash / non-bank accounts.
        bank_code:
          type: string
          minLength: 1
          description: "Alternative to `bank_id`: the bank's public COMPE/BACEN code (e.g. `341`), resolved to the catalog bank server-side. Mutually exclusive with `bank_id` — sending both returns 400; an unknown code returns 422."
          example: "341"
        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{1,13}\.\d{1,2}$
          description: Opening balance seed (signed 2-decimal string, up to 13 integer digits). 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:
            - checking
            - savings
            - cash
            - investment
            - credit
        bank_id:
          type:
            - string
            - "null"
          format: uuid
          description: Reference to a `banks` row. `null` for cash / non-bank accounts.
        bank_code:
          type: string
          minLength: 1
          description: "Alternative to `bank_id`: the bank's public COMPE/BACEN code (e.g. `341`), resolved to the catalog bank server-side. Mutually exclusive with `bank_id` — sending both returns 400; an unknown code returns 422."
          example: "341"
        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).
    AdjustBalanceRequest:
      type: object
      properties:
        target_balance:
          type: string
          pattern: ^-?\d{1,13}\.\d{1,2}$
          description: The balance the account should have after the adjustment. Signed 2-decimal string (up to 13 integer digits).
          example: "1500.00"
        category_id:
          type: string
          format: uuid
          description: Category for the adjustment transaction. Its type must match the diff direction (credit for a positive diff, debit for negative) and its company must match the account's.
        occurred_at:
          type: string
          format: date-time
          description: When the adjustment transaction is dated (ISO 8601). Defaults to now.
        description:
          type: string
          maxLength: 500
          description: Overrides the auto-created transaction description (default "Ajuste de saldo").
      required:
        - target_balance
        - category_id
    AdjustBalanceResultV1:
      type: object
      properties:
        transaction_id:
          type: string
          format: uuid
          description: The auto-created adjustment transaction.
        difference:
          type: string
          description: Signed `target_balance − previous_balance`.
          example: "250.00"
        previous_balance:
          type: string
          description: Calculated balance before the adjustment.
          example: "1250.00"
        new_balance:
          type: string
          description: Calculated balance after the adjustment.
          example: "1500.00"
      required:
        - transaction_id
        - difference
        - previous_balance
        - new_balance
    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.
        classification_center_id:
          type:
            - string
            - "null"
          format: uuid
          description: Primary classification center on the row. Splits (rateio) live in `classification_center_allocations[]` on the detail endpoint.
        classification_center_name:
          type:
            - string
            - "null"
          description: Denormalised name of the primary classification center. `null` when `classification_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:
            - 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.
        payment_authorization:
          type:
            - string
            - "null"
          enum:
            - pending_authorization
            - authorizing
            - authorized
            - rejected
          description: "Read-only mirror of the human payment-authorization gate. `null` = **official** (never gated, or already authorized) — the title is on the list. The gate is a per-workspace setting (on by default): when it is on, a payable created through this API starts `pending_authorization` and is **hidden from this list until a person authorizes it in the app** (`GET /payables/{id}` still returns it). `rejected` = blocked forever (fraud/DDA). `authorizing` is reserved for a future async flow. To **list the authorization queue**, filter it explicitly — e.g. `?payment_authorization=pending_authorization` (or `[in]=pending_authorization,rejected`); the gated titles then appear. Read-only: this API never authorizes or rejects — that stays a human action in the app. Detail-only audit stamps (`authorized_at`, `rejected_at`, `rejection_reason`, `payment_source`) live on `GET /payables/{id}`."
        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
        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.
        classification_center_allocations_count:
          type: integer
          minimum: 0
          description: Number of classification-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
        - classification_center_id
        - classification_center_name
        - financial_account_id
        - expected_account_id
        - payment_method_kind
        - operation_kind
        - payment_authorization
        - document
        - document_number
        - document_date
        - installment_number
        - total_installments
        - parcels_amount
        - group_id
        - transaction_id
        - ir_relevant
        - notes
        - is_reconciled
        - tax_charges_count
        - classification_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
    PayableClassificationCenterAllocationV1:
      type: object
      properties:
        classification_center_id:
          type: string
          format: uuid
        classification_center_name:
          type:
            - string
            - "null"
          description: Denormalised name of the classification 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:
        - classification_center_id
        - classification_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:
                - 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:
                - 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.
            authorized_at:
              type:
                - string
                - "null"
              format: date-time
              description: When the payment was authorized (ISO 8601). Read-only. `null` unless `payment_authorization = authorized`.
            rejected_at:
              type:
                - string
                - "null"
              format: date-time
              description: When the payment was rejected (ISO 8601). Read-only. `null` unless `payment_authorization = rejected`.
            rejection_reason:
              type:
                - string
                - "null"
              description: Audit note recorded when the payment was rejected. Read-only.
            payment_source:
              type:
                - string
                - "null"
              enum:
                - manual
                - csv_import
                - dda
                - ai_agent
              description: Origin channel of the payable — the anti-fraud context of the authorization screen (`manual` = created in the app/API, `csv_import` = the CSV importer). Read-only; `null` for rows predating the feature.
            classification_center_allocations:
              type: array
              items:
                $ref: "#/components/schemas/PayableClassificationCenterAllocationV1"
              description: Rateio rows (classification-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
            - authorized_at
            - rejected_at
            - rejection_reason
            - payment_source
            - classification_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
    PayableClassificationCenterSplitInput:
      type: object
      properties:
        classification_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:
        - classification_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{1,13}\.\d{1,2}$
          description: Decimal string with 1–2 fractional digits (up to 13 integer 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:
            - 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`.
        classification_center_id:
          type:
            - string
            - "null"
          format: uuid
        classification_center_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `classification_center_id`. Find-or-create with `cost` polarity — matches a center typed `cost` or `both`; creates as `cost` when no match. Mutually exclusive with `classification_center_id`.
        payment_method_kind:
          type:
            - string
            - "null"
          enum:
            - bank_billet
            - utility
            - tribute
            - ted
            - pix
            - credit_card
            - debit_card
            - cash
            - check
            - direct_debit
            - other
            - none
        operation_kind:
          type:
            - string
            - "null"
          enum:
            - 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).
        classification_centers:
          type: array
          items:
            $ref: "#/components/schemas/PayableClassificationCenterSplitInput"
          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:
            - cpf
            - cnpj
            - email
            - phone
            - random
        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:
            - itbi
            - icms
            - iss
            - iptu
            - fgts
            - dare
            - darf
            - gru
            - gps
          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{1,13}\.\d{1,2}$
          description: Decimal string with 1–2 fractional digits (up to 13 integer 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:
            - 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`.
        classification_center_id:
          type:
            - string
            - "null"
          format: uuid
        classification_center_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `classification_center_id`. Find-or-create with `cost` polarity — matches a center typed `cost` or `both`; creates as `cost` when no match. Mutually exclusive with `classification_center_id`.
        payment_method_kind:
          type:
            - string
            - "null"
          enum:
            - bank_billet
            - utility
            - tribute
            - ted
            - pix
            - credit_card
            - debit_card
            - cash
            - check
            - direct_debit
            - other
            - none
        operation_kind:
          type:
            - string
            - "null"
          enum:
            - 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).
        classification_centers:
          type: array
          items:
            $ref: "#/components/schemas/PayableClassificationCenterSplitInput"
          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:
            - cpf
            - cnpj
            - email
            - phone
            - random
        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:
            - itbi
            - icms
            - iss
            - iptu
            - fgts
            - dare
            - darf
            - gru
            - gps
          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"
        classification_center_id:
          type:
            - string
            - "null"
          format: uuid
          description: Primary classification center on the row. Splits (rateio) live in `classification_center_allocations[]` on the detail endpoint.
        classification_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:
            - bank_billet
            - ted
            - pix
            - credit_card
            - debit_card
            - cash
            - check
            - direct_debit
            - other
            - none
        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
        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
        classification_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
        - classification_center_id
        - classification_center_name
        - financial_account_id
        - expected_account_id
        - payment_method_kind
        - document
        - document_number
        - document_date
        - installment_number
        - total_installments
        - parcels_amount
        - group_id
        - transaction_id
        - ir_relevant
        - notes
        - is_reconciled
        - tax_charges_count
        - classification_center_allocations_count
        - attachments_count
        - created_at
        - updated_at
    ReceivableClassificationCenterAllocationV1:
      type: object
      properties:
        classification_center_id:
          type: string
          format: uuid
        classification_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:
        - classification_center_id
        - classification_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:
            classification_center_allocations:
              type: array
              items:
                $ref: "#/components/schemas/ReceivableClassificationCenterAllocationV1"
              description: Rateio rows (classification-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:
            - classification_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
    ReceivableClassificationCenterSplitInput:
      type: object
      properties:
        classification_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:
        - classification_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{1,13}\.\d{1,2}$
          description: Decimal string with 1–2 fractional digits (up to 13 integer 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:
            - 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`.
        classification_center_id:
          type:
            - string
            - "null"
          format: uuid
        classification_center_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `classification_center_id`. Find-or-create with `revenue` polarity — matches a center typed `revenue` or `both`; creates as `revenue` when no match. Mutually exclusive with `classification_center_id`.
        payment_method_kind:
          type:
            - string
            - "null"
          enum:
            - bank_billet
            - ted
            - pix
            - credit_card
            - debit_card
            - cash
            - check
            - direct_debit
            - other
            - none
        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).
        classification_centers:
          type: array
          items:
            $ref: "#/components/schemas/ReceivableClassificationCenterSplitInput"
          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{1,13}\.\d{1,2}$
          description: Decimal string with 1–2 fractional digits (up to 13 integer 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:
            - 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`.
        classification_center_id:
          type:
            - string
            - "null"
          format: uuid
        classification_center_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `classification_center_id`. Find-or-create with `revenue` polarity — matches a center typed `revenue` or `both`; creates as `revenue` when no match. Mutually exclusive with `classification_center_id`.
        payment_method_kind:
          type:
            - string
            - "null"
          enum:
            - bank_billet
            - ted
            - pix
            - credit_card
            - debit_card
            - cash
            - check
            - direct_debit
            - other
            - none
        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).
        classification_centers:
          type: array
          items:
            $ref: "#/components/schemas/ReceivableClassificationCenterSplitInput"
          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
    BankV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: b3d9a5f8-2c1e-4a90-9f23-7e6b1c4d8a02
        code:
          type: string
          description: COMPE/BACEN clearing code. Unique across the catalog (e.g. `001`, `237`, `341`).
          example: "341"
        name:
          type: string
          description: Official institution name.
          example: Itaú Unibanco S.A.
      required:
        - id
        - code
        - name
    BankListMeta:
      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
    BankListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/BankV1"
        meta:
          $ref: "#/components/schemas/BankListMeta"
      required:
        - data
        - meta
    TransferV1:
      type: object
      properties:
        transfer_id:
          type: string
          format: uuid
          description: Stable id of the linked pair. Read a transfer via `GET /transfers/{transfer_id}`.
          example: 9c2f1a44-7b3e-4e01-9a5c-2d6b8e1f0a33
        from_account_id:
          type: string
          format: uuid
          description: Source (debited) financial account.
        to_account_id:
          type: string
          format: uuid
          description: Destination (credited) financial account.
        from_transaction_id:
          type: string
          format: uuid
          description: The `debit` leg's transaction id (money out of `from_account_id`).
        to_transaction_id:
          type: string
          format: uuid
          description: The `credit` leg's transaction id (money into `to_account_id`).
        amount:
          type: string
          description: Positive decimal string with 2 fractional digits. Same magnitude on both legs.
          example: "1500.00"
        occurred_at:
          type: string
          format: date-time
          description: When the movement hit both accounts. Shared by both legs.
          example: 2026-07-10T00:00:00.000Z
        competence_date:
          type: string
          format: date-time
          description: Accounting period. Falls back to `occurred_at` when not supplied on create.
        description:
          type: string
          example: Aporte para conta pagamentos
        notes:
          type:
            - string
            - "null"
        is_pending:
          type: boolean
          description: When `true`, a forecast transfer — excluded from realised balance until confirmed.
      required:
        - transfer_id
        - from_account_id
        - to_account_id
        - from_transaction_id
        - to_transaction_id
        - amount
        - occurred_at
        - competence_date
        - description
        - notes
        - is_pending
    TransferCreateRequest:
      type: object
      properties:
        from_account_id:
          type: string
          format: uuid
          description: Source account UUID (debited). Must belong to the tenant.
        to_account_id:
          type: string
          format: uuid
          description: Destination account UUID (credited). Must belong to the tenant and differ from `from_account_id`.
        amount:
          type: string
          pattern: ^\d{1,13}\.\d{1,2}$
          description: Positive decimal string with 1–2 fractional digits (up to 13 integer digits).
          example: "1500.00"
        occurred_at:
          type: string
          format: date-time
          description: When the movement hits both accounts (ISO 8601).
        competence_date:
          type:
            - string
            - "null"
          format: date-time
          description: Accounting period this transfer belongs to. Defaults to `occurred_at` when omitted.
        description:
          type: string
          minLength: 1
          maxLength: 500
          example: Aporte para conta pagamentos
        notes:
          type:
            - string
            - "null"
          maxLength: 2000
        is_pending:
          type: boolean
          description: Defaults to `false`. `true` marks a forecast transfer.
      required:
        - from_account_id
        - to_account_id
        - amount
        - occurred_at
        - description
    PayableAggregatesV1:
      type: object
      properties:
        count:
          type: integer
          description: Count of non-draft payables in scope. Includes cancelled rows — which the `pending`/`paid`/`total` money buckets exclude — so `count` may exceed the number of rows behind `total`.
        total:
          type: string
          description: Sum of `pending`+`paid` amounts (cancelled excluded).
          example: "12500.00"
        pending:
          type: string
          description: Sum of amounts still pending.
          example: "8000.00"
        paid:
          type: string
          description: Sum of amounts already paid.
          example: "4500.00"
        overdue_count:
          type: integer
          description: Pending payables in the window whose `due_date` is already past. 0 for a fully-future window.
      required:
        - count
        - total
        - pending
        - paid
        - overdue_count
    ReceivableAggregatesV1:
      type: object
      properties:
        count:
          type: integer
          description: Count of non-draft receivables in scope. Includes cancelled rows — which the `pending`/`received`/`total` money buckets exclude — so `count` may exceed the number of rows behind `total`.
        total:
          type: string
          description: Sum of `pending`+`received` amounts (cancelled excluded).
          example: "9000.00"
        pending:
          type: string
          description: Sum of amounts still pending.
          example: "6000.00"
        received:
          type: string
          description: Sum of amounts already received.
          example: "3000.00"
        overdue_count:
          type: integer
          description: Pending receivables in the window whose `due_date` is already past. 0 for a fully-future window.
      required:
        - count
        - total
        - pending
        - received
        - overdue_count
    TransactionAggregatesV1:
      type: object
      properties:
        count:
          type: integer
          description: Number of realised transactions in the window (forecasts excluded).
        credit:
          type: string
          description: Sum of credit (money-in) amounts.
          example: "20000.00"
        debit:
          type: string
          description: Sum of debit (money-out) amounts.
          example: "12000.00"
        net:
          type: string
          description: "`credit` − `debit`. May be negative."
          example: "8000.00"
      required:
        - count
        - credit
        - debit
        - net
    CashFlowBucketV1:
      type: object
      properties:
        bucket:
          type: string
          description: Canonical bucket key. Format depends on `granularity` (e.g. `2026-07` for month, `2026-07-13` for day).
          example: 2026-07
        income:
          type: string
          description: Total money-in in the bucket (realised + projected).
          example: "12000.00"
        expenses:
          type: string
          description: Total money-out in the bucket (realised + projected).
          example: "8000.00"
        net:
          type: string
          description: "`income` − `expenses`. May be negative."
          example: "4000.00"
        projected_income:
          type: string
          description: The projected-only slice of `income` (pending titles + forecast). 0 on fully-realised past buckets.
        projected_expenses:
          type: string
          description: The projected-only slice of `expenses`. 0 on fully-realised past buckets.
        opening_balance:
          type: string
          description: Running balance at the start of the bucket.
        closing_balance:
          type: string
          description: Running balance at the end of the bucket (`opening_balance` + `net`).
        is_future:
          type: boolean
          description: True from the `today_bucket` onward (the projected side of the curve).
      required:
        - bucket
        - income
        - expenses
        - net
        - projected_income
        - projected_expenses
        - opening_balance
        - closing_balance
        - is_future
    CashFlowV1:
      type: object
      properties:
        granularity:
          type: string
          enum:
            - day
            - week
            - month
            - year
        current_balance:
          type: string
          description: Balance as of now (`saldo atual`), the projection anchor.
          example: "15000.00"
        today_bucket:
          type: string
          description: Canonical key of the bucket containing today — the past/future seam.
          example: 2026-07
        truncated:
          type: boolean
          description: True when the requested range exceeded the per-granularity cap and was clipped to a today-anchored window.
        retroactive_overdue:
          type: object
          properties:
            income:
              type: string
            expenses:
              type: string
          required:
            - income
            - expenses
          description: Overdue (past-due pending) flow folded into `today_bucket`. Lets a consumer flag that the today column isn't strictly today's money. Zeros when nothing is overdue.
        buckets:
          type: array
          items:
            $ref: "#/components/schemas/CashFlowBucketV1"
      required:
        - granularity
        - current_balance
        - today_bucket
        - truncated
        - retroactive_overdue
        - buckets
    ReconciliationV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
        source:
          type: string
          description: 'Origin of the group: `"manual"` (user/API-driven) or `"fintera"` (imported, read-only).'
        kind:
          type: string
          enum:
            - payable
            - receivable
          description: Polarity of the group — every `billing_id` is this kind.
        billing_ids:
          type: array
          items:
            type: string
            format: uuid
          description: IDs of the payables (or receivables) settled by this reconciliation.
        transaction_ids:
          type: array
          items:
            type: string
            format: uuid
          description: IDs of the bank transactions in the group.
      required:
        - id
        - source
        - kind
        - billing_ids
        - transaction_ids
    ReconciliationCreateRequest:
      type: object
      properties:
        billings:
          type: array
          items:
            type: object
            properties:
              kind:
                type: string
                enum:
                  - payable
                  - receivable
              id:
                type: string
                format: uuid
            required:
              - kind
              - id
          minItems: 1
          description: Billings to reconcile. All must share one kind and one company.
        transaction_ids:
          type: array
          items:
            type: string
            format: uuid
          minItems: 1
          description: Bank transactions to reconcile the billings against.
      required:
        - billings
        - transaction_ids
    AutomaticRuleV1:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: a1f2c3d4-5e6f-4a1b-8c2d-3e4f5a6b7c8d
        company_id:
          type: string
          format: uuid
          description: Owning company. Rules are scoped to `(company, scope)`; `position` is unique within that pair.
        scope:
          type: string
          enum:
            - transactions
            - billings
          description: "Target ledger: `transactions` (Contas Financeiras) or `billings` (Payables + Receivables). Immutable after create."
        position:
          type: integer
          description: 1-based precedence within `(company, scope)`. Lower runs first (first-match-wins). Server-assigned.
          example: 1
        active:
          type: boolean
          description: Inactive rules stay in the list but are skipped by the evaluator.
        account_id:
          type:
            - string
            - "null"
          format: uuid
          description: Criterion — match rows on this financial account. Only meaningful for `scope=transactions`.
        transaction_type:
          type:
            - string
            - "null"
          enum:
            - debit
            - credit
          description: Criterion — match only debit or credit rows. `null` matches both.
        match_person_id:
          type:
            - string
            - "null"
          format: uuid
          description: Criterion — match rows already tied to this person (customer/supplier).
        description_contains:
          type:
            - string
            - "null"
          description: Criterion — case-insensitive substring on the row description. `""` is a wildcard (matches all).
        notes_contains:
          type:
            - string
            - "null"
          description: Criterion — case-insensitive substring on the row notes.
        set_description:
          type:
            - string
            - "null"
          description: Action — overwrite the description.
        set_person_id:
          type:
            - string
            - "null"
          format: uuid
          description: Action — assign this person.
        set_category_id:
          type:
            - string
            - "null"
          format: uuid
          description: Action — assign this category.
        set_classification_center_id:
          type:
            - string
            - "null"
          format: uuid
          description: Action — assign this classification center (cost/revenue center). References a `/v1/classification_centers` row.
        set_expected_account_id:
          type:
            - string
            - "null"
          format: uuid
          description: Action — set the expected settlement account. Only meaningful for `scope=billings`.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - company_id
        - scope
        - position
        - active
        - account_id
        - transaction_type
        - match_person_id
        - description_contains
        - notes_contains
        - set_description
        - set_person_id
        - set_category_id
        - set_classification_center_id
        - set_expected_account_id
        - created_at
        - updated_at
    AutomaticRuleListMeta:
      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
    AutomaticRuleListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/AutomaticRuleV1"
        meta:
          $ref: "#/components/schemas/AutomaticRuleListMeta"
      required:
        - data
        - meta
    AutomaticRuleCreateRequest:
      type: object
      properties:
        company_id:
          type: string
          format: uuid
          description: Owning company UUID. Must belong to the tenant.
        scope:
          type: string
          enum:
            - transactions
            - billings
          description: Target ledger. Immutable after create.
        active:
          type: boolean
          description: Defaults to `true`.
        account_id:
          type:
            - string
            - "null"
          format: uuid
          description: Criterion — financial account. Only valid for `scope=transactions` (422 otherwise).
        transaction_type:
          type:
            - string
            - "null"
          enum:
            - debit
            - credit
        match_person_id:
          type:
            - string
            - "null"
          format: uuid
        description_contains:
          type:
            - string
            - "null"
          maxLength: 255
          description: Case-insensitive substring on description. `""` opts into a wildcard (matches every row).
        notes_contains:
          type:
            - string
            - "null"
          maxLength: 255
        set_description:
          type:
            - string
            - "null"
          maxLength: 255
        set_person_id:
          type:
            - string
            - "null"
          format: uuid
        set_category_id:
          type:
            - string
            - "null"
          format: uuid
        set_classification_center_id:
          type:
            - string
            - "null"
          format: uuid
          description: Action — classification center. Maps to the internal cost-center column.
        set_expected_account_id:
          type:
            - string
            - "null"
          format: uuid
          description: Action — expected settlement account. Only valid for `scope=billings` (422 otherwise).
      required:
        - company_id
        - scope
    AutomaticRuleUpdateRequest:
      type: object
      properties:
        active:
          type: boolean
        account_id:
          type:
            - string
            - "null"
          format: uuid
          description: Criterion — financial account. Only valid for `scope=transactions` (422 otherwise).
        transaction_type:
          type:
            - string
            - "null"
          enum:
            - debit
            - credit
        match_person_id:
          type:
            - string
            - "null"
          format: uuid
        description_contains:
          type:
            - string
            - "null"
          maxLength: 255
          description: Case-insensitive substring on description. `""` opts into a wildcard (matches every row).
        notes_contains:
          type:
            - string
            - "null"
          maxLength: 255
        set_description:
          type:
            - string
            - "null"
          maxLength: 255
        set_person_id:
          type:
            - string
            - "null"
          format: uuid
        set_category_id:
          type:
            - string
            - "null"
          format: uuid
        set_classification_center_id:
          type:
            - string
            - "null"
          format: uuid
          description: Action — classification center. Maps to the internal cost-center column.
        set_expected_account_id:
          type:
            - string
            - "null"
          format: uuid
          description: Action — expected settlement account. Only valid for `scope=billings` (422 otherwise).
    AutomaticRuleReorderRequest:
      type: object
      properties:
        company_id:
          type: string
          format: uuid
          description: Company whose rules are being reordered.
        scope:
          type: string
          enum:
            - transactions
            - billings
          description: Ledger whose rules are being reordered.
        ordered_ids:
          type: array
          items:
            type: string
            format: uuid
          minItems: 1
          description: The COMPLETE ordered set of rule ids for `(company_id, scope)`, first to last. Positions are rewritten 1..N to match. A partial or foreign set is rejected (422).
      required:
        - company_id
        - scope
        - ordered_ids
    FinancialAccountListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/FinancialAccountV1"
        meta:
          $ref: "#/components/schemas/FinancialAccountListMeta"
      required:
        - data
        - meta
    FinancialAccountCreateRequest:
      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:
            - checking
            - savings
            - cash
            - investment
            - credit
        bank_id:
          type:
            - string
            - "null"
          format: uuid
          description: Reference to a `banks` row. `null` for cash / non-bank accounts.
        bank_code:
          type: string
          minLength: 1
          description: "Alternative to `bank_id`: the bank's public COMPE/BACEN code (e.g. `341`), resolved to the catalog bank server-side. Mutually exclusive with `bank_id` — sending both returns 400; an unknown code returns 422."
          example: "341"
        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{1,13}\.\d{1,2}$
          description: Opening balance seed (signed 2-decimal string, up to 13 integer digits). 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
    FinancialAccountUpdateRequest:
      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:
            - checking
            - savings
            - cash
            - investment
            - credit
        bank_id:
          type:
            - string
            - "null"
          format: uuid
          description: Reference to a `banks` row. `null` for cash / non-bank accounts.
        bank_code:
          type: string
          minLength: 1
          description: "Alternative to `bank_id`: the bank's public COMPE/BACEN code (e.g. `341`), resolved to the catalog bank server-side. Mutually exclusive with `bank_id` — sending both returns 400; an unknown code returns 422."
          example: "341"
        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).
    IdSweepV1:
      type: object
      properties:
        data:
          type: array
          items:
            type: string
            format: uuid
          description: Ids visible to this token, ascending. Ordering is stable but carries no meaning.
        meta:
          type: object
          properties:
            next_cursor:
              type:
                - string
                - "null"
              description: Pass as `?cursor=`. `null` on the last page.
          required:
            - next_cursor
      required:
        - data
        - meta
    FinancialTransactionListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/FinancialTransactionV1"
        meta:
          $ref: "#/components/schemas/FinancialTransactionListMeta"
      required:
        - data
        - meta
    FinancialTransactionCreateRequest:
      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{1,13}\.\d{1,2}$
          description: Decimal string with 1–2 fractional digits (up to 13 integer 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:
            - 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`.
        classification_center_id:
          type:
            - string
            - "null"
          format: uuid
        classification_center_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `classification_center_id`. Find-or-create with `revenue` polarity for credits / `cost` for debits — a center typed `both` matches either; creates with the narrow polarity when no match. Mutually exclusive with `classification_center_id`.
        classification_centers:
          type: array
          items:
            $ref: "#/components/schemas/TransactionClassificationCenterSplitInput"
          description: "Rateio (split) across classification centers. Replace-all: the array becomes the row's set; an empty array clears it; omit to leave it alone. Effective amounts must sum to `amount` (1-cent tolerance). Each center's polarity must match the transaction `type` (`both` matches either). When present, `classification_center_id` / `_name` are ignored and the legacy FK mirrors rows[0]."
        payment_method_kind:
          type:
            - string
            - "null"
          enum:
            - bank_billet
            - ted
            - pix
            - credit_card
            - debit_card
            - cash
            - check
            - direct_debit
            - other
            - none
        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
    FinancialTransactionUpdateRequest:
      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{1,13}\.\d{1,2}$
          description: Decimal string with 1–2 fractional digits (up to 13 integer 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:
            - 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`.
        classification_center_id:
          type:
            - string
            - "null"
          format: uuid
        classification_center_name:
          type: string
          minLength: 1
          maxLength: 255
          description: Alternative to `classification_center_id`. Find-or-create with `revenue` polarity for credits / `cost` for debits — a center typed `both` matches either; creates with the narrow polarity when no match. Mutually exclusive with `classification_center_id`.
        classification_centers:
          type: array
          items:
            $ref: "#/components/schemas/TransactionClassificationCenterSplitInput"
          description: "Rateio (split) across classification centers. Replace-all: the array becomes the row's set; an empty array clears it; omit to leave it alone. Effective amounts must sum to `amount` (1-cent tolerance). Each center's polarity must match the transaction `type` (`both` matches either). When present, `classification_center_id` / `_name` are ignored and the legacy FK mirrors rows[0]."
        payment_method_kind:
          type:
            - string
            - "null"
          enum:
            - bank_billet
            - ted
            - pix
            - credit_card
            - debit_card
            - cash
            - check
            - direct_debit
            - other
            - none
        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.
  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:
  /financial-accounts:
    get:
      operationId: listFinancialAccounts
      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:
        - Financial 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:
            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/FinancialAccountListResponse"
        "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: createFinancialAccount
      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. The bank may be given as `bank_id` (UUID) or `bank_code` (public COMPE/BACEN, e.g. `341`) — not both (400); an unknown code → 422. The response echoes the resolved bank as `bank: { code, name }`. Pass an `X-Idempotency-Key` header on retries."
      tags:
        - Financial 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 workspace; the fingerprint covers the request method, path, and raw body."
          required: false
          name: X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FinancialAccountCreateRequest"
            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/FinancialAccountDetailV1"
        "400":
          description: Malformed JSON body, or `bank_id` and `bank_code` sent together.
          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: "`X-Idempotency-Key` reused with a different body."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure, `company_id` not in the tenant, or unknown `bank_code`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /financial-accounts/{id}:
    get:
      operationId: getFinancialAccount
      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:
        - Financial 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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Get a financial account by id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FinancialAccountDetailV1"
        "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: updateFinancialAccount
      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). The bank accepts `bank_id` or `bank_code` (not both → 400; unknown code → 422); the response echoes `bank: { code, name }`."
      tags:
        - Financial 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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FinancialAccountUpdateRequest"
            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/FinancialAccountDetailV1"
        "400":
          description: Malformed JSON body, or `bank_id` and `bank_code` sent together.
          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, `company_id` not in the tenant, or unknown `bank_code`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    delete:
      operationId: deleteFinancialAccount
      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:
        - Financial 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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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"
  /financial-accounts/{id}/adjust-balance:
    post:
      operationId: adjustAccountBalance
      summary: Adjust an account balance
      description: Corrects the account's calculated balance to `target_balance` by inserting an auto-created adjustment transaction for the signed diff. Requires scope `finance.accounts.write`. The `category_id` type must match the diff direction (credit for a positive diff, debit for negative) and its company must match the account's. A target equal to the current balance is rejected (422). Pass an `X-Idempotency-Key` header on retries.
      tags:
        - Financial Accounts
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - schema:
            type: string
            description: Opaque idempotency token. Same key + same body replays the cached response; same key + different body returns 409.
          required: false
          name: X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdjustBalanceRequest"
            examples:
              topUp:
                summary: Set the balance to R$ 1.500,00
                value:
                  target_balance: "1500.00"
                  category_id: c1b2c3d4-0000-4000-8000-000000000001
      responses:
        "201":
          description: Adjustment created. The `Location` header points at the auto-created adjustment transaction (`GET /financial-transactions/{transaction_id}`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdjustBalanceResultV1"
        "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 or category not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "409":
          description: "`X-Idempotency-Key` reuse with a different body."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: "Validation failure: target equals current balance, account without a company, or a category of the wrong type/company."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /payables/aggregates:
    get:
      operationId: aggregatePayables
      summary: Aggregate payable totals
      description: "Sums payables for the workspace tied to the token: `count`, `total`, `pending`, `paid`, `overdue_count`. Requires scope `finance.payables`. Filter by `company_id` and a period; omit all filters for all-time."
      tags:
        - Payables
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            description: Scope to one company. Omit to aggregate across the whole tenant.
          required: false
          name: company_id
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 12
            description: Calendar month (1–12). Requires `year`. Mutually exclusive with `start_date`/`end_date`.
          required: false
          name: month
          in: query
        - schema:
            type:
              - integer
              - "null"
            description: Calendar year. Requires `month`.
            example: 2026
          required: false
          name: year
          in: query
        - schema:
            type: string
            description: Window start (ISO 8601). Requires `end_date`. Mutually exclusive with `month`/`year`.
            example: 2026-07-01
          required: false
          name: start_date
          in: query
        - schema:
            type: string
            description: Window end (ISO 8601). Requires `start_date`.
            example: 2026-07-31
          required: false
          name: end_date
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Payable totals for the scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayableAggregatesV1"
        "400":
          description: Malformed query (unknown param, bad pairing, invalid date/uuid).
          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 resource read 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"
  /receivables/aggregates:
    get:
      operationId: aggregateReceivables
      summary: Aggregate receivable totals
      description: "Sums receivables for the workspace tied to the token: `count`, `total`, `pending`, `received`, `overdue_count`. Requires scope `finance.receivables`. Filter by `company_id` and a period; omit all filters for all-time."
      tags:
        - Receivables
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            description: Scope to one company. Omit to aggregate across the whole tenant.
          required: false
          name: company_id
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 12
            description: Calendar month (1–12). Requires `year`. Mutually exclusive with `start_date`/`end_date`.
          required: false
          name: month
          in: query
        - schema:
            type:
              - integer
              - "null"
            description: Calendar year. Requires `month`.
            example: 2026
          required: false
          name: year
          in: query
        - schema:
            type: string
            description: Window start (ISO 8601). Requires `end_date`. Mutually exclusive with `month`/`year`.
            example: 2026-07-01
          required: false
          name: start_date
          in: query
        - schema:
            type: string
            description: Window end (ISO 8601). Requires `start_date`.
            example: 2026-07-31
          required: false
          name: end_date
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Receivable totals for the scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReceivableAggregatesV1"
        "400":
          description: Malformed query (unknown param, bad pairing, invalid date/uuid).
          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 resource read 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"
  /financial-transactions/aggregates:
    get:
      operationId: aggregateTransactions
      summary: Aggregate transaction totals
      description: "Sums realised transactions for the workspace tied to the token (forecasts excluded): `count`, `credit`, `debit`, `net`. Requires scope `finance.transactions`. Filter by `company_id` and a period; omit all filters for all-time."
      tags:
        - Financial Transactions
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            description: Scope to one company. Omit to aggregate across the whole tenant.
          required: false
          name: company_id
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 12
            description: Calendar month (1–12). Requires `year`. Mutually exclusive with `start_date`/`end_date`.
          required: false
          name: month
          in: query
        - schema:
            type:
              - integer
              - "null"
            description: Calendar year. Requires `month`.
            example: 2026
          required: false
          name: year
          in: query
        - schema:
            type: string
            description: Window start (ISO 8601). Requires `end_date`. Mutually exclusive with `month`/`year`.
            example: 2026-07-01
          required: false
          name: start_date
          in: query
        - schema:
            type: string
            description: Window end (ISO 8601). Requires `start_date`.
            example: 2026-07-31
          required: false
          name: end_date
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Transaction totals for the scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionAggregatesV1"
        "400":
          description: Malformed query (unknown param, bad pairing, invalid date/uuid).
          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 resource read 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"
  /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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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 `X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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"
  /automatic-rules:
    get:
      operationId: listAutomaticRules
      summary: List automatic rules
      description: Returns a cursor-paginated page of automatic (classification) rules for the workspace tied to the token's `sub` claim. Rules fire on freshly-created transactions/billings, first-match-wins within `(company, scope, position)`. Default order is the evaluation order `(company, scope, position)`. Requires scope `finance.automatic_rules`.
      tags:
        - Automatic Rules
      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:
              - transactions
              - billings
            description: "Filter by target ledger: `transactions` or `billings`."
          required: false
          name: scope
          in: query
        - schema:
            type: boolean
            description: Filter by active flag. `?active=true` or `?active=false`.
          required: false
          name: active
          in: query
        - schema:
            type: string
            enum:
              - position
              - -position
              - created_at
              - -created_at
            description: "Sort field. Prefix with `-` for descending. Default: `(company, scope, position)` ascending — the evaluation order."
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Page of rules plus pagination metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutomaticRuleListResponse"
        "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.automatic_rules` 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: createAutomaticRule
      summary: Create an automatic rule
      description: Creates a rule and appends it at the end of its `(company, scope)` group (`position` = next). Requires scope `finance.automatic_rules.write`. `workspace_id` comes from the token. A rule needs at least one criterion and at least one action; `account_id` is only valid for `scope=transactions` and `set_expected_account_id` only for `scope=billings`. Pass an `X-Idempotency-Key` header on retries.
      tags:
        - Automatic Rules
      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 workspace."
          required: false
          name: X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AutomaticRuleCreateRequest"
            examples:
              categorizeBySupplier:
                summary: "On transactions: set category when description contains a keyword"
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  scope: transactions
                  description_contains: uber
                  set_category_id: 22222222-2222-2222-2222-222222222222
              routeBillings:
                summary: "On billings: set the expected settlement account for a supplier"
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  scope: billings
                  match_person_id: 33333333-3333-3333-3333-333333333333
                  set_expected_account_id: 44444444-4444-4444-4444-444444444444
      responses:
        "201":
          description: Rule created. The `Location` header points at the new row.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutomaticRuleV1"
        "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.automatic_rules.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "409":
          description: "`X-Idempotency-Key` reused with a different body."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure; `company_id` or a referenced entity not in the tenant; no criterion / no action; or a scope↔field mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /automatic-rules/{id}:
    get:
      operationId: getAutomaticRule
      summary: Get an automatic rule by id
      description: " Returns the same shape as the rows from the list endpoint. Requires scope `finance.automatic_rules`. 404 covers both \"doesn't exist\" and \"not in the tenant\" — the API does not distinguish."
      tags:
        - Automatic Rules
      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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Get an automatic rule by id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutomaticRuleV1"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.automatic_rules` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Rule 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: updateAutomaticRule
      summary: Update an automatic rule
      description: Partial update of criteria/actions and `active`. Requires scope `finance.automatic_rules.write`. `scope` and `company_id` are immutable (delete + recreate to move a rule); `position` is changed via `POST /automatic-rules/reorder`. The merged rule must still have at least one criterion and one action.
      tags:
        - Automatic Rules
      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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AutomaticRuleUpdateRequest"
            examples:
              deactivate:
                summary: Pause a rule
                value:
                  active: false
              retarget:
                summary: Change the assigned category
                value:
                  set_category_id: 55555555-5555-5555-5555-555555555555
      responses:
        "200":
          description: Updated rule.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutomaticRuleV1"
        "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.automatic_rules.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Rule not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure; a referenced entity not in the tenant; or the merged rule loses its last criterion/action.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
    delete:
      operationId: deleteAutomaticRule
      summary: Delete an automatic rule
      description: Hard-deletes a rule. Requires scope `finance.automatic_rules.write`. Surviving rules keep their positions (a gap is left — reorder to compact). Calling DELETE twice returns 204 then 404.
      tags:
        - Automatic Rules
      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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "204":
          description: Rule 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.automatic_rules.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Rule 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"
  /automatic-rules/reorder:
    post:
      operationId: reorderAutomaticRules
      summary: Reorder automatic rules
      description: Rewrites `position` for one `(company_id, scope)` group so it matches `ordered_ids` (first → position 1). Requires scope `finance.automatic_rules.write`. `ordered_ids` must be the COMPLETE ordered set for that group — a partial or foreign set is rejected (422). Idempotent via `X-Idempotency-Key`. Returns 204.
      tags:
        - Automatic Rules
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: Opaque idempotency token. Same key + same body replays; same key + different body returns 409.
          required: false
          name: X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AutomaticRuleReorderRequest"
            examples:
              reorder:
                summary: Put rule B before rule A
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  scope: transactions
                  ordered_ids:
                    - bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb
                    - aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
      responses:
        "204":
          description: Positions rewritten. 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.automatic_rules.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "409":
          description: "`X-Idempotency-Key` reused with a different body."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure; `company_id` not in the tenant; or `ordered_ids` is not the exact set for `(company, scope)`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /banks:
    get:
      operationId: listBanks
      summary: List banks
      description: Returns a cursor-paginated page of the global bank catalog (COMPE/BACEN codes). Requires scope `finance.banks`. Tenant-less reference data — every token sees the same rows. Defaults to alphabetical sort by `name`.
      tags:
        - Banks
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: Filter by exact COMPE/BACEN code (unique). Accepts `[in]=001,237,341` for OR matching.
            example: "341"
          required: false
          name: code
          in: query
        - schema:
            type: string
            description: Filter by exact institution name. Accepts `[in]=...` for OR matching.
            example: Itaú Unibanco S.A.
          required: false
          name: name
          in: query
        - schema:
            type: string
            enum:
              - name
              - -name
              - code
              - -code
            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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Page of banks plus pagination metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BankListResponse"
        "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.banks` 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"
  /cash-flow:
    get:
      operationId: getCashFlow
      summary: Get the cash-flow projection
      description: "Returns the cash-flow projection for the workspace tied to the token: a series of time buckets (realised + projected income/expenses, running balance) plus `current_balance`, the `today_bucket` seam and `retroactive_overdue`. Requires scope `finance.cash_flow`. Read-only."
      tags:
        - Cash Flow
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: Window start (ISO 8601). Required.
            example: 2026-01-01
          required: true
          name: from
          in: query
        - schema:
            type: string
            description: Window end (ISO 8601). Required.
            example: 2026-12-31
          required: true
          name: to
          in: query
        - schema:
            type: string
            enum:
              - day
              - week
              - month
              - year
            description: "Bucket size. Default `month`. A range over the per-granularity cap is clipped (`truncated: true`)."
          required: false
          name: granularity
          in: query
        - schema:
            type: string
            format: uuid
            description: Scope to one company. Omit for the consolidated (all-company) projection.
          required: false
          name: company_id
          in: query
        - schema:
            type: string
            format: uuid
            description: Scope to one financial account. Omit for all accounts.
          required: false
          name: account_id
          in: query
        - schema:
            type: string
            description: IANA time zone that anchors the `today` seam (e.g. `America/Sao_Paulo`, the default). Only the seam is tz-aware; bucketing stays UTC. An unrecognized value falls back to the default.
          required: false
          name: tz
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: The cash-flow projection.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CashFlowV1"
        "400":
          description: Malformed query (missing/invalid `from`/`to`, bad granularity, non-uuid scope id, unknown param).
          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.cash_flow` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: The `company_id` or `account_id` is 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"
  /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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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 `X-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 workspace; the fingerprint covers the request method, path, and raw body."
          required: false
          name: X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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: "`X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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 `X-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 workspace; the fingerprint covers the request method, path, and raw body."
          required: false
          name: X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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 `X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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, classification centers, financial accounts, 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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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"
  /classification_centers:
    get:
      operationId: listClassificationCenters
      summary: List classification centers
      description: Returns a cursor-paginated page of classification 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:
        - ClassificationCenters
      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
              - both
            description: "`cost` (deducts), `revenue` (adds) or `both` (usable on either side). Accepts `[in]=cost,revenue,both`."
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Page of classification centers plus pagination metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClassificationCenterListResponse"
        "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: createClassificationCenter
      summary: Create a classification center
      description: Creates a classification center. Requires scope `finance.cost_centers.write`. `workspace_id` is taken from the token. `type` (`cost` / `revenue`) drives the rateio polarity. Pass an `X-Idempotency-Key` header on retries.
      tags:
        - ClassificationCenters
      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 workspace; the fingerprint covers the request method, path, and raw body."
          required: false
          name: X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ClassificationCenterCreateRequest"
            examples:
              cost:
                summary: A classification 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
              both:
                summary: A center usable on both sides
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  name: Projetos
                  type: both
                  description: Custos e receitas por projeto
      responses:
        "201":
          description: Classification center created. The `Location` header points at the new row.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClassificationCenterV1"
        "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: "`X-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"
  /classification_centers/{id}:
    get:
      operationId: getClassificationCenter
      summary: Get a classification 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:
        - ClassificationCenters
      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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Get a classification center by id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClassificationCenterV1"
        "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: Classification 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: updateClassificationCenter
      summary: Update a classification 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:
        - ClassificationCenters
      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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ClassificationCenterUpdateRequest"
            examples:
              deactivate:
                summary: Soft-deactivate
                value:
                  active: false
              rename:
                summary: Rename + describe
                value:
                  name: Operacional (matriz)
                  description: Centro principal
      responses:
        "200":
          description: Updated classification center.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClassificationCenterV1"
        "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: Classification 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: deleteClassificationCenter
      summary: Delete a classification center
      description: "Deletes a classification center. Requires scope `finance.cost_centers.write`. Returns 422 (`reason: in_use`, with a `usage` breakdown) when the classification 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:
        - ClassificationCenters
      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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "204":
          description: Classification 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: Classification center not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: "The classification 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/ids:
    get:
      operationId: listPayableIds
      summary: List payable ids
      description: "Every payable id the token can see. Requires scope `finance.payables`. Pairs with `?updated_at[gte]=` on the listing. The delta reports what changed; this reports what still exists. A delta can never report a disappearance — the row it would have to describe is gone — so an id that stops appearing here is the only signal that something left. Diff this set against your mirror and drop what is missing. Takes no filters: the sweep must cover exactly what the listing shows, and a filter applied here but not to your mirror would read as mass deletion. Note that deletion is not the only exit: a payable whose payment authorization is rejected leaves the visible set while its row stays put, and it disappears from this sweep exactly as a deleted one would."
      tags:
        - Payables
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: Page size. Default 1000, max 10000 — far above the listing's 100, because a row here is one UUID.
          required: false
          name: limit
          in: query
        - schema:
            type: string
            description: Opaque cursor from the previous page's `meta.next_cursor`.
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Live payable ids.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IdSweepV1"
        "400":
          description: Malformed query. This endpoint takes no filters or sort — passing one is a 400.
          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 resource read 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"
  /receivables/ids:
    get:
      operationId: listReceivableIds
      summary: List receivable ids
      description: "Every receivable id the token can see. Requires scope `finance.receivables`. Pairs with `?updated_at[gte]=` on the listing. The delta reports what changed; this reports what still exists. A delta can never report a disappearance — the row it would have to describe is gone — so an id that stops appearing here is the only signal that something left. Diff this set against your mirror and drop what is missing. Takes no filters: the sweep must cover exactly what the listing shows, and a filter applied here but not to your mirror would read as mass deletion."
      tags:
        - Receivables
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: Page size. Default 1000, max 10000 — far above the listing's 100, because a row here is one UUID.
          required: false
          name: limit
          in: query
        - schema:
            type: string
            description: Opaque cursor from the previous page's `meta.next_cursor`.
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Live receivable ids.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IdSweepV1"
        "400":
          description: Malformed query. This endpoint takes no filters or sort — passing one is a 400.
          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 resource read 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"
  /financial-transactions/ids:
    get:
      operationId: listTransactionIds
      summary: List financial transaction ids
      description: "Every financial transaction id the token can see. Requires scope `finance.transactions`. Pairs with `?updated_at[gte]=` on the listing. The delta reports what changed; this reports what still exists. A delta can never report a disappearance — the row it would have to describe is gone — so an id that stops appearing here is the only signal that something left. Diff this set against your mirror and drop what is missing. Takes no filters: the sweep must cover exactly what the listing shows, and a filter applied here but not to your mirror would read as mass deletion. This is the largest resource in the API, which is what makes the pairing worth the trouble: re-paging the full history to find a handful of removals is exactly the cost the delta exists to avoid."
      tags:
        - Financial Transactions
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: Page size. Default 1000, max 10000 — far above the listing's 100, because a row here is one UUID.
          required: false
          name: limit
          in: query
        - schema:
            type: string
            description: Opaque cursor from the previous page's `meta.next_cursor`.
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Live financial transaction ids.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IdSweepV1"
        "400":
          description: Malformed query. This endpoint takes no filters or sort — passing one is a 400.
          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 resource read 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"
  /people/ids:
    get:
      operationId: listPersonIds
      summary: List person ids
      description: "Every person id the token can see. Requires scope `finance.people`. Pairs with `?updated_at[gte]=` on the listing. The delta reports what changed; this reports what still exists. A delta can never report a disappearance — the row it would have to describe is gone — so an id that stops appearing here is the only signal that something left. Diff this set against your mirror and drop what is missing. Takes no filters: the sweep must cover exactly what the listing shows, and a filter applied here but not to your mirror would read as mass deletion. People are soft-deleted, so a removed person's row survives internally — but it is filtered out of every v1 read, including this one, which makes its absence here the deletion signal."
      tags:
        - People
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: Page size. Default 1000, max 10000 — far above the listing's 100, because a row here is one UUID.
          required: false
          name: limit
          in: query
        - schema:
            type: string
            description: Opaque cursor from the previous page's `meta.next_cursor`.
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Live person ids.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IdSweepV1"
        "400":
          description: Malformed query. This endpoint takes no filters or sort — passing one is a 400.
          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 resource read 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"
  /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`, `classification_center_name`) and aggregate counts (`tax_charges_count`, `classification_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. Payables awaiting human authorization (see the create endpoint) are **omitted** from this list until an operator authorizes them in the app; a title is still retrievable by id via `GET /payables/{id}`. To **list the authorization queue** instead, filter it explicitly — `?payment_authorization=pending_authorization` (read-only; there is no authorize/reject action on this API). Each row carries its `payment_authorization` state. 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 classification-center UUID (not the rateio). Accepts `[in]`.
          required: false
          name: classification_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
            enum:
              - pending_authorization
              - authorizing
              - authorized
              - rejected
            description: Read-only view of the human payment-authorization gate. By default the list hides gated titles; filter this explicitly to **list the authorization queue** — e.g. `?payment_authorization=pending_authorization`. Accepts `[in]`. There is no authorize/reject action on this API; that stays a human action in the app.
          required: false
          name: payment_authorization
          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: "ISO timestamp filter on the last write. Range operators supported. This is the incremental-sync window: poll `?updated_at[gte]=<end of your last poll>&sort=updated_at` to fetch only what changed. Deletions do NOT appear here — a deleted payable has no row to return; reconcile them against `GET /v1/payables/ids`."
          required: false
          name: updated_at
          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
              - updated_at
              - -updated_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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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
                        classification_center_id: null
                        classification_center_name: null
                        financial_account_id: null
                        expected_account_id: 22222222-2222-2222-2222-222222222222
                        payment_method_kind: bank_billet
                        operation_kind: payment
                        payment_authorization: null
                        document: null
                        document_number: ALUG-2026-06
                        document_date: 2026-05-25T00:00:00.000Z
                        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
                        classification_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
                        classification_center_id: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
                        classification_center_name: Administrativo
                        financial_account_id: 22222222-2222-2222-2222-222222222222
                        expected_account_id: null
                        payment_method_kind: pix
                        operation_kind: transfer
                        payment_authorization: null
                        document: null
                        document_number: ENEL-202604
                        document_date: 2026-04-25T00:00:00.000Z
                        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
                        classification_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 / classification_center) is find-or-create; sending both `*_id` and `*_name` for the same FK returns 400. `classification_centers[]` (rateio) and `tax_charges[]` are accepted; the rateio Σ is validated against `amount`. When payment authorization is enabled for the workspace (a per-workspace setting, on by default), the created payable enters an **authorization queue**: it is returned here (with its id) but is **hidden from `GET /payables` until an operator authorizes it in the app** — fetch it by id meanwhile. With the setting off, the payable appears on `GET /payables` immediately. Pass an `X-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 workspace; the fingerprint covers the request method, path, and raw body."
          required: false
          name: X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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
                  classification_centers:
                    - classification_center_id: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
                      percentage: 60
                    - classification_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: "`X-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: `classification_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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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"
  /payables/{id}/send-to-bank:
    post:
      operationId: dispatchPayable
      summary: Dispatch a payable to the bank
      description: Dispatches an authorized payable to the bank through the Kobana gateway (creates the payment, then envelopes and approves it). Requires scope `finance.payables.dispatch` — deliberately **separate** from `finance.payables.write` (dispatching money is not editing a title) and, by default, never granted in the same token as `.authorize`. `X-Idempotency-Key` is **required**. Only an **official** title dispatches — one whose `payment_authorization` is `null` or `authorized`; a title still awaiting authorization (`pending_authorization` / `authorizing`) or `rejected` returns 422. The payable must also carry the payment coordinates for its method and a Kobana-mirrored source account. Returns the refreshed payable detail with the execution mirror (`kobana_status`, `kobana_payment_uid`) populated. **Note:** the Kobana sandbox `example_bank` does not execute outbound payments, so the full `confirmed` cycle is only observable against an environment that actually settles.
      tags:
        - Payables
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - schema:
            type: string
            description: "REQUIRED for this operation — a duplicate dispatch is duplicate money. Opaque token; same key + same body replays the cached response with `X-Idempotency-Replayed: true`. A missing / blank key returns 400."
          required: true
          name: X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Dispatched. Body is the refreshed payable detail with the Kobana mirror populated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayableDetailV1"
        "400":
          description: Missing / blank `X-Idempotency-Key`.
          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.dispatch` 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"
        "409":
          description: "`X-Idempotency-Key` reused with a different body."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: "Not dispatchable: title not official (awaiting authorization / rejected), missing payment coordinates, source account not mirrored in Kobana, or the Kobana integration is not connected."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /payables/{id}/cancel-dispatch:
    post:
      operationId: cancelPayableDispatch
      summary: Cancel a payable's bank dispatch
      description: Undoes a payable's bank dispatch on the Kobana side and mirrors the payment as `canceled` (the `kobana_*` mirror is kept, not cleared — the title returns to the manual rail because `canceled` is terminal). Requires scope `finance.payables.dispatch` and a **required** `X-Idempotency-Key`. A title that was never dispatched, or whose execution already reached a terminal state (`confirmed` / `canceled` / …), returns 422. Returns the refreshed payable detail.
      tags:
        - Payables
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
          required: true
          name: id
          in: path
        - schema:
            type: string
            description: "REQUIRED for this operation — a duplicate dispatch is duplicate money. Opaque token; same key + same body replays the cached response with `X-Idempotency-Replayed: true`. A missing / blank key returns 400."
          required: true
          name: X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Dispatch cancelled. Body is the refreshed payable detail (`kobana_status` = `canceled`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayableDetailV1"
        "400":
          description: Missing / blank `X-Idempotency-Key`.
          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.dispatch` 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"
        "409":
          description: "`X-Idempotency-Key` reused with a different body."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: "Not cancellable: the title was never dispatched, or its execution already reached a terminal state."
          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
            description: "ISO timestamp filter on the last write. Range operators supported. This is the incremental-sync window: poll `?updated_at[gte]=<end of your last poll>&sort=updated_at` to fetch only what changed. Deletions do NOT appear here — a deleted person is filtered out of every v1 read; reconcile them against `GET /v1/people/ids`."
          required: false
          name: updated_at
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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 `X-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 workspace; the fingerprint covers the request method, path, and raw body."
          required: false
          name: X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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 `X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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). The CPF/CNPJ is re-validated only when `document_number` / `document_type` actually changes — re-sending the stored value verbatim (even a legacy/invalid one) is accepted so unrelated edits aren't blocked. A `document_type` sent on its own is rejected with 422 when it contradicts the stored number's digit count (e.g. tagging an 11-digit number as `cnpj`).
      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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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, invalid CPF/CNPJ check digits when the document is changed, or a type-only change that contradicts the stored number's digit count.
          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). Refused with 409 while a classification rule still references the person (as match criterion or set action) — edit or remove the rule first.
      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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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"
        "409":
          description: The person is still referenced by a classification rule (match or set) — edit/remove the rule before deleting.
          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 classification-center UUID. Accepts `[in]`.
          required: false
          name: classification_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: "ISO timestamp filter on the last write. Range operators supported. This is the incremental-sync window: poll `?updated_at[gte]=<end of your last poll>&sort=updated_at` to fetch only what changed. Deletions do NOT appear here — a deleted receivable has no row to return; reconcile them against `GET /v1/receivables/ids`."
          required: false
          name: updated_at
          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
              - updated_at
              - -updated_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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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
                        classification_center_id: null
                        classification_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
                        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
                        classification_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
                        classification_center_id: null
                        classification_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
                        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
                        classification_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 / classification_center) is find-or-create (customer/credit/revenue polarity); sending both `*_id` and `*_name` for the same FK returns 400. `classification_centers[]` (rateio) and `tax_charges[]` are accepted. Pass an `X-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 workspace; the fingerprint covers the request method, path, and raw body."
          required: false
          name: X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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: "`X-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 `classification_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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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"
  /reconciliations:
    post:
      operationId: createReconciliation
      summary: Create a reconciliation
      description: Reconciles billings against bank transactions (n:m), settling every billing (paid/received). Requires scope `finance.reconciliations.write`. All billings must share one polarity and one company; transaction polarity must match (payable↔debit, receivable↔credit). A billing already reconciled/linked, cancelled or draft is rejected (422). Pass an `X-Idempotency-Key` header on retries.
      tags:
        - Reconciliations
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            description: Opaque idempotency token. Same key + same body replays the cached response; same key + different body returns 409.
          required: false
          name: X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReconciliationCreateRequest"
            examples:
              oneToOne:
                summary: Reconcile one payable with one debit transaction
                value:
                  billings:
                    - kind: payable
                      id: a1b2c3d4-0000-4000-8000-000000000001
                  transaction_ids:
                    - b1b2c3d4-0000-4000-8000-000000000002
      responses:
        "201":
          description: Reconciliation created; the `Location` header points at `GET /reconciliations/{id}`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReconciliationV1"
        "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.reconciliations.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: A billing or transaction was not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "409":
          description: "`X-Idempotency-Key` reuse with a different body."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: "Validation failure: mixed polarity/company, wrong transaction polarity, a member already reconciled/linked, or a cancelled/draft billing."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /reconciliations/{id}:
    get:
      operationId: getReconciliation
      summary: Get a reconciliation by id
      description: Returns the reconciliation's light id-list shape (`source`, `kind`, `billing_ids`, `transaction_ids`). Requires scope `finance.reconciliations`. 404 when not in the tenant.
      tags:
        - Reconciliations
      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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: The reconciliation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReconciliationV1"
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.reconciliations` scope, or `sub` does not match any workspace.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Reconciliation 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: deleteReconciliation
      summary: Delete a reconciliation
      description: "Dissolves a `manual` reconciliation: reverts every member billing to pending and clears the links. Requires scope `finance.reconciliations.write`. Imported (`fintera`) reconciliations are read-only → 422."
      tags:
        - Reconciliations
      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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "204":
          description: Reconciliation dissolved. No response body.
        "401":
          description: Missing / invalid / expired / revoked token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "403":
          description: Token lacks the `finance.reconciliations.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: Reconciliation not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: "The reconciliation is imported (`source: fintera`) and cannot be dissolved via the API."
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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 `X-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 workspace; the fingerprint covers the request method, path, and raw body."
          required: false
          name: X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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 `X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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"
  /financial-transactions:
    get:
      operationId: listFinancialTransactions
      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:
        - Financial 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 classification-center UUID. Accepts `[in]`.
          required: false
          name: classification_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: "ISO timestamp filter on the last write. Range operators supported. This is the incremental-sync window: poll `?updated_at[gte]=<end of your last poll>&sort=updated_at` to fetch only what changed. Deletions do NOT appear here — a deleted transaction has no row to return; reconcile them against `GET /v1/financial-transactions/ids`."
          required: false
          name: updated_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
              - updated_at
              - -updated_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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Page of transactions plus pagination metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FinancialTransactionListResponse"
              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
                        classification_center_id: null
                        classification_center_allocations: []
                        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
                        classification_center_id: null
                        classification_center_allocations: []
                        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: createFinancialTransaction
      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 / classification_center) for CSV-importer ergonomics; sending both for the same FK returns 400. Send `classification_centers[]` to split the amount across classification centers (rateio): effective amounts must sum to `amount` (1-cent tolerance) and each center's polarity must match `type` (`both` matches either). When present it supersedes `classification_center_id`/`_name` (which then mirrors rows[0]); the full split rides back on every read as `classification_center_allocations[]`. Pass an `X-Idempotency-Key` header on retries to avoid duplicate inserts."
      tags:
        - Financial 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 workspace; the fingerprint covers the request method, path, and raw body."
          required: false
          name: X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FinancialTransactionCreateRequest"
            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
              withRateio:
                summary: Debit split across two classification centers (rateio)
                value:
                  company_id: 11111111-1111-1111-1111-111111111111
                  financial_account_id: 22222222-2222-2222-2222-222222222222
                  description: Aluguel rateado entre centros
                  amount: "1000.00"
                  occurred_at: 2026-05-24T00:00:00.000Z
                  type: debit
                  classification_centers:
                    - classification_center_id: 55555555-5555-5555-5555-555555555555
                      percentage: 70
                    - classification_center_id: 66666666-6666-6666-6666-666666666666
                      amount: "300.00"
      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/FinancialTransactionV1"
              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
                    classification_center_id: null
                    classification_center_allocations: []
                    payment_method_kind: ted
                    classified: false
                    notes: null
                    created_at: 2026-05-24T10:32:18.412Z
                createdWithRateio:
                  summary: Created transaction with a two-center rateio (mirrors the withRateio request)
                  value:
                    id: 7b7a3b3e-1d9d-3e5d-8a7f-1f0b1a2c3d4e
                    company_id: 11111111-1111-1111-1111-111111111111
                    financial_account_id: 22222222-2222-2222-2222-222222222222
                    description: Aluguel rateado entre centros
                    bank_description: null
                    document: null
                    document_date: null
                    amount: "1000.00"
                    occurred_at: 2026-05-24T00:00:00.000Z
                    competence_date: 2026-05-24T00:00:00.000Z
                    type: debit
                    category_id: null
                    person_id: null
                    classification_center_id: 55555555-5555-5555-5555-555555555555
                    classification_center_allocations:
                      - classification_center_id: 55555555-5555-5555-5555-555555555555
                        classification_center_name: Administrativo
                        percentage: "70"
                        amount: "700.00"
                      - classification_center_id: 66666666-6666-6666-6666-666666666666
                        classification_center_name: Comercial
                        percentage: null
                        amount: "300.00"
                    payment_method_kind: null
                    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: "`X-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: X-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"
  /financial-transactions/{id}:
    patch:
      operationId: updateFinancialTransaction
      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:
        - Financial 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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FinancialTransactionUpdateRequest"
      responses:
        "200":
          description: Updated transaction.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FinancialTransactionV1"
        "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: deleteFinancialTransaction
      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:
        - Financial 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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          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: getFinancialTransaction
      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:
        - Financial 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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: Get a transaction by id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FinancialTransactionV1"
        "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"
  /transfers:
    post:
      operationId: createTransfer
      summary: Create a transfer
      description: Creates an account-to-account transfer as a linked debit/credit pair sharing one `transfer_id`. Requires scope `finance.transactions.write`. The two accounts must differ, both must belong to the tenant, and both must have a linked company (cross-company is allowed). Pass an `X-Idempotency-Key` header on retries.
      tags:
        - Transfers
      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."
          required: false
          name: X-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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TransferCreateRequest"
            examples:
              basic:
                summary: Move R$ 1.500,00 between two accounts
                value:
                  from_account_id: a1b2c3d4-0000-4000-8000-000000000001
                  to_account_id: a1b2c3d4-0000-4000-8000-000000000002
                  amount: "1500.00"
                  occurred_at: 2026-07-10T00:00:00.000Z
                  description: Aporte para conta pagamentos
      responses:
        "201":
          description: Transfer created. The `Location` header points at `GET /transfers/{transfer_id}`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransferV1"
        "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.transactions.write` scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "404":
          description: A source/destination account was not found within the tenant.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "409":
          description: "`X-Idempotency-Key` reuse with a different body."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "422":
          description: Schema validation failure, same source/destination account, non-positive amount, or an account without a linked company.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
        "500":
          description: Unhandled server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiV1Error"
  /transfers/{id}:
    get:
      operationId: getTransfer
      summary: Get a transfer by id
      description: Returns the linked debit/credit pair for a `transfer_id`. Requires scope `finance.transactions`. 404 when the transfer is not in the tenant.
      tags:
        - Transfers
      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:
            type: string
            example: Kevin Mitnick <kmitnick@example.com>
          example: Kevin Mitnick <kmitnick@example.com>
      responses:
        "200":
          description: The transfer.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransferV1"
        "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: Transfer 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: {}
