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

# List transactions

> AppTransactions, account Activity and Home "Recent transactions". Newest first. `account_id` matches the debited or credited account and,
for internal moves, either side. Type tabs: All, Payouts, Collections, Transfers. "Needs attention" counts come from `status=failed` / `status=processing`
with `include=total_count`. "Export CSV": send `Accept: text/csv` with the same filters (up to 10,000 rows; use statements for more).

**Error codes:** `invalid_api_key`, `parameter_invalid`, `rate_limited`.



## OpenAPI

````yaml /api-reference/openapi.yaml get /transactions
openapi: 3.1.0
info:
  title: PanoFlow API
  version: 0.3.0
  description: >-
    Identity, business, team, customer-side onboarding (KYB), accounts,
    statements, the customer transactions view,

    in-app notifications and search for the PanoFlow public API. The customer
    web app (app.panoflow.io) uses exactly these endpoints;

    it has no private API. Rules: `CONVENTIONS.md`. Behaviour:
    `docs/architecture.md`. Design/architecture conflicts:

    `docs/discrepancies/identity-accounts.md`.


    ## Authentication


    * **Customer session** (`__Host-pf_session` cookie; the web app only). The
    browser calls the API through the web app's same-origin
      proxy, `https://app.panoflow.io/api/v1/...`, which forwards to `https://api.panoflow.io/v1/...` (ADR 0004). The cookie is host-only on
      app.panoflow.io (`__Host-` prefix, Secure, HttpOnly, SameSite=Lax, Path=/, no Domain), so the browser never sends it to
      api.panoflow.io and no credentialed CORS is needed; api.panoflow.io ignores a session cookie that didn't come through the proxy.
      The session ends after 15 minutes without activity or 12 hours after sign-in, whichever comes first, and is revoked on a password or
      2FA change (ADR 0005). A session starts `pending` and becomes `active` once sign-up is finished (legal name, phone verified,
      authenticator enrolled) or, for a returning user, once 2FA is passed.
    * **CSRF.** Every state-changing request (POST, PATCH, DELETE) made with the
    session cookie sends `X-CSRF-Token` with the
      `csrf_token` of the current session. The proxy passes the header and the browser's `Origin` through unchanged and never adds a
      token itself; the API checks the token against the session and that `Origin` is `https://app.panoflow.io`, and answers 403
      `csrf_token_invalid` otherwise. Sign-up, sign-in and the other unauthenticated auth calls have no token yet, so through the proxy
      only the `Origin` check applies to them. API keys and OAuth tokens never send the header.
    * **API key** (`Authorization: Bearer pk_live_…`) and **OAuth 2.1** access
    tokens (agents, MCP) can call the business, team-read,
      onboarding-read, account, statement, transaction and search operations allowed by their scopes. They can never call `/v1/auth/*`,
      `/v1/users/me/*` or `/v1/notifications*` (session only), and agent tokens never hold `team:*`.

    ## `x-required-scope`


    * `none`: unauthenticated (sign-up, sign-in, password reset, invite links).
    Rate-limited per IP and per account.

    * `self`: any signed-in user acting on their own user, session or
    notifications. Session only.

    * `<resource>:<read|write>`: the scope an API key or token needs. For
    sessions the scope comes from the member's role:


    | Role | Scopes (this file) | Notes |

    | --- | --- | --- |

    | owner | business:read/write, team:read/write, accounts:read/write |
    Everything, including closing the business account. |

    | admin | business:read/write, team:read/write, accounts:read/write |
    Everything except changing the owner. |

    | finance | business:read, team:read, accounts:read/write | Pays up to the
    approval threshold alone. |

    | approver | business:read, team:read, accounts:read | Decides pending
    approvals; can't start payments. |

    | developer | business:read, team:read | API keys, webhooks, logs. No
    balances or payments. |

    | viewer | business:read, team:read, accounts:read | Read only. |


    Some writes are limited further by role ("**Roles:**" in the operation
    description); a disallowed role gets 403 `role_not_permitted`.


    ## Step-up


    Operations marked "**Step-up**" need `X-Step-Up-Token` from `POST
    /v1/auth/step-up` (TOTP or backup code within 5 minutes).


    ## Errors


    Errors use the shared `Error` shape, with `error.response_code` (ISO 8583,
    CONVENTIONS §7a) mapped from `error.code`. Each operation lists the

    codes it can return. Non-money resources in this file carry no
    `response_code`; `Transaction` (a view of money movements) composes
    `ResponseOutcome`.



    Generated from panobaas-contracts; only operations marked `x-audience:
    public`.
  contact:
    name: PanoFlow API
    url: https://docs.panoflow.io
    email: api@panoflow.io
servers:
  - url: https://api.panoflow.io/v1
    description: Live and sandbox (sandbox is chosen by `pk_test_` keys and test sessions).
  - url: https://api-dev-9d8b.up.railway.app/v1
    description: >-
      Development (Railway). Synthetic data and fake partners; redeployed from
      main, may be reset at any time.
security: []
tags:
  - name: Business
    description: >-
      The business (`/v1/businesses/me`): CAC record, how it appears, and the
      approval threshold for the customer-side two-person rule.
    x-displayName: Business
  - name: Accounts
    description: >-
      Main, Payroll and sub-accounts with their balances; open, rename, close,
      share details, confirmation letters and balance totals. Static virtual
      accounts are in the collections API.
    x-displayName: Accounts
  - name: Statements
    description: >-
      Monthly and custom-range account statements, generated asynchronously as
      PDF and CSV.
    x-displayName: Statements
  - name: Transactions
    description: >-
      The customer transactions view: every movement in and out of the
      business's accounts, with filters, notes, attachments, receipts and CSV
      export.
    x-displayName: Transactions
  - name: Banks
    description: >
      The bank picker and bank name enquiry. Name enquiry asks the receiving
      bank for the name on an account before

      anyone pays it or saves it as a recipient.
    x-displayName: Banks
  - name: Recipients
    description: >
      The people and businesses a business pays (Payments → Recipients). Every
      recipient is name-enquiry verified when

      it is saved. Recipients are archived, never deleted.
    x-displayName: Recipients
  - name: Transfers
    description: >
      One resource for money leaving an account: `payout` to a bank account
      outside PanoFlow (NIP), and

      `own_account` moves between the business's own accounts in the same
      currency. `reversal` transfers made by

      PanoFlow also appear here, read-only.
    x-displayName: Transfers
  - name: Batches
    description: >
      Pay many: validate a CSV (or JSON rows), then create a batch. Each valid
      row becomes its own transfer with

      `batch_id`; rows that fail validation are skipped and listed with a
      reason.
    x-displayName: Batches
  - name: Scheduled payments
    description: >
      Payments that repeat (or run once on a future date). Each run creates a
      normal transfer, checked like any other.
    x-displayName: Scheduled payments
  - name: Approvals
    description: >
      When a second person on your team must approve. One generic endpoint for
      every customer `ApprovalKind`:

      transfers and batches over the approval threshold, agent payments over an
      agent's ask-first amount, pay runs,

      role changes and limit changes.
    x-displayName: Approvals
  - name: Limits
    description: >-
      Read-only payment limits for the business (per payment, daily, approval
      threshold).
    x-displayName: Limits
  - name: Collections
    description: >-
      Header figures for the Collections screen (outstanding, overdue, collected
      this month, average days to pay) and saved customers (payers).
    x-displayName: Collections
  - name: Invoices
    description: >
      Create, send, chase, void and track invoices. VAT (7.5%) is computed
      server-side. An invoice can have its

      own account number; payments into it are recorded against the invoice
      automatically, including part payments.
    x-displayName: Invoices
  - name: Payment links
    description: >-
      Reusable pay pages at `pay.panoflow.io/<slug>` with a fixed or
      customer-entered amount. Can be paused and turned back on.
    x-displayName: Payment links
  - name: Virtual accounts
    description: >
      Static virtual accounts only: one permanent account number per customer,
      settling into one of your accounts.

      Per-invoice account numbers live on the invoice (`virtual_account`), not
      here.
    x-displayName: Virtual accounts
  - name: Inbound credits
    description: >-
      Money in. Every transfer received into an invoice account, a static
      virtual account, a pay page checkout or one of your accounts, and what it
      was matched to.
    x-displayName: Inbound credits
  - name: People
    description: >-
      Add, list, update and offboard the people you pay, and the deductions
      added to each person.
    x-displayName: People
  - name: Deduction types
    description: >-
      Deductions set up once and applied to everyone or to selected people
      (PAYE, pension, NHF, loans, advances, savings, custom levies).
    x-displayName: Deduction types
  - name: Payroll settings
    description: >-
      Pay day, payslip emails, reminders, automatic pay, the second approver and
      the account runs are paid from.
    x-displayName: Payroll settings
  - name: Pay runs
    description: >
      Prepare a month's run (computes lines), adjust who is paid and one-off
      bonuses, approve with 2FA (and a second

      approver above the threshold), then execute. Execution is asynchronous
      (202); poll the run or wait for

      `payrun.completed`.
    x-displayName: Pay runs
  - name: Payslips
    description: Payslips issued for paid lines. Download as PDF or email to the person.
    x-displayName: Payslips
  - name: OAuth
    description: >-
      OAuth 2.1 authorization server for MCP clients and other apps: metadata,
      dynamic client registration, the consent page's API (2FA at grant time),
      grants (connected apps), token and revocation endpoints. Access tokens
      live 15 minutes; refresh tokens rotate and are bound to the grant.
    x-displayName: OAuth
  - name: Webhooks
    description: >-
      Endpoints that receive signed events (HMAC-SHA256, `PanoFlow-Signature`),
      their signing secrets, test events, and deliveries with response codes and
      latency, retry and replay.
    x-displayName: Webhooks
  - name: Events
    description: >-
      The event log behind webhooks (30 days), replay, and the event-type
      catalogue.
    x-displayName: Events
paths:
  /transactions:
    get:
      tags:
        - Transactions
      summary: List transactions
      description: >-
        AppTransactions, account Activity and Home "Recent transactions". Newest
        first. `account_id` matches the debited or credited account and,

        for internal moves, either side. Type tabs: All, Payouts, Collections,
        Transfers. "Needs attention" counts come from `status=failed` /
        `status=processing`

        with `include=total_count`. "Export CSV": send `Accept: text/csv` with
        the same filters (up to 10,000 rows; use statements for more).


        **Error codes:** `invalid_api_key`, `parameter_invalid`, `rate_limited`.
      operationId: listTransactions
      parameters:
        - $ref: '#/components/parameters/RequestId'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/After'
        - $ref: '#/components/parameters/Before'
        - name: account_id
          in: query
          required: false
          description: Comma-separated account ids.
          schema:
            type: array
            items:
              type: string
          style: form
          explode: false
          example:
            - acc_01JCNND0RKATSR2MYBK8VQ3468
        - name: type
          in: query
          required: false
          description: Comma-separated transaction types.
          schema:
            type: array
            items:
              type: string
          style: form
          explode: false
          example:
            - payout
            - collection
        - name: status
          in: query
          required: false
          description: Comma-separated statuses.
          schema:
            type: array
            items:
              type: string
          style: form
          explode: false
          example:
            - failed
            - processing
        - name: direction
          in: query
          required: false
          description: Money in, out or between own accounts.
          schema:
            type: string
            enum:
              - in
              - out
              - internal
          example: in
        - $ref: '#/components/parameters/CreatedGte'
        - $ref: '#/components/parameters/CreatedLte'
        - name: amount_gte
          in: query
          required: false
          description: Minimum amount in minor units.
          schema:
            type: integer
            format: int64
            minimum: 0
          example: 10000000
        - name: amount_lte
          in: query
          required: false
          description: Maximum amount in minor units.
          schema:
            type: integer
            format: int64
            minimum: 0
          example: 500000000
        - name: category
          in: query
          required: false
          description: Exact category.
          schema:
            type: string
            maxLength: 40
          example: Fuel
        - name: source_id
          in: query
          required: false
          description: Transactions of one source object (a batch, pay run, invoice, ...).
          schema:
            type: string
          example: run_01JC3F01BK3VK5ZHFXVTX5XQW5
        - name: livemode
          in: query
          required: false
          description: >-
            Sandbox (`false`) or live (`true`) data. Defaults to the session's
            mode.
          schema:
            type: boolean
          example: true
        - $ref: '#/components/parameters/Query'
        - $ref: '#/components/parameters/IncludeTotalCount'
      responses:
        '200':
          description: OK.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                description: Transactions.
                allOf:
                  - $ref: '#/components/schemas/ListEnvelope'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/Transaction'
                        description: Items, newest first.
              examples:
                example:
                  summary: October
                  value:
                    object: list
                    data:
                      - response_code: '09'
                        response_message: Request processing in progress
                        id: txn_01JCKFR5NXF5E32DSDWR52C6X9
                        object: transaction
                        created_at: '2026-10-28T08:00:00Z'
                        updated_at: '2026-10-28T08:31:00Z'
                        type: payout
                        status: processing
                        direction: out
                        amount:
                          amount: 229745000
                          currency: NGN
                        fee: null
                        account_id: acc_01JCQF1Y99G6K6NSAK9M83PSW5
                        account_display_name: Naira · Payroll
                        counterparty:
                          type: pay_run
                          name: October salaries · 11 people
                          bank_account: null
                          account_id: null
                          recipient_id: null
                        description: October salaries · 11 people
                        reference: Bulk payout
                        source:
                          type: pay_run
                          id: run_01JC3F01BK3VK5ZHFXVTX5XQW5
                        initiated_by:
                          type: user
                          id: usr_01JCC3128TMRXGSNHYMZYCF4FZ
                          name: Adaeze Okafor
                        approved_by:
                          type: user
                          id: usr_01JCCYD6WPX04EWRQ599Q9700T
                          name: Tolu Martins
                        category: Salaries
                        note: null
                        attachment_document_ids: []
                        failure: null
                        events:
                          - type: created
                            description: Created by Adaeze Okafor
                            at: '2026-10-28T08:00:00Z'
                            actor:
                              type: user
                              id: usr_01JCC3128TMRXGSNHYMZYCF4FZ
                              name: Adaeze Okafor
                          - type: approved
                            description: Approved by Tolu Martins
                            at: '2026-10-28T08:20:00Z'
                            actor:
                              type: user
                              id: usr_01JCCYD6WPX04EWRQ599Q9700T
                              name: Tolu Martins
                          - type: sent
                            description: Sent to the receiving banks
                            at: '2026-10-28T08:31:00Z'
                            actor: null
                        nip_session_id: null
                        completed_at: null
                        livemode: true
                      - response_code: '00'
                        response_message: Approved or completed successfully
                        id: txn_01JC31CVY7B6Y8XTZW3AEMMED3
                        object: transaction
                        created_at: '2026-10-27T08:41:00Z'
                        updated_at: '2026-10-27T08:41:00Z'
                        type: collection
                        status: completed
                        direction: in
                        amount:
                          amount: 125000000
                          currency: NGN
                        fee: null
                        account_id: acc_01JCNND0RKATSR2MYBK8VQ3468
                        account_display_name: Naira · Main
                        counterparty:
                          type: bank_account
                          name: Kano Freight Services
                          bank_account:
                            account_number: '0123984471'
                            bank_code: '058'
                            bank_name: GTBank
                            account_name: KANO FREIGHT SERVICES LTD
                          account_id: null
                          recipient_id: null
                        description: Kano Freight Services
                        reference: INV-0142
                        source:
                          type: invoice
                          id: ivc_01JCFK328EG017P1145YK913EH
                        initiated_by: null
                        approved_by: null
                        category: Sales
                        note: null
                        attachment_document_ids: []
                        failure: null
                        events:
                          - type: received
                            description: Received
                            at: '2026-10-27T08:41:00Z'
                            actor: null
                          - type: matched
                            description: Recorded against INV-0142
                            at: '2026-10-27T08:41:00Z'
                            actor: null
                          - type: completed
                            description: Settled
                            at: '2026-10-27T08:41:00Z'
                            actor: null
                        nip_session_id: '000013261027094100123984471881'
                        completed_at: '2026-10-27T08:41:00Z'
                        livemode: true
                      - response_code: '00'
                        response_message: Approved or completed successfully
                        id: txn_01JC99Y651CH732QS0JYGWR759
                        object: transaction
                        created_at: '2026-10-27T07:15:00Z'
                        updated_at: '2026-10-27T07:15:00Z'
                        type: transfer
                        status: completed
                        direction: internal
                        amount:
                          amount: 240000000
                          currency: NGN
                        fee: null
                        account_id: acc_01JCNND0RKATSR2MYBK8VQ3468
                        account_display_name: Naira · Main
                        counterparty:
                          type: own_account
                          name: Naira · Payroll
                          bank_account: null
                          account_id: acc_01JCQF1Y99G6K6NSAK9M83PSW5
                          recipient_id: null
                        description: Naira · Main to Naira · Payroll
                        reference: Own accounts
                        source:
                          type: transfer
                          id: trf_01JC71GSH285W3WGDCD827WR1H
                        initiated_by:
                          type: user
                          id: usr_01JCC3128TMRXGSNHYMZYCF4FZ
                          name: Adaeze Okafor
                        approved_by: null
                        category: null
                        note: null
                        attachment_document_ids: []
                        failure: null
                        events:
                          - type: created
                            description: Created by Adaeze Okafor
                            at: '2026-10-27T07:15:00Z'
                            actor:
                              type: user
                              id: usr_01JCC3128TMRXGSNHYMZYCF4FZ
                              name: Adaeze Okafor
                          - type: completed
                            description: Settled
                            at: '2026-10-27T07:15:00Z'
                            actor: null
                        nip_session_id: null
                        completed_at: '2026-10-27T07:15:00Z'
                        livemode: true
                      - response_code: '00'
                        response_message: Approved or completed successfully
                        id: txn_01JCHS5CC3TEEQV903RVCV0YEW
                        object: transaction
                        created_at: '2026-10-25T15:48:00Z'
                        updated_at: '2026-10-25T15:48:40Z'
                        type: payout
                        status: completed
                        direction: out
                        amount:
                          amount: 60000000
                          currency: NGN
                        fee:
                          amount: 5375
                          currency: NGN
                        account_id: acc_01JCNND0RKATSR2MYBK8VQ3468
                        account_display_name: Naira · Main
                        counterparty:
                          type: bank_account
                          name: Total Energies
                          bank_account:
                            account_number: '1013377920'
                            bank_code: '057'
                            bank_name: Zenith Bank
                            account_name: TOTAL ENERGIES MARKETING NIG PLC
                          account_id: null
                          recipient_id: null
                        description: Total Energies
                        reference: Diesel supply
                        source:
                          type: transfer
                          id: trf_01JCDCEKM598BSNK9886DFRC60
                        initiated_by:
                          type: user
                          id: usr_01JCQ5P8YZWNBVQ8HSK8N67DHE
                          name: Tunde Bakare
                        approved_by: null
                        category: Fuel
                        note: null
                        attachment_document_ids: []
                        failure: null
                        events:
                          - type: created
                            description: Created by Tunde Bakare
                            at: '2026-10-25T15:48:00Z'
                            actor:
                              type: user
                              id: usr_01JCQ5P8YZWNBVQ8HSK8N67DHE
                              name: Tunde Bakare
                          - type: sent
                            description: Sent to the receiving bank
                            at: '2026-10-25T15:48:10Z'
                            actor: null
                          - type: completed
                            description: Settled
                            at: '2026-10-25T15:48:40Z'
                            actor: null
                        nip_session_id: '000013261025164810101337792042'
                        completed_at: '2026-10-25T15:48:40Z'
                        livemode: true
                      - response_code: '07'
                        response_message: Invalid account
                        id: txn_01JCN79G430A3WHT69SZ7MJBBG
                        object: transaction
                        created_at: '2026-10-25T09:02:00Z'
                        updated_at: '2026-10-25T09:41:00Z'
                        type: payout
                        status: failed
                        direction: out
                        amount:
                          amount: 15000000
                          currency: NGN
                        fee: null
                        account_id: acc_01JCNND0RKATSR2MYBK8VQ3468
                        account_display_name: Naira · Main
                        counterparty:
                          type: bank_account
                          name: Chidi Eze
                          bank_account:
                            account_number: '3089912245'
                            bank_code: '011'
                            bank_name: First Bank
                            account_name: EZE CHIDI JOHN
                          account_id: null
                          recipient_id: null
                        description: Chidi Eze
                        reference: Lagos dispatch
                        source:
                          type: transfer
                          id: trf_01JCG8D76RRAG1B7KPS05S5KR2
                        initiated_by:
                          type: user
                          id: usr_01JC85CBTESY1N92X9NGAQ954H
                          name: Ngozi Uche
                        approved_by: null
                        category: Contractors
                        note: null
                        attachment_document_ids: []
                        failure:
                          code: account_name_mismatch
                          message: >-
                            The receiving bank rejected the payment: account
                            number and name don't match. The money is back in
                            Naira · Main.
                        events:
                          - type: created
                            description: Created by Ngozi Uche
                            at: '2026-10-25T09:02:00Z'
                            actor:
                              type: user
                              id: usr_01JC85CBTESY1N92X9NGAQ954H
                              name: Ngozi Uche
                          - type: sent
                            description: Sent to the receiving bank
                            at: '2026-10-25T09:03:00Z'
                            actor: null
                          - type: failed
                            description: Rejected, money returned
                            at: '2026-10-25T09:41:00Z'
                            actor: null
                        nip_session_id: '000013261025100300308991224577'
                        completed_at: null
                        livemode: true
                    has_more: true
                    next_cursor: txn_01JCN79G430A3WHT69SZ7MJBBG
            text/csv:
              schema:
                type: string
                description: >-
                  CSV with a header row: date, description, reference, type,
                  account, status, direction, amount, currency, fee,
                  transaction_id.
              example: >
                date,description,reference,type,account,status,direction,amount,currency,fee,transaction_id

                2026-10-27T08:41:00Z,Kano Freight
                Services,INV-0142,collection,Naira ·
                Main,completed,in,1250000.00,NGN,,txn_01JC31CVY7B6Y8XTZW3AEMMED3
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
      security:
        - ApiKey: []
        - OAuth2: []
components:
  parameters:
    RequestId:
      name: X-Request-Id
      in: header
      required: false
      description: >
        Optional client correlation id, up to 128 characters. It is not echoed:
        the response's `X-Request-Id` is always

        our own `req_…` id. We log your value with the request as
        `client_request_id`, so support can find a request by

        either id. A longer value is refused with 400 `parameter_invalid`.
      schema:
        type: string
        maxLength: 128
    Limit:
      name: limit
      in: query
      description: Page size, 1–100.
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 25
    After:
      name: after
      in: query
      description: >-
        Cursor from the previous page's `next_cursor`. Returns older items.
        Don't send with `before`.
      schema:
        type: string
    Before:
      name: before
      in: query
      description: >
        Cursor for the reverse direction: returns the items newer than this one.
        The page is still sorted newest first;

        `has_more` and `next_cursor` then continue in the same (newer)
        direction, so pass `next_cursor` as `before` to

        keep paging towards the newest items. Don't send with `after`.
      schema:
        type: string
    CreatedGte:
      name: created_gte
      in: query
      description: Only items created at or after this time.
      schema:
        type: string
        format: date-time
    CreatedLte:
      name: created_lte
      in: query
      description: Only items created at or before this time.
      schema:
        type: string
        format: date-time
    Query:
      name: q
      in: query
      description: Free-text search.
      schema:
        type: string
        maxLength: 200
    IncludeTotalCount:
      name: include
      in: query
      description: Set to `total_count` to add `total_count` to the list response.
      schema:
        type: string
        enum:
          - total_count
  headers:
    X-Request-Id:
      description: >-
        Our request id (`req_…`), on every response. Always ours, never the
        client's `X-Request-Id`. Quote it to support.
      schema:
        type: string
    X-RateLimit-Limit:
      description: Requests allowed per minute for this key, token or session.
      schema:
        type: integer
    X-RateLimit-Remaining:
      description: Requests left in the current window.
      schema:
        type: integer
    X-RateLimit-Reset:
      description: Unix time when the window resets.
      schema:
        type: integer
    Retry-After:
      description: Seconds to wait before retrying.
      schema:
        type: integer
  schemas:
    ListEnvelope:
      type: object
      description: >-
        Cursor page. Domain files compose this with `allOf` and set
        `data.items`.
      required:
        - object
        - data
        - has_more
      properties:
        object:
          type: string
          const: list
        data:
          type: array
          items: {}
          description: Items, newest first.
        has_more:
          type: boolean
          description: True when another page exists.
        next_cursor:
          type:
            - string
            - 'null'
          description: >-
            Cursor for the next page in the same direction: pass it as `after`
            if this page was read with `after` (or no cursor), as `before` if it
            was read with `before`. Null when `has_more` is false.
        total_count:
          type: integer
          description: Only when `include=total_count` was sent.
    Transaction:
      description: >-
        One movement on the business's accounts, as customers see it
        (Transactions, account Activity). Read-only view of payments,
        collections and other account movements. Carries the ISO 8583 outcome of
        the underlying movement: `09` while
        pending_approval/scheduled/processing, `01` while a status query is
        unresolved, `00` when completed, the bank's NIP code (or ours) when
        failed or rejected.
      allOf:
        - $ref: '#/components/schemas/ResponseOutcome'
        - type: object
          required:
            - id
            - object
            - created_at
            - updated_at
            - type
            - status
            - direction
            - amount
            - account_id
            - account_display_name
            - counterparty
            - description
            - reference
            - source
            - events
            - attachment_document_ids
            - livemode
          properties:
            id:
              type: string
              pattern: ^txn_[0-9A-HJKMNP-TV-Z]{26}$
              description: Transaction id.
            object:
              type: string
              description: Always `transaction`.
              const: transaction
            created_at:
              $ref: '#/components/schemas/Timestamp'
              description: When it was created.
            updated_at:
              $ref: '#/components/schemas/Timestamp'
              description: When it was created.
            type:
              $ref: '#/components/schemas/TransactionType'
              description: >-
                `payout`: money out to another bank (single or part of a batch,
                pay run or agent payment).
            status:
              $ref: '#/components/schemas/MovementStatus'
              description: >-
                Lifecycle (CONVENTIONS §8). UI labels: completed → "Settled",
                processing → "Processing", pending_approval → "Pending
                approval".
            direction:
              type: string
              enum:
                - in
                - out
                - internal
              description: >-
                `in`: money into `account_id`. `out`: money out of `account_id`.
                `internal`: from `account_id` to `counterparty.account_id`, both
                the business's own.
            amount:
              $ref: '#/components/schemas/Money'
              description: >-
                Always positive; sign comes from `direction` and the account
                viewed. A failed payout shows its original amount; its money is
                back ("Failed, money returned").
            fee:
              description: Fee charged on top, if any.
              oneOf:
                - $ref: '#/components/schemas/Money'
                - type: 'null'
            account_id:
              type: string
              pattern: ^acc_[0-9A-HJKMNP-TV-Z]{26}$
              description: Account debited (`out`, `internal`) or credited (`in`).
            account_display_name:
              type: string
              description: '"Naira · Main".'
            counterparty:
              $ref: '#/components/schemas/Counterparty'
              description: The other side of a transaction.
            description:
              type: string
              description: >-
                Main line in lists, e.g. "Kano Freight Services" or "Naira ·
                Main to Naira · Payroll".
            reference:
              type:
                - string
                - 'null'
              description: >-
                Second line and "Narration": invoice number, payment reference
                or note sent with the payment, e.g. "INV-0142", "Diesel supply".
            source:
              $ref: '#/components/schemas/TransactionSource'
              description: Where the transaction came from.
            initiated_by:
              description: '"Created by".'
              oneOf:
                - $ref: '#/components/schemas/Actor'
                - type: 'null'
            approved_by:
              description: Second approver, when the payment needed one.
              oneOf:
                - $ref: '#/components/schemas/Actor'
                - type: 'null'
            category:
              type:
                - string
                - 'null'
              description: Spending or income category, e.g. "Fuel", "Sales".
              maxLength: 40
            note:
              type:
                - string
                - 'null'
              description: '"Note" added by the team.'
              maxLength: 500
            attachment_document_ids:
              type: array
              items:
                type: string
                description: A `doc_…` with purpose `transaction_attachment`.
                pattern: ^doc_[0-9A-HJKMNP-TV-Z]{26}$
              description: '"Receipt or invoice" files attached.'
            failure:
              description: Why it failed, safe to show. Null unless `failed`.
              oneOf:
                - type: object
                  required:
                    - code
                    - message
                  properties:
                    code:
                      type: string
                      description: >-
                        Stable failure code, e.g. `account_name_mismatch`,
                        `receiving_bank_rejected`.
                    message:
                      type: string
                      description: >-
                        "The receiving bank rejected the payment: account number
                        and name don't match. The money is back in Naira ·
                        Main."
                - type: 'null'
            events:
              type: array
              items:
                $ref: '#/components/schemas/TransactionEvent'
              description: Progress timeline, oldest first.
            nip_session_id:
              type:
                - string
                - 'null'
              description: NIP session id (30 digits) for NIP transfers.
              pattern: ^\d{30}$
            completed_at:
              type:
                - string
                - 'null'
              description: When it settled.
              format: date-time
            livemode:
              type: boolean
              description: False for sandbox (TEST) transactions.
    ResponseOutcome:
      type: object
      description: >
        The ISO 8583 outcome of a money movement. Every transfer, batch row,
        inbound credit, pay-run line and agent

        payment includes these two fields (compose with `allOf`). By
        `MovementStatus`:

        `draft`, `pending_approval`, `scheduled`, `processing`: `09`.
        `completed`: `00`. `failed`, `rejected`: the

        bank's NIP code (e.g. `51`, `91`), or ours from the error mapping when
        it never reached the bank.

        `canceled`: `21` (no action taken; no money moved). `reversed`: keeps
        the original `00` (the money did settle);

        the reversal is its own linked movement with its own code. `01` means we
        couldn't confirm the outcome with the

        bank yet; the payment is never retried until it resolves.
      required:
        - response_code
        - response_message
      properties:
        response_code:
          $ref: '#/components/schemas/ResponseCode'
        response_message:
          type: string
          description: >-
            Standard meaning of the code (the `x-codes` text), not customer
            copy. Customer-safe wording is in `status_reason` / `error.message`.
          examples:
            - Approved or completed successfully
    Timestamp:
      type: string
      format: date-time
      description: RFC 3339, UTC.
      examples:
        - '2026-10-27T08:41:00Z'
    TransactionType:
      type: string
      enum:
        - payout
        - collection
        - transfer
        - fee
        - reversal
        - conversion
        - card
      description: >-
        `payout`: money out to another bank (single or part of a batch, pay run
        or agent payment). `collection`: money in (invoice, payment link,
        virtual account or plain transfer). `transfer`: between the business's
        own accounts. `fee`: a standalone fee (payout fees ride on the payout's
        `fee`). `reversal`: a linked reversal of an earlier transaction.
        `conversion` and `card` are after launch.
      x-phase-values:
        conversion: after-launch
        card: after-launch
    MovementStatus:
      type: string
      description: >
        Lifecycle shared by every money-moving resource (CONVENTIONS §8). UI
        labels: completed → "Settled",

        pending_approval → "Pending approval".
      enum:
        - draft
        - pending_approval
        - scheduled
        - processing
        - completed
        - failed
        - rejected
        - canceled
        - reversed
    Money:
      type: object
      description: >-
        An amount in minor units (kobo, cents) with its currency. Never a
        decimal.
      required:
        - amount
        - currency
      properties:
        amount:
          type: integer
          format: int64
          description: Amount in minor units. ₦450,000.00 is 45000000.
          examples:
            - 45000000
        currency:
          $ref: '#/components/schemas/Currency'
    Counterparty:
      type: object
      description: The other side of a transaction.
      required:
        - type
        - name
      properties:
        type:
          type: string
          enum:
            - bank_account
            - own_account
            - batch
            - pay_run
            - fx
            - panoflow
          description: What is on the other side.
        name:
          type: string
          description: >-
            Display name ("Kano Freight Services", "October salaries · 11
            people").
        bank_account:
          description: >-
            For `bank_account`: the other bank account, with the name from name
            enquiry.
          oneOf:
            - $ref: '#/components/schemas/BankAccountRef'
            - type: 'null'
        account_id:
          type:
            - string
            - 'null'
          description: 'For `own_account`: the other PanoFlow account.'
          pattern: ^acc_[0-9A-HJKMNP-TV-Z]{26}$
        recipient_id:
          type:
            - string
            - 'null'
          description: Saved recipient, if any.
          pattern: ^rcp_[0-9A-HJKMNP-TV-Z]{26}$
    TransactionSource:
      type: object
      description: Where the transaction came from.
      required:
        - type
        - id
      properties:
        type:
          type: string
          enum:
            - transfer
            - batch
            - pay_run
            - inbound_credit
            - invoice
            - payment_link
            - static_virtual_account
            - agent_payment
            - fee
            - adjustment
            - conversion
            - card
          description: The domain object that produced the transaction.
        id:
          type: string
          description: >-
            Its id (`trf_…`, `bat_…`, `run_…`, `icr_…`, `ivc_…`, `lnk_…`,
            `sva_…`, `adj_…`, …). Fetch it from its own API for full detail.
    Actor:
      type: object
      description: Who did something. Shown in activity, approvals and the audit log.
      required:
        - type
        - id
        - name
      properties:
        type:
          type: string
          enum:
            - user
            - staff
            - agent
            - api_key
            - system
          description: Kind of actor.
        id:
          type: string
          description: The actor's id (`usr_…`, `stf_…`, `agt_…`, `key_…`, or `system`).
        name:
          type: string
          description: Display name, e.g. "Adaeze Okafor" or "Procurement agent".
    TransactionEvent:
      type: object
      description: One step of "PROGRESS" in the transaction drawer.
      required:
        - type
        - description
        - at
      properties:
        type:
          type: string
          enum:
            - created
            - approved
            - sent
            - received
            - matched
            - completed
            - failed
            - returned
            - reversed
          description: Step in the progress timeline.
        description:
          type: string
          description: >-
            Safe to show, e.g. "Sent to the receiving bank", "Recorded against
            INV-0142", "Rejected, money returned".
        at:
          $ref: '#/components/schemas/Timestamp'
          description: When it was created.
        actor:
          description: Who did it, when a person did.
          oneOf:
            - $ref: '#/components/schemas/Actor'
            - type: 'null'
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - response_code
            - type
            - code
            - message
            - request_id
          properties:
            response_code:
              $ref: '#/components/schemas/ResponseCode'
            type:
              type: string
              enum:
                - invalid_request
                - authentication
                - permission
                - not_found
                - conflict
                - rate_limited
                - partner
                - internal
              description: >-
                Error class, fixed per `code` (errors.yaml `x-codes` `type`;
                CONVENTIONS §7).
            code:
              $ref: '#/components/schemas/ErrorCode'
            message:
              type: string
              description: >-
                Safe to show the customer. Never reveals screening, fraud or STR
                state.
            param:
              type: string
              description: Dot path of the offending field.
            request_id:
              type: string
              description: '`req_…`'
            doc_url:
              type: string
              format: uri
              description: Page explaining the code.
            details:
              type: object
              additionalProperties: true
              description: Machine-readable extra context for the code.
    ResponseCode:
      type: string
      pattern: ^[0-9A-Z]{2}$
      description: >
        Two-character ISO 8583 response code, using the NIBSS NIP meanings
        (CONVENTIONS §7a). `00` is success.

        Carried on every error (mapped from `error.code`, see errors.yaml
        `x-codes`) and on every money-movement

        object (the bank's final code, or our own while it is in flight).
        Clients must tolerate codes added later.
      enum:
        - '00'
        - '01'
        - '03'
        - '05'
        - '06'
        - '07'
        - '08'
        - '09'
        - '12'
        - '13'
        - '14'
        - '15'
        - '16'
        - '17'
        - '21'
        - '25'
        - '26'
        - '30'
        - '34'
        - '35'
        - '51'
        - '57'
        - '58'
        - '61'
        - '63'
        - '65'
        - '68'
        - '91'
        - '92'
        - '94'
        - '96'
        - '97'
      x-codes:
        '12': Invalid transaction
        '13': Invalid amount
        '14': Invalid batch number
        '15': Invalid session or record id
        '16': Unknown bank code
        '17': Invalid channel
        '21': No action taken
        '25': Unable to locate record
        '26': Duplicate record
        '30': Format error
        '34': Suspected fraud
        '35': Contact sending bank
        '51': No sufficient funds
        '57': Transaction not permitted to sender
        '58': Transaction not permitted on channel
        '61': Transfer limit exceeded
        '63': Security violation
        '65': Exceeds withdrawal frequency
        '68': Response received too late
        '91': Beneficiary bank not available
        '92': Routing error
        '94': Duplicate transaction
        '96': System malfunction
        '97': Timeout waiting for response from destination
        '00': Approved or completed successfully
        '01': Status unknown, please wait for settlement report
        '03': Invalid sender
        '05': Do not honour
        '06': Dormant account
        '07': Invalid account
        '08': Account name mismatch
        '09': Request processing in progress
    Currency:
      type: string
      description: >-
        ISO 4217. Only NGN is live at launch; others return 422
        `currency_not_enabled`.
      enum:
        - NGN
        - USD
        - GBP
        - EUR
        - GHS
        - KES
    BankAccountRef:
      type: object
      description: A bank account outside PanoFlow (a recipient or a payer).
      required:
        - account_number
        - bank_code
      properties:
        account_number:
          $ref: '#/components/schemas/NubanAccountNumber'
        bank_code:
          $ref: '#/components/schemas/BankCode'
        bank_name:
          type: string
          description: Display name of the bank.
          examples:
            - GTBank
        account_name:
          type: string
          description: Name on the account as returned by name enquiry.
    ErrorCode:
      type: string
      description: >-
        Stable machine-readable error code. Clients must tolerate codes added
        later.
      enum:
        - invalid_json
        - parameter_missing
        - parameter_invalid
        - parameter_unknown
        - invalid_api_key
        - session_expired
        - invalid_credentials
        - two_factor_required
        - two_factor_invalid
        - token_expired
        - staff_account_not_allowed
        - role_not_permitted
        - scope_missing
        - step_up_required
        - business_frozen
        - business_not_approved
        - account_closed
        - ip_not_allowed
        - agent_paused
        - test_key_on_live
        - email_not_verified
        - two_factor_enrolment_required
        - csrf_token_invalid
        - resource_not_found
        - idempotency_key_reused
        - invalid_state
        - self_approval_not_allowed
        - already_decided
        - duplicate_resource
        - version_conflict
        - insufficient_balance
        - limit_exceeded
        - daily_limit_exceeded
        - approval_threshold_exceeded
        - recipient_not_allowed
        - recipient_name_mismatch
        - name_enquiry_failed
        - account_number_invalid
        - currency_not_enabled
        - currency_mismatch
        - amount_too_small
        - amount_too_large
        - schedule_in_past
        - batch_row_invalid
        - batch_too_large
        - document_rejected
        - checks_incomplete
        - rate_expired
        - payroll_short_funds
        - webhook_url_invalid
        - password_too_weak
        - password_breached
        - scope_not_allowed
        - application_incomplete
        - account_balance_not_zero
        - verification_code_invalid
        - link_expired
        - invite_expired
        - net_pay_negative
        - notification_channel_locked
        - phone_not_verified
        - rate_limited
        - bank_unavailable
        - identity_provider_unavailable
        - registry_unavailable
        - partner_timeout
        - screening_unavailable
        - messaging_unavailable
        - internal_error
      x-codes:
        invalid_json:
          status: 400
          type: invalid_request
          response_code: '30'
          meaning: Body is not valid JSON.
        parameter_missing:
          status: 400
          type: invalid_request
          response_code: '30'
          meaning: A required field is missing; see `param`.
        parameter_invalid:
          status: 400
          type: invalid_request
          response_code: '30'
          meaning: A field has the wrong type or format; see `param`.
        parameter_unknown:
          status: 400
          type: invalid_request
          response_code: '30'
          meaning: An unknown field was sent.
        invalid_api_key:
          status: 401
          type: authentication
          response_code: '63'
          meaning: >-
            Key unknown, revoked, or its business inactive. Never treated as
            anonymous.
        session_expired:
          status: 401
          type: authentication
          response_code: '63'
          meaning: Session timed out or was revoked.
        invalid_credentials:
          status: 401
          type: authentication
          response_code: '63'
          meaning: Email or password wrong (no hint which).
        two_factor_required:
          status: 401
          type: authentication
          response_code: '63'
          meaning: Password accepted; a 2FA code is needed to finish sign-in.
        two_factor_invalid:
          status: 401
          type: authentication
          response_code: '63'
          meaning: 2FA or backup code wrong or already used.
        token_expired:
          status: 401
          type: authentication
          response_code: '63'
          meaning: OAuth access token expired; refresh it.
        role_not_permitted:
          status: 403
          type: permission
          response_code: '57'
          meaning: The caller's role can't do this.
        scope_missing:
          status: 403
          type: permission
          response_code: '57'
          meaning: The key or token lacks the scope in `details.required_scope`.
        step_up_required:
          status: 403
          type: permission
          response_code: '63'
          meaning: Send a fresh X-Step-Up-Token (2FA within 5 minutes).
        business_frozen:
          status: 403
          type: permission
          response_code: '57'
          meaning: The business is frozen; money can't move. Message stays neutral.
        business_not_approved:
          status: 403
          type: permission
          response_code: '57'
          meaning: Onboarding not approved yet; live money movement is closed.
        account_closed:
          status: 403
          type: permission
          response_code: '57'
          meaning: The account is closed.
        ip_not_allowed:
          status: 403
          type: permission
          response_code: '63'
          meaning: Request came from outside the allowed IP ranges.
        agent_paused:
          status: 403
          type: permission
          response_code: '57'
          meaning: The agent is paused by its owner; its tokens are revoked.
        test_key_on_live:
          status: 403
          type: permission
          response_code: '58'
          meaning: A pk_test_ key was used against a live-only operation.
        resource_not_found:
          status: 404
          type: not_found
          response_code: '25'
          meaning: Not found or not visible to this tenant.
        idempotency_key_reused:
          status: 409
          type: conflict
          response_code: '94'
          meaning: Same Idempotency-Key with a different body.
        invalid_state:
          status: 409
          type: conflict
          response_code: '12'
          meaning: >-
            The resource's status doesn't allow this action; see
            `details.status`.
        self_approval_not_allowed:
          status: 409
          type: conflict
          response_code: '57'
          meaning: The requester can't decide their own approval.
        already_decided:
          status: 409
          type: conflict
          response_code: '21'
          meaning: The approval was already approved, rejected or expired.
        duplicate_resource:
          status: 409
          type: conflict
          response_code: '26'
          meaning: An identical resource already exists (e.g. recipient).
        version_conflict:
          status: 409
          type: conflict
          response_code: '21'
          meaning: The resource changed since it was read; re-read and retry.
        insufficient_balance:
          status: 422
          type: invalid_request
          response_code: '51'
          meaning: Available balance can't cover amount + fee; `details.available`.
        limit_exceeded:
          status: 422
          type: invalid_request
          response_code: '61'
          meaning: Over a per-payment limit; `details.limit`.
        daily_limit_exceeded:
          status: 422
          type: invalid_request
          response_code: '61'
          meaning: Over the daily limit.
        approval_threshold_exceeded:
          status: 422
          type: invalid_request
          response_code: '09'
          meaning: >-
            Over the caller's approval threshold; a pending approval was created
            instead (see `details.approval_id`).
        recipient_not_allowed:
          status: 422
          type: invalid_request
          response_code: '57'
          meaning: Agent tried to pay a recipient outside its allowlist.
        recipient_name_mismatch:
          status: 422
          type: invalid_request
          response_code: '08'
          meaning: >-
            Name on the account doesn't match the name given and confirmation
            wasn't sent.
        name_enquiry_failed:
          status: 422
          type: invalid_request
          response_code: '25'
          meaning: The bank couldn't confirm the account name.
        account_number_invalid:
          status: 422
          type: invalid_request
          response_code: '07'
          meaning: Not a valid NUBAN for that bank.
        currency_not_enabled:
          status: 422
          type: invalid_request
          response_code: '12'
          meaning: Currency not live for this business yet.
        currency_mismatch:
          status: 422
          type: invalid_request
          response_code: '12'
          meaning: Amount currency doesn't match the account's currency.
        amount_too_small:
          status: 422
          type: invalid_request
          response_code: '13'
          meaning: Below the minimum for this payment type.
        amount_too_large:
          status: 422
          type: invalid_request
          response_code: '13'
          meaning: Above the maximum for this payment type.
        schedule_in_past:
          status: 422
          type: invalid_request
          response_code: '12'
          meaning: Scheduled date is not in the future.
        batch_row_invalid:
          status: 422
          type: invalid_request
          response_code: '30'
          meaning: One or more batch rows failed validation; `details.rows`.
        batch_too_large:
          status: 422
          type: invalid_request
          response_code: '14'
          meaning: Batch exceeds the row limit for this plan.
        document_rejected:
          status: 422
          type: invalid_request
          response_code: '30'
          meaning: Upload is the wrong type, too large, or unreadable.
        checks_incomplete:
          status: 422
          type: invalid_request
          response_code: '21'
          meaning: Can't approve; not every KYB check has passed.
        rate_expired:
          status: 422
          type: invalid_request
          response_code: '12'
          meaning: FX quote expired; get a new one.
        payroll_short_funds:
          status: 422
          type: invalid_request
          response_code: '51'
          meaning: Payroll account can't cover the run; `details.short_by`.
        webhook_url_invalid:
          status: 422
          type: invalid_request
          response_code: '30'
          meaning: Webhook URL must be https and publicly reachable.
        password_too_weak:
          status: 422
          type: invalid_request
          response_code: '30'
          meaning: Password fails the strength rules.
        password_breached:
          status: 422
          type: invalid_request
          response_code: '30'
          meaning: Password appears in a known breach.
        email_not_verified:
          status: 403
          type: permission
          response_code: '63'
          meaning: Verify the email address before continuing.
        two_factor_enrolment_required:
          status: 403
          type: permission
          response_code: '63'
          meaning: Set up two-factor authentication before doing anything else.
        staff_account_not_allowed:
          status: 401
          type: authentication
          response_code: '63'
          meaning: >-
            Sign-in with this Google account is not allowed. Not returned to
            customers.
        scope_not_allowed:
          status: 422
          type: invalid_request
          response_code: '57'
          meaning: None of the requested OAuth scopes can be granted to this client.
        application_incomplete:
          status: 422
          type: invalid_request
          response_code: '21'
          meaning: >-
            The onboarding application is missing required details; see
            `details.issues`.
        account_balance_not_zero:
          status: 422
          type: invalid_request
          response_code: '12'
          meaning: Move the balance out before closing the account.
        verification_code_invalid:
          status: 422
          type: invalid_request
          response_code: '63'
          meaning: The verification code is wrong or expired.
        link_expired:
          status: 422
          type: invalid_request
          response_code: '63'
          meaning: The link has expired; request a new one.
        invite_expired:
          status: 422
          type: invalid_request
          response_code: '15'
          meaning: The invite has expired; ask for a new one.
        net_pay_negative:
          status: 422
          type: invalid_request
          response_code: '13'
          meaning: >-
            Deductions exceed gross pay for this person; see
            `details.person_id`.
        notification_channel_locked:
          status: 422
          type: invalid_request
          response_code: '57'
          meaning: >-
            This notification channel can't be turned off (e.g. email for
            `compliance_request`); see `param`.
        phone_not_verified:
          status: 422
          type: invalid_request
          response_code: '63'
          meaning: >-
            Needs a verified phone number first (SMS notifications, SMS recovery
            codes).
        csrf_token_invalid:
          status: 403
          type: permission
          response_code: '63'
          meaning: >-
            A state-changing request made with a session cookie had a missing or
            wrong `X-CSRF-Token`, or came from another origin.
        rate_limited:
          status: 429
          type: rate_limited
          response_code: '65'
          meaning: Too many requests; wait `Retry-After` seconds.
        bank_unavailable:
          status: 503
          type: partner
          response_code: '91'
          meaning: >-
            Partner bank is down or degraded. Retry with the same
            Idempotency-Key.
        identity_provider_unavailable:
          status: 503
          type: partner
          response_code: '96'
          meaning: Identity checks provider unavailable.
        registry_unavailable:
          status: 503
          type: partner
          response_code: '96'
          meaning: >-
            CAC registry unavailable; onboarding continues as "pending
            registry".
        partner_timeout:
          status: 502
          type: partner
          response_code: '97'
          meaning: >-
            A partner (the bank, or the SMS provider) didn't answer in time. For
            payments we confirm the status with the bank before any retry.
        screening_unavailable:
          status: 503
          type: partner
          response_code: '96'
          meaning: >-
            Sanctions and PEP screening is unavailable; nothing was changed.
            Retry with the same Idempotency-Key.
        messaging_unavailable:
          status: 503
          type: partner
          response_code: '96'
          meaning: We couldn't send the email or text message right now. Retry shortly.
        internal_error:
          status: 500
          type: internal
          response_code: '96'
          meaning: Our fault. Safe to retry with the same Idempotency-Key.
    NubanAccountNumber:
      type: string
      description: 10-digit Nigerian NUBAN account number.
      pattern: ^\d{10}$
      examples:
        - '8012457739'
    BankCode:
      type: string
      description: The bank's NIBSS NIP institution code (CBN bank code).
      examples:
        - '058'
  responses:
    BadRequest:
      description: Malformed request.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
        X-RateLimit-Limit:
          $ref: '#/components/headers/X-RateLimit-Limit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/X-RateLimit-Remaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/X-RateLimit-Reset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Missing, invalid or expired credentials.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
        X-RateLimit-Limit:
          $ref: '#/components/headers/X-RateLimit-Limit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/X-RateLimit-Remaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/X-RateLimit-Reset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: Authenticated but not allowed (role, scope, step-up, frozen business).
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
        X-RateLimit-Limit:
          $ref: '#/components/headers/X-RateLimit-Limit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/X-RateLimit-Remaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/X-RateLimit-Reset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: Too many requests.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
        X-RateLimit-Limit:
          $ref: '#/components/headers/X-RateLimit-Limit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/X-RateLimit-Remaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/X-RateLimit-Reset'
        Retry-After:
          $ref: '#/components/headers/Retry-After'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    InternalError:
      description: >-
        Something went wrong on our side (`internal_error`). Safe to retry with
        the same Idempotency-Key.
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
        X-RateLimit-Limit:
          $ref: '#/components/headers/X-RateLimit-Limit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/X-RateLimit-Remaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/X-RateLimit-Reset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer
      bearerFormat: pk_live_… / pk_test_…
      description: Server API key, scoped. `pk_test_` keys hit the sandbox tenant.
    OAuth2:
      type: oauth2
      description: >-
        OAuth 2.1 with PKCE. Used by agents (MCP) and third-party apps. Access
        tokens live 15 minutes.
      flows:
        authorizationCode:
          authorizationUrl: https://app.panoflow.io/oauth/authorize
          tokenUrl: https://api.panoflow.io/v1/oauth/token
          refreshUrl: https://api.panoflow.io/v1/oauth/token
          scopes:
            accounts:read: Read balances, accounts and transactions
            transfers:read: Read payments
            transfers:write: Create payments (guardrails-checked for agents)
            recipients:read: Read recipients
            recipients:write: Create recipients (allowlist-checked for agents)
            collections:read: Read invoices, payment links and virtual accounts
            collections:write: Create invoices and payment links

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.