{
  "openapi": "3.0.3",
  "info": {
    "title": "BOB API",
    "version": "1.0.0",
    "description": "The BOB client API: org-pinned bearer tokens (`bok_org_…`) over your organization's financial data, with propose-tier writes — a write can only ever PROPOSE; approval and execution stay with humans in BOB.\n\n## Authentication\nSend your org API token as a bearer credential:\n\n```\nAuthorization: Bearer bok_org_…\n```\n\nTokens are org-scoped and least-privilege — each carries only the scopes it was minted with (e.g. `account:read`, `invoices:read`, `payments:read`, `invoices:propose`, `event_feed:read`, `webhook_endpoints:manage`). A token belongs to the organization (not the person who minted it) and stays valid until it is explicitly revoked or, if an expiry was chosen at mint, until it expires.\n\n## Verifying webhook signatures\nEvery delivery is signed in the `X-BoB-Signature` header as `t=\u003cunix-ts\u003e,v1=\u003chex\u003e`. To verify:\n\n1. Reject if `t` is more than 5 minutes old (replay protection).\n2. Compute `HMAC-SHA256(secret, \"\u003ct\u003e.\u003craw-request-body\u003e\")` and hex-encode it.\n3. Constant-time compare against `v1`.\n\nDuring a secret rotation the header carries TWO `v1=` values (the retiring and the new secret, ~24h overlap) — accept the delivery if EITHER matches, so you can roll secrets with zero downtime.\n\n## Event catalog\nEvents are delivered by webhook and are also queryable at `GET /events` (90-day retention) — the same envelope on both, so dedupe by `id`.\n\nEvery event arrives in this envelope; `data` is a thin id + minimal-state object (fetch full detail back through the authenticated read API):\n\n```json\n{\n  \"id\": \"evt_2aQ9xL\",\n  \"type\": \"invoice.paid\",\n  \"version\": 1,\n  \"occurred_at\": \"2026-01-15T09:30:00Z\",\n  \"data\": { \"invoice_id\": \"inv_8fq2Lm\", \"number\": \"INV-1042\", \"status\": \"paid\", \"total_cents\": 150000, \"currency\": \"USD\" }\n}\n```\n\nCurrent event types and the `data` each carries:\n\n- `invoice.overdue` — A sent invoice passed its due date without being paid.\n  ```json\n  {\"invoice_id\":\"inv_8fq2Lm\",\"number\":\"INV-1042\",\"status\":\"overdue\",\"total_cents\":150000,\"currency\":\"USD\"}\n  ```\n- `invoice.paid` — An invoice was paid in full (terminal — after any provisional clearing window).\n  ```json\n  {\"invoice_id\":\"inv_8fq2Lm\",\"number\":\"INV-1042\",\"status\":\"paid\",\"total_cents\":150000,\"currency\":\"USD\"}\n  ```\n- `invoice.sent` — An invoice was sent to its recipient.\n  ```json\n  {\"invoice_id\":\"inv_8fq2Lm\",\"number\":\"INV-1042\",\"status\":\"sent\",\"total_cents\":150000,\"currency\":\"USD\"}\n  ```\n- `invoice.voided` — An invoice was voided and is no longer payable.\n  ```json\n  {\"invoice_id\":\"inv_8fq2Lm\",\"number\":\"INV-1042\",\"status\":\"voided\",\"total_cents\":150000,\"currency\":\"USD\"}\n  ```\n- `payment.canceled` — A payment was canceled and will not proceed.\n  ```json\n  {\"payment_intent_id\":\"pi_3Nk2Ht7Qz\",\"amount_cents\":42000,\"currency\":\"USD\"}\n  ```\n- `payment.completed` — A payment completed (settled).\n  ```json\n  {\"payment_intent_id\":\"pi_3Nk2Ht7Qz\",\"amount_cents\":42000,\"currency\":\"USD\"}\n  ```\n- `payment.failed` — A payment failed and did not move money.\n  ```json\n  {\"payment_intent_id\":\"pi_3Nk2Ht7Qz\",\"amount_cents\":42000,\"currency\":\"USD\"}\n  ```\n- `payment.held` — A payment is held awaiting approval or step-up before it can proceed.\n  ```json\n  {\"payment_intent_id\":\"pi_3Nk2Ht7Qz\",\"amount_cents\":42000,\"currency\":\"USD\"}\n  ```\n- `test.event` — A test event sent on demand to verify an endpoint's delivery and signature handling.\n  ```json\n  {\"message\":\"This is a test event.\"}\n  ```\n"
  },
  "servers": [
    {
      "url": "https://api.bankofbots.ai",
      "description": "BOB API"
    }
  ],
  "paths": {
    "/api/ext/v1/account": {
      "get": {
        "operationId": "get_api_ext_v1_account",
        "summary": "Get account status and balances",
        "tags": [
          "Account"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Account status and live balances for your organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Account state temporarily unavailable (live read failed) — retry later; never returned as a zero balance or a fabricated status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/ext/v1/events": {
      "get": {
        "operationId": "get_api_ext_v1_events",
        "summary": "List events",
        "tags": [
          "Events"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "types",
            "in": "query",
            "description": "Comma-separated public event types to include (default: all).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size (default 50, max 100).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "description": "Event id cursor — returns events older than it.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Your organization's events, newest first, queryable for 90 days — the same envelopes webhooks deliver (dedupe by id).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventList"
                }
              }
            }
          },
          "400": {
            "description": "Unknown event type, bad limit, or unknown starting_after id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/ext/v1/events/{eventId}": {
      "get": {
        "operationId": "get_api_ext_v1_events_by_eventid",
        "summary": "Get an event",
        "tags": [
          "Events"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "eventId",
            "in": "path",
            "description": "The event's id (from a webhook delivery or the events list).",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The event envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such event in your organization's 90-day window",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/ext/v1/insights/cash-position": {
      "get": {
        "operationId": "get_api_ext_v1_insights_cash_position",
        "summary": "Get cash position",
        "tags": [
          "Insights"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Cash on hand, estimated monthly burn, and runway — BOB's computed numbers for your organization's account. Amounts in integer cents. Runway appears only when there is enough history to estimate honestly (`sufficient`); status is `not_open` when no BOB account exists yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CashPositionResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Failed to compute the figures — retry later",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Figures temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/ext/v1/invoices": {
      "get": {
        "operationId": "get_api_ext_v1_invoices",
        "summary": "List invoices",
        "tags": [
          "Invoices"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "description": "Filter by status (draft, sent, paid, overdue, voided). Default: all.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size (default 50, max 200).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Your organization's invoices, newest first. Amounts are in integer cents; bearer share links and payment-processor identifiers are never included.",
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/PublicInvoice"
                  },
                  "type": "array"
                }
              }
            }
          },
          "400": {
            "description": "Invalid limit (must be 1..200)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "post_api_ext_v1_invoices",
        "summary": "Propose an invoice",
        "tags": [
          "Invoices"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateInvoiceRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The drafted invoice. PROPOSE-ONLY: it lands as a draft under your organization and is never sent or charged by this call — a person or BOB sends it. Amounts in integer cents.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicInvoice"
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, due_date, or a non-positive line item amount",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Your organization has no active owner to attribute the invoice to",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Unsupported currency (USD only)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/ext/v1/invoices/{invoiceId}": {
      "get": {
        "operationId": "get_api_ext_v1_invoices_by_invoiceid",
        "summary": "Get an invoice",
        "tags": [
          "Invoices"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "description": "The invoice's id.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The invoice. Amounts in integer cents; no share link or processor ids.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicInvoice"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such invoice in your organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/ext/v1/invoices/{invoiceId}/pdf": {
      "get": {
        "operationId": "get_api_ext_v1_invoices_by_invoiceid_pdf",
        "summary": "Download an invoice PDF",
        "tags": [
          "Invoices"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "invoiceId",
            "in": "path",
            "description": "The invoice's id.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The rendered invoice document (USD only). Generated on demand and streamed — never stored.",
            "content": {
              "application/pdf": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such invoice in your organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "The invoice cannot be rendered as a PDF (non-USD, or content withheld)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/ext/v1/payment-intents/{intentId}": {
      "get": {
        "operationId": "get_api_ext_v1_payment_intents_by_intentid",
        "summary": "Get a payment intent",
        "tags": [
          "Payments"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "intentId",
            "in": "path",
            "description": "The payment intent's id (from a payment.* event).",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The payment intent's status and context. Curated: the destination account/wallet, internal ids, rail, and fee cap are never included; amount is in integer cents.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicPaymentIntent"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such payment intent in your organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/ext/v1/ping": {
      "get": {
        "operationId": "get_api_ext_v1_ping",
        "summary": "Verify your API token",
        "tags": [
          "Health"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Token is valid; echoes the organization it is pinned to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PingResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/ext/v1/receivables/aging": {
      "get": {
        "operationId": "get_api_ext_v1_receivables_aging",
        "summary": "Get receivables aging",
        "tags": [
          "Insights"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Who owes your organization money and how overdue: totals, day buckets, and the top overdue customers. Covers ALL receivables — invoices issued through BOB and any imported from connected accounting systems — USD-normalized, in integer cents.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReceivablesAgingResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Failed to compute the figures — retry later",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Figures temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/ext/v1/transactions": {
      "get": {
        "operationId": "get_api_ext_v1_transactions",
        "summary": "List transactions",
        "tags": [
          "Transactions"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "direction",
            "in": "query",
            "description": "Filter by 'inflow' or 'outflow'. Default: both.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Only transactions on or after this date (YYYY-MM-DD).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Only transactions on or before this date (YYYY-MM-DD).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "keyword",
            "in": "query",
            "description": "Free-text match on counterparty name, memo, or reference.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size (default 100, max 200).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Keyset cursor from a previous page's next_cursor. Omit for the first page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Your organization's money ledger, newest first, keyset-paginated. Amounts are integer cents, each paired with a *_known flag (render '—' when false, not $0); internal ids are never included. The summary totals the full filtered set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicTransactionList"
                }
              }
            }
          },
          "400": {
            "description": "Invalid direction, date, limit, or cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/ext/v1/transactions/{transactionId}": {
      "get": {
        "operationId": "get_api_ext_v1_transactions_by_transactionid",
        "summary": "Get a transaction",
        "tags": [
          "Transactions"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "transactionId",
            "in": "path",
            "description": "The transaction's id (from the transactions list).",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The transaction. Amounts in integer cents; internal ids are never included.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicTransaction"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such transaction in your organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/ext/v1/webhooks": {
      "get": {
        "operationId": "get_api_ext_v1_webhooks",
        "summary": "List webhook endpoints",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Your organization's webhook endpoints — metadata only; signing secrets are shown once at create/rotate and never re-listed.",
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/WebhookEndpoint"
                  },
                  "type": "array"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "post_api_ext_v1_webhooks",
        "summary": "Create a webhook endpoint",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointCreateRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The endpoint plus its ONE-TIME signing secret (bok_whsec_…) — capture it now; it is never shown again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointCreatedResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid URL (must be HTTPS and publicly routable) or unknown event type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Endpoint limit reached for your organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/ext/v1/webhooks/{webhookId}": {
      "delete": {
        "operationId": "delete_api_ext_v1_webhooks_by_webhookid",
        "summary": "Delete a webhook endpoint",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "webhookId",
            "in": "path",
            "description": "The webhook endpoint's id.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted. In-flight deliveries to it are abandoned."
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such endpoint in your organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "get_api_ext_v1_webhooks_by_webhookid",
        "summary": "Get a webhook endpoint",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "webhookId",
            "in": "path",
            "description": "The webhook endpoint's id.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoint (no secret).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such endpoint in your organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "patch_api_ext_v1_webhooks_by_webhookid",
        "summary": "Update a webhook endpoint",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "webhookId",
            "in": "path",
            "description": "The webhook endpoint's id.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated endpoint. Setting status active on an auto_disabled endpoint re-enables it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            }
          },
          "400": {
            "description": "Invalid URL, unknown event type, or a status other than active/paused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such endpoint in your organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/ext/v1/webhooks/{webhookId}/deliveries": {
      "get": {
        "operationId": "get_api_ext_v1_webhooks_by_webhookid_deliveries",
        "summary": "List an endpoint's deliveries",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "webhookId",
            "in": "path",
            "description": "The webhook endpoint's id.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Recent delivery attempts, newest first: status (pending/delivered/failed), attempt count, last response code, next retry time while pending.",
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/WebhookDelivery"
                  },
                  "type": "array"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such endpoint in your organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/ext/v1/webhooks/{webhookId}/rotate-secret": {
      "post": {
        "operationId": "post_api_ext_v1_webhooks_by_webhookid_rotate_secret",
        "summary": "Rotate the signing secret",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "webhookId",
            "in": "path",
            "description": "The webhook endpoint's id.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The NEW one-time secret. The previous secret keeps co-signing deliveries for 24 hours (a second v1 entry in X-BoB-Signature) so your fleet can roll.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSecretRotatedResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such endpoint in your organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/ext/v1/webhooks/{webhookId}/verify": {
      "post": {
        "operationId": "post_api_ext_v1_webhooks_by_webhookid_verify",
        "summary": "Send a test event",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "webhookId",
            "in": "path",
            "description": "The webhook endpoint's id.",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Test event queued for THIS endpoint only. It arrives as a signed test.event delivery; check your receiver or the deliveries listing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookVerifyResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope or credential class",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such endpoint in your organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AccountBalance": {
        "properties": {
          "available_cents": {
            "description": "Funds available to spend now, in cents.",
            "example": 1250000,
            "format": "int64",
            "type": "integer"
          },
          "holding_cents": {
            "description": "Funds held (e.g. awaiting release), in cents.",
            "example": 0,
            "format": "int64",
            "type": "integer"
          },
          "locked_cents": {
            "description": "Funds locked against obligations, in cents.",
            "example": 0,
            "format": "int64",
            "type": "integer"
          },
          "pending_cents": {
            "description": "Funds pending clearing, in cents.",
            "example": 50000,
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "available_cents",
          "holding_cents",
          "locked_cents",
          "pending_cents"
        ],
        "type": "object"
      },
      "AccountResponse": {
        "properties": {
          "balance": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AccountBalance"
              }
            ],
            "description": "Live balance split in cents. Null when status is not_open.",
            "nullable": true
          },
          "org_id": {
            "description": "Your organization id.",
            "example": "org_9f3k2p",
            "type": "string"
          },
          "status": {
            "description": "\"open\" (managed account exists; balance populated) or \"not_open\" (no account for this org; balance null).",
            "example": "open",
            "type": "string"
          },
          "synced_at": {
            "description": "When the balance was last read from the provider.",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          }
        },
        "required": [
          "org_id",
          "status"
        ],
        "type": "object"
      },
      "AgingBuckets": {
        "properties": {
          "current_cents": {
            "description": "Not yet due.",
            "example": 627000,
            "format": "int64",
            "type": "integer"
          },
          "days_1_30_cents": {
            "description": "1-30 days past due.",
            "example": 120000,
            "format": "int64",
            "type": "integer"
          },
          "days_31_60_cents": {
            "description": "31-60 days past due.",
            "example": 60000,
            "format": "int64",
            "type": "integer"
          },
          "days_61_90_cents": {
            "description": "61-90 days past due.",
            "example": 35000,
            "format": "int64",
            "type": "integer"
          },
          "days_90_plus_cents": {
            "description": "More than 90 days past due.",
            "example": 0,
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "current_cents",
          "days_1_30_cents",
          "days_31_60_cents",
          "days_61_90_cents",
          "days_90_plus_cents"
        ],
        "type": "object"
      },
      "CashPositionResponse": {
        "properties": {
          "as_of": {
            "description": "When these figures were computed (RFC 3339).",
            "example": "2026-01-15T09:30:00Z",
            "format": "date-time",
            "type": "string"
          },
          "cash_cents": {
            "description": "Cash on hand, in integer cents (USD).",
            "example": 1250000,
            "format": "int64",
            "type": "integer"
          },
          "is_burning": {
            "description": "Whether net cash flow is negative over the estimation window.",
            "example": true,
            "type": "boolean"
          },
          "monthly_burn_cents": {
            "description": "Estimated monthly net burn in cents; 0 when not burning.",
            "example": 300000,
            "format": "int64",
            "type": "integer"
          },
          "org_id": {
            "description": "Your organization id.",
            "example": "org_9f3k2p",
            "type": "string"
          },
          "runway_cash_cents": {
            "description": "Conservative cash numerator used for runway math, in integer cents (USD); null when no source-aligned history and cash basis exists.",
            "example": 1000000,
            "format": "int64",
            "nullable": true,
            "type": "integer"
          },
          "runway_months": {
            "description": "Estimated months of runway; present only when burning and the estimate is sufficient.",
            "example": 4.2,
            "nullable": true,
            "type": "number"
          },
          "runway_note": {
            "description": "Human-readable runway phrase, or why no estimate is given.",
            "example": "About 4 months of runway at the current burn.",
            "type": "string"
          },
          "status": {
            "description": "Account status backing these figures: open or not_open.",
            "example": "open",
            "type": "string"
          },
          "sufficient": {
            "description": "Whether there was enough history and a cash reading to estimate honestly.",
            "example": true,
            "type": "boolean"
          }
        },
        "required": [
          "as_of",
          "cash_cents",
          "is_burning",
          "monthly_burn_cents",
          "org_id",
          "status",
          "sufficient"
        ],
        "type": "object"
      },
      "CreateInvoiceLineItem": {
        "properties": {
          "description": {
            "description": "Line item description.",
            "example": "Brand identity design",
            "type": "string"
          },
          "quantity": {
            "description": "Quantity (decimal string, e.g. \"2\" or \"1.5\").",
            "example": "1",
            "type": "string"
          },
          "unit_price_cents": {
            "description": "Unit price, in cents.",
            "example": 150000,
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "description",
          "quantity",
          "unit_price_cents"
        ],
        "type": "object"
      },
      "CreateInvoiceRequest": {
        "properties": {
          "currency": {
            "description": "ISO currency code (USD only).",
            "example": "USD",
            "type": "string"
          },
          "description": {
            "description": "Invoice description / memo.",
            "example": "Design services — March 2026",
            "type": "string"
          },
          "due_date": {
            "description": "Due date (YYYY-MM-DD). Optional.",
            "example": "2026-02-14",
            "nullable": true,
            "type": "string"
          },
          "line_items": {
            "description": "One or more line items.",
            "items": {
              "$ref": "#/components/schemas/CreateInvoiceLineItem"
            },
            "type": "array"
          },
          "notes": {
            "description": "Optional internal notes.",
            "example": "Net 30.",
            "type": "string"
          },
          "recipient_email": {
            "description": "Recipient email address.",
            "example": "ap@acmecorp.com",
            "type": "string"
          },
          "recipient_name": {
            "description": "Who to bill.",
            "example": "Acme Corp",
            "type": "string"
          }
        },
        "required": [
          "currency",
          "description",
          "line_items",
          "notes",
          "recipient_email",
          "recipient_name"
        ],
        "type": "object"
      },
      "ErrorResponse": {
        "properties": {
          "code": {
            "description": "A stable, machine-readable error code when the caller can take a specific recovery action. Omitted for errors without a coded branch.",
            "type": "string"
          },
          "error": {
            "description": "A short, human-readable error message.",
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "type": "object"
      },
      "EventList": {
        "properties": {
          "data": {
            "description": "The page of events, newest first.",
            "items": {
              "$ref": "#/components/schemas/PublicEnvelope"
            },
            "type": "array"
          },
          "has_more": {
            "description": "Whether another page exists; page with starting_after=\u003clast id\u003e.",
            "example": false,
            "type": "boolean"
          }
        },
        "required": [
          "data",
          "has_more"
        ],
        "type": "object"
      },
      "OverdueEntry": {
        "properties": {
          "balance_cents": {
            "description": "Their overdue balance, in cents.",
            "example": 150000,
            "format": "int64",
            "type": "integer"
          },
          "customer_name": {
            "description": "The customer's display name.",
            "example": "Acme Corp",
            "type": "string"
          },
          "days_late": {
            "description": "Days past due of their oldest overdue invoice.",
            "example": 12,
            "type": "integer"
          }
        },
        "required": [
          "balance_cents",
          "customer_name",
          "days_late"
        ],
        "type": "object"
      },
      "PingResponse": {
        "properties": {
          "org_id": {
            "description": "The organization the token is pinned to.",
            "example": "org_9f3k2p",
            "type": "string"
          },
          "status": {
            "description": "Always \"ok\" when the token is valid.",
            "example": "ok",
            "type": "string"
          }
        },
        "required": [
          "org_id",
          "status"
        ],
        "type": "object"
      },
      "PublicEnvelope": {
        "properties": {
          "data": {
            "description": "Thin id + minimal-state payload for the event. Shape varies per event type — see the event catalog.",
            "example": {
              "currency": "USD",
              "invoice_id": "inv_8fq2Lm",
              "number": "INV-1042",
              "status": "paid",
              "total_cents": 150000
            }
          },
          "id": {
            "description": "Unique event id — dedupe on this across webhook + API delivery.",
            "example": "evt_2aQ9xL",
            "type": "string"
          },
          "occurred_at": {
            "description": "When the event occurred (RFC 3339).",
            "example": "2026-01-15T09:30:00Z",
            "format": "date-time",
            "type": "string"
          },
          "type": {
            "description": "The public event type, e.g. invoice.paid. See the event catalog for the full list.",
            "example": "invoice.paid",
            "type": "string"
          },
          "version": {
            "description": "Payload projection version.",
            "example": 1,
            "type": "integer"
          }
        },
        "required": [
          "data",
          "id",
          "occurred_at",
          "type",
          "version"
        ],
        "type": "object"
      },
      "PublicInvoice": {
        "properties": {
          "created_at": {
            "description": "When the invoice was created.",
            "format": "date-time",
            "type": "string"
          },
          "currency": {
            "description": "ISO currency code (USD).",
            "example": "USD",
            "type": "string"
          },
          "description": {
            "description": "Free-text description / memo.",
            "example": "Design services — March 2026",
            "type": "string"
          },
          "due_date": {
            "description": "When payment is due. Null if unset.",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "id": {
            "description": "The invoice id.",
            "example": "inv_8fq2Lm",
            "type": "string"
          },
          "number": {
            "description": "Human-readable invoice number (e.g. INV-1042).",
            "example": "INV-1042",
            "type": "string"
          },
          "paid_at": {
            "description": "When the invoice was paid in full. Null if unpaid.",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "recipient_email": {
            "description": "Recipient email address.",
            "example": "ap@acmecorp.com",
            "type": "string"
          },
          "recipient_name": {
            "description": "Who the invoice is billed to.",
            "example": "Acme Corp",
            "type": "string"
          },
          "sent_at": {
            "description": "When the invoice was sent. Null if still a draft.",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "status": {
            "description": "draft, sent, paid, overdue, or voided.",
            "example": "paid",
            "type": "string"
          },
          "subtotal_cents": {
            "description": "Line-item subtotal, in cents.",
            "example": 150000,
            "format": "int64",
            "type": "integer"
          },
          "tax_cents": {
            "description": "Tax amount, in cents.",
            "example": 0,
            "format": "int64",
            "type": "integer"
          },
          "total_cents": {
            "description": "Total due, in cents.",
            "example": 150000,
            "format": "int64",
            "type": "integer"
          },
          "updated_at": {
            "description": "When the invoice was last updated.",
            "format": "date-time",
            "type": "string"
          },
          "voided_at": {
            "description": "When the invoice was voided. Null if not voided.",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          }
        },
        "required": [
          "created_at",
          "currency",
          "description",
          "id",
          "number",
          "recipient_email",
          "recipient_name",
          "status",
          "subtotal_cents",
          "tax_cents",
          "total_cents",
          "updated_at"
        ],
        "type": "object"
      },
      "PublicPaymentIntent": {
        "properties": {
          "amount_cents": {
            "description": "Amount, in cents.",
            "example": 42000,
            "format": "int64",
            "type": "integer"
          },
          "created_at": {
            "description": "When the intent was created.",
            "format": "date-time",
            "type": "string"
          },
          "currency": {
            "description": "ISO currency code.",
            "example": "USD",
            "type": "string"
          },
          "destination_type": {
            "description": "How the payee is referenced (never the destination itself).",
            "example": "counterparty_us_bank_account",
            "type": "string"
          },
          "id": {
            "description": "The payment intent id.",
            "example": "pi_3Nk2Ht7Qz",
            "type": "string"
          },
          "latest_settlement_by": {
            "description": "Latest expected settlement time. Null if not applicable.",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "status": {
            "description": "completed, failed, held, canceled, or in-flight.",
            "example": "completed",
            "type": "string"
          },
          "updated_at": {
            "description": "When the intent was last updated.",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "amount_cents",
          "created_at",
          "currency",
          "destination_type",
          "id",
          "status",
          "updated_at"
        ],
        "type": "object"
      },
      "PublicTransaction": {
        "properties": {
          "amount_cents": {
            "description": "Amount in the transaction's native currency, integer cents, unsigned magnitude.",
            "example": 150000,
            "format": "int64",
            "type": "integer"
          },
          "amount_known": {
            "description": "When false, amount_cents is a placeholder (0) to render as '—', not money.",
            "example": true,
            "type": "boolean"
          },
          "amount_usd_cents": {
            "description": "Amount converted to USD, integer cents.",
            "example": 150000,
            "format": "int64",
            "type": "integer"
          },
          "counterparty_name": {
            "description": "Resolved counterparty name, or '(unknown)'.",
            "example": "Acme Corp",
            "type": "string"
          },
          "currency": {
            "description": "ISO currency code of amount_cents.",
            "example": "USD",
            "type": "string"
          },
          "date": {
            "description": "Value date (YYYY-MM-DD).",
            "example": "2026-02-14",
            "type": "string"
          },
          "direction": {
            "description": "'inflow' (money in) or 'outflow' (money out) — carries the sign.",
            "example": "outflow",
            "type": "string"
          },
          "has_receipt": {
            "description": "True when this payment is matched to an invoice or bill.",
            "example": true,
            "type": "boolean"
          },
          "id": {
            "description": "The transaction id.",
            "example": "a1b2c3d4-0000-4000-8000-000000000001",
            "type": "string"
          },
          "logo_url": {
            "description": "Counterparty logo URL, if known.",
            "example": "https://logo.example.com/acme.png",
            "type": "string"
          },
          "memo": {
            "description": "Free-text memo, if any.",
            "example": "February retainer",
            "type": "string"
          },
          "method": {
            "description": "Payment method, if known.",
            "example": "ach",
            "type": "string"
          },
          "reference": {
            "description": "External reference, if any.",
            "example": "INV-1042",
            "type": "string"
          },
          "source": {
            "description": "Money feed: 'bob' (BOB-native) or 'plaid' (linked bank).",
            "example": "bob",
            "type": "string"
          },
          "source_label": {
            "description": "Human-readable feed label ('BOB' or 'Plaid').",
            "example": "BOB",
            "type": "string"
          },
          "usd_known": {
            "description": "When false, amount_usd_cents is a placeholder and is excluded from USD totals.",
            "example": true,
            "type": "boolean"
          }
        },
        "required": [
          "amount_cents",
          "amount_known",
          "amount_usd_cents",
          "counterparty_name",
          "currency",
          "date",
          "direction",
          "has_receipt",
          "id",
          "source",
          "source_label",
          "usd_known"
        ],
        "type": "object"
      },
      "PublicTransactionList": {
        "properties": {
          "data": {
            "description": "One page of transactions, newest first.",
            "items": {
              "$ref": "#/components/schemas/PublicTransaction"
            },
            "type": "array"
          },
          "next_cursor": {
            "description": "Opaque cursor for the next page (keyset). Empty when this is the last page.",
            "example": "2026-02-14/a1b2c3d4-0000-4000-8000-000000000001",
            "type": "string"
          },
          "summary": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PublicTransactionsSummary"
              }
            ],
            "description": "Totals over the full filtered set (not just this page)."
          }
        },
        "required": [
          "data",
          "summary"
        ],
        "type": "object"
      },
      "PublicTransactionsSummary": {
        "properties": {
          "count": {
            "description": "Number of transactions in the filtered set.",
            "example": 42,
            "format": "int64",
            "type": "integer"
          },
          "inflow_usd_cents": {
            "description": "Total money in over the filtered set, USD cents.",
            "example": 500000,
            "format": "int64",
            "type": "integer"
          },
          "net_usd_cents": {
            "description": "inflow − outflow, USD cents (may be negative).",
            "example": 180000,
            "format": "int64",
            "type": "integer"
          },
          "outflow_usd_cents": {
            "description": "Total money out over the filtered set, USD cents.",
            "example": 320000,
            "format": "int64",
            "type": "integer"
          },
          "unconverted_count": {
            "description": "How many rows had no known USD value (excluded from the totals).",
            "example": 0,
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "count",
          "inflow_usd_cents",
          "net_usd_cents",
          "outflow_usd_cents",
          "unconverted_count"
        ],
        "type": "object"
      },
      "ReceivablesAgingResponse": {
        "properties": {
          "as_of": {
            "description": "When these figures were computed (RFC 3339).",
            "example": "2026-01-15T09:30:00Z",
            "format": "date-time",
            "type": "string"
          },
          "buckets": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AgingBuckets"
              }
            ],
            "description": "Open receivables bucketed by days past due."
          },
          "org_id": {
            "description": "Your organization id.",
            "example": "org_9f3k2p",
            "type": "string"
          },
          "top_overdue": {
            "description": "Up to five customers with the largest overdue balances.",
            "items": {
              "$ref": "#/components/schemas/OverdueEntry"
            },
            "type": "array"
          },
          "total_open_cents": {
            "description": "All open receivables, in integer cents (USD-normalized).",
            "example": 842000,
            "format": "int64",
            "type": "integer"
          },
          "total_overdue_cents": {
            "description": "The past-due portion of open receivables, in cents.",
            "example": 215000,
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "as_of",
          "buckets",
          "org_id",
          "top_overdue",
          "total_open_cents",
          "total_overdue_cents"
        ],
        "type": "object"
      },
      "WebhookDelivery": {
        "properties": {
          "attempts": {
            "description": "Number of delivery attempts so far.",
            "example": 1,
            "type": "integer"
          },
          "created_at": {
            "description": "When the delivery was created.",
            "format": "date-time",
            "type": "string"
          },
          "event_id": {
            "description": "The event that was delivered.",
            "example": "evt_2aQ9xL",
            "type": "string"
          },
          "id": {
            "description": "The delivery id.",
            "example": "whd_7bQ3nK",
            "type": "string"
          },
          "last_error": {
            "description": "Error from the last failed attempt, if any.",
            "example": "",
            "type": "string"
          },
          "last_status_code": {
            "description": "HTTP status from the most recent attempt.",
            "example": 200,
            "type": "integer"
          },
          "next_attempt_at": {
            "description": "When the next retry is scheduled, while pending.",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "status": {
            "description": "pending, delivered, or failed.",
            "example": "delivered",
            "type": "string"
          }
        },
        "required": [
          "attempts",
          "created_at",
          "event_id",
          "id",
          "last_status_code",
          "status"
        ],
        "type": "object"
      },
      "WebhookEndpoint": {
        "properties": {
          "created_at": {
            "description": "When the endpoint was created.",
            "format": "date-time",
            "type": "string"
          },
          "events": {
            "description": "Subscribed event types. Empty = all events in the catalog.",
            "example": [
              "invoice.paid",
              "invoice.overdue"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "id": {
            "description": "The webhook endpoint id.",
            "example": "whk_5tQ8mR2x",
            "type": "string"
          },
          "status": {
            "description": "active, paused, or auto_disabled (after sustained failures).",
            "example": "active",
            "type": "string"
          },
          "url": {
            "description": "HTTPS URL deliveries POST to.",
            "example": "https://example.com/webhooks/bob",
            "type": "string"
          }
        },
        "required": [
          "created_at",
          "events",
          "id",
          "status",
          "url"
        ],
        "type": "object"
      },
      "WebhookEndpointCreateRequest": {
        "properties": {
          "events": {
            "description": "Event types to subscribe to. Omit for all events.",
            "example": [
              "invoice.paid",
              "invoice.overdue"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "url": {
            "description": "Endpoint URL. Must be HTTPS and publicly routable.",
            "example": "https://example.com/webhooks/bob",
            "type": "string"
          }
        },
        "required": [
          "url"
        ],
        "type": "object"
      },
      "WebhookEndpointCreatedResponse": {
        "properties": {
          "endpoint": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WebhookEndpoint"
              }
            ],
            "description": "The created endpoint."
          },
          "secret": {
            "description": "One-time signing secret (bok_whsec_…). Shown once — capture it now.",
            "example": "bok_whsec_EXAMPLE_not_a_real_secret",
            "type": "string"
          }
        },
        "required": [
          "endpoint",
          "secret"
        ],
        "type": "object"
      },
      "WebhookEndpointUpdateRequest": {
        "properties": {
          "events": {
            "items": {
              "type": "string"
            },
            "nullable": true,
            "type": "array"
          },
          "status": {
            "nullable": true,
            "type": "string"
          },
          "url": {
            "nullable": true,
            "type": "string"
          }
        },
        "type": "object"
      },
      "WebhookSecretRotatedResponse": {
        "properties": {
          "endpoint": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WebhookEndpoint"
              }
            ],
            "description": "The endpoint whose secret was rotated."
          },
          "secret": {
            "description": "The new one-time signing secret (bok_whsec_…). Shown once.",
            "example": "bok_whsec_EXAMPLE_not_a_real_secret",
            "type": "string"
          }
        },
        "required": [
          "endpoint",
          "secret"
        ],
        "type": "object"
      },
      "WebhookVerifyResponse": {
        "properties": {
          "delivery_id": {
            "description": "The delivery attempt's id — track it in the deliveries list.",
            "example": "whd_7bQ3nK",
            "type": "string"
          },
          "event_id": {
            "description": "The test event's id.",
            "example": "evt_2aQ9xL",
            "type": "string"
          },
          "status": {
            "description": "Always \"queued\".",
            "example": "queued",
            "type": "string"
          }
        },
        "required": [
          "delivery_id",
          "event_id",
          "status"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "BearerAuth": {
        "bearerFormat": "APIKey",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "x-bob-event-catalog": [
    {
      "name": "invoice.overdue",
      "description": "A sent invoice passed its due date without being paid.",
      "sample": {
        "invoice_id": "inv_8fq2Lm",
        "number": "INV-1042",
        "status": "overdue",
        "total_cents": 150000,
        "currency": "USD"
      }
    },
    {
      "name": "invoice.paid",
      "description": "An invoice was paid in full (terminal — after any provisional clearing window).",
      "sample": {
        "invoice_id": "inv_8fq2Lm",
        "number": "INV-1042",
        "status": "paid",
        "total_cents": 150000,
        "currency": "USD"
      }
    },
    {
      "name": "invoice.sent",
      "description": "An invoice was sent to its recipient.",
      "sample": {
        "invoice_id": "inv_8fq2Lm",
        "number": "INV-1042",
        "status": "sent",
        "total_cents": 150000,
        "currency": "USD"
      }
    },
    {
      "name": "invoice.voided",
      "description": "An invoice was voided and is no longer payable.",
      "sample": {
        "invoice_id": "inv_8fq2Lm",
        "number": "INV-1042",
        "status": "voided",
        "total_cents": 150000,
        "currency": "USD"
      }
    },
    {
      "name": "payment.canceled",
      "description": "A payment was canceled and will not proceed.",
      "sample": {
        "payment_intent_id": "pi_3Nk2Ht7Qz",
        "amount_cents": 42000,
        "currency": "USD"
      }
    },
    {
      "name": "payment.completed",
      "description": "A payment completed (settled).",
      "sample": {
        "payment_intent_id": "pi_3Nk2Ht7Qz",
        "amount_cents": 42000,
        "currency": "USD"
      }
    },
    {
      "name": "payment.failed",
      "description": "A payment failed and did not move money.",
      "sample": {
        "payment_intent_id": "pi_3Nk2Ht7Qz",
        "amount_cents": 42000,
        "currency": "USD"
      }
    },
    {
      "name": "payment.held",
      "description": "A payment is held awaiting approval or step-up before it can proceed.",
      "sample": {
        "payment_intent_id": "pi_3Nk2Ht7Qz",
        "amount_cents": 42000,
        "currency": "USD"
      }
    },
    {
      "name": "test.event",
      "description": "A test event sent on demand to verify an endpoint's delivery and signature handling.",
      "sample": {
        "message": "This is a test event."
      }
    }
  ]
}
