openapi: 3.1.0
# The StrideHQ public API, v1. Source of truth for the API reference (the docs repo copies it with
# `npm run openapi:sync`). lib/api/__tests__/openapi-contract.test.ts checks it against the code:
# every built endpoint is here, everything here that isn't built is marked x-planned, and every
# field an endpoint returns is documented with the right type.
info:
  title: StrideHQ API
  version: 0.1.0
  description: Creators, invoices, purchase orders, payment runs and payments, for one organisation per key.
servers:
  - url: https://api.sandbox.stridehq.ai/v1
    description: Sandbox
  - url: https://api.stridehq.ai/v1
    description: Production
security:
  - bearer: []
tags:
  - name: Organisation
  - name: Creators
  - name: Invoices
  - name: Purchase orders
  - name: Payment runs
  - name: Payments
  - name: Webhooks
  - name: Events
  - name: Reference data
  - name: Sandbox test helpers
    description: Sandbox only. Not part of the production API.

paths:
  /organisation:
    get:
      operationId: get-organisation
      summary: Get your organisation
      description: Returns the organisation the key belongs to, including its fee rate and which features are on.
      tags: [Organisation]
      x-scope: read
      responses:
        "200": { description: "Your organisation.", content: { application/json: { schema: { $ref: "#/components/schemas/Organisation" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }

  /creators:
    get:
      operationId: list-creators
      summary: List creators
      description: Returns your creators, newest first.
      tags: [Creators]
      x-scope: read
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/StartingAfter"
        - { name: sort, in: query, description: "One of `created_at`, `updated_at`, `name`. Prefix with `-` for descending.", schema: { type: string, default: "-created_at" } }
        - { name: status, in: query, description: "Only creators with this status.", schema: { $ref: "#/components/schemas/CreatorStatus" } }
        - { name: can_be_paid, in: query, description: "Only creators who can or can't be paid right now.", schema: { type: boolean } }
        - $ref: "#/components/parameters/CreatedAfter"
        - $ref: "#/components/parameters/CreatedBefore"
      responses:
        "200": { description: "A page of creators.", content: { application/json: { schema: { $ref: "#/components/schemas/CreatorList" } } } }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorised" }
    post:
      operationId: create-creator
      summary: Create a creator
      description: Adds a creator. Bank details can't be sent through the API; the creator adds them in StrideHQ. Check with Search creators first to avoid duplicates.
      tags: [Creators]
      x-scope: creators:write
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreatorCreate" }
      responses:
        "201": { description: "The creator.", content: { application/json: { schema: { $ref: "#/components/schemas/Creator" } } } }
        "400": { description: "`unknown_field`, `invalid_parameter`, `bank_details_not_accepted`, `invalid_json` or `invalid_content_type`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "409": { description: "`creator_email_exists` or `external_id_exists` (`error.existing_id` is the existing creator's id), or `idempotency_key_reused` or `idempotency_key_in_use`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "422": { description: "`currency_not_supported` or `country_not_supported`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }

  /creators/search:
    get:
      operationId: search-creators
      summary: Search creators
      description: Finds a creator by exact email or external id. Returns at most one creator, so you can check before creating.
      tags: [Creators]
      x-scope: read
      parameters:
        - { name: email, in: query, description: "Exact email address. Not case sensitive.", schema: { type: string, format: email }, example: maya@example.com }
        - { name: external_id, in: query, description: "Your own reference for the creator.", schema: { type: string } }
      responses:
        "200": { description: "A list with the matching creator, or an empty list.", content: { application/json: { schema: { $ref: "#/components/schemas/CreatorList" } } } }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorised" }

  /creators/{id}:
    parameters:
      - { name: id, in: path, required: true, description: "The creator's id.", schema: { type: string }, example: cre_8f3a2c1d9b7e4f60a1b2c3d4e5f60718 }
    get:
      operationId: get-creator
      summary: Get a creator
      description: Returns one creator, including whether they can be paid right now and why not.
      tags: [Creators]
      x-scope: read
      responses:
        "200": { description: "The creator.", content: { application/json: { schema: { $ref: "#/components/schemas/Creator" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      operationId: update-creator
      summary: Update a creator
      description: Updates the fields you send. Status can't be changed through the API, and email and payout currency can't be changed once the creator has added bank details.
      tags: [Creators]
      x-scope: creators:write
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreatorUpdate" }
      responses:
        "200": { description: "The updated creator.", content: { application/json: { schema: { $ref: "#/components/schemas/Creator" } } } }
        "400": { description: "`unknown_field` (including `status`), `invalid_parameter`, `bank_details_not_accepted`, `invalid_json` or `invalid_content_type`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { description: "`creator_email_exists` or `external_id_exists`; `creator_has_bank_details` (email and payout currency can't be changed once the creator has added bank details); or `idempotency_key_reused` or `idempotency_key_in_use`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "422": { description: "`currency_not_supported` or `country_not_supported`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }

  /invoices:
    get:
      operationId: list-invoices
      summary: List invoices
      description: Returns your invoices, newest first.
      tags: [Invoices]
      x-scope: read
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/StartingAfter"
        - { name: sort, in: query, description: "One of `created_at`, `updated_at`, `due_date`, `invoice_date`, `total_amount`. Prefix with `-` for descending.", schema: { type: string, default: "-created_at" } }
        - { name: status, in: query, description: "Only invoices with this status.", schema: { $ref: "#/components/schemas/InvoiceStatus" } }
        - { name: creator_id, in: query, description: "Only this creator's invoices.", schema: { type: string } }
        - { name: purchase_order_id, in: query, description: "Only invoices against this purchase order.", schema: { type: string } }
        - { name: payment_run_id, in: query, description: "Only invoices in this payment run.", schema: { type: string } }
        - { name: due_before, in: query, description: "Only invoices due before this date.", schema: { type: string, format: date } }
        - $ref: "#/components/parameters/CreatedAfter"
        - $ref: "#/components/parameters/CreatedBefore"
      responses:
        "200": { description: "A page of invoices.", content: { application/json: { schema: { $ref: "#/components/schemas/InvoiceList" } } } }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorised" }
    post:
      operationId: submit-invoice
      summary: Submit an invoice
      description: Submits an invoice as structured fields for a creator you've already added. It waits for review in StrideHQ like an invoice uploaded there, and is matched to your purchase orders. Nothing is approved through the API. Uploading the invoice file through the API is coming later. `Idempotency-Key` is required.
      tags: [Invoices]
      x-scope: invoices:write
      parameters:
        - $ref: "#/components/parameters/IdempotencyKeyRequired"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/InvoiceCreate" }
      responses:
        "201": { description: "The invoice, `in_review`.", content: { application/json: { schema: { $ref: "#/components/schemas/Invoice" } } } }
        "400": { description: "`unknown_field`, `invalid_parameter` (including a `creator_id` that isn't in your organisation), `idempotency_key_required`, `invalid_json` or `invalid_content_type`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "409": { description: "`duplicate_invoice` (the same creator and invoice number) or `external_id_exists` (`error.existing_id` is the existing invoice's id), or `idempotency_key_reused` or `idempotency_key_in_use`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "422": { description: "`currency_not_supported`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }

  /invoices/{id}:
    parameters:
      - { name: id, in: path, required: true, description: "The invoice's id.", schema: { type: string }, example: inv_4b1e7d2a9c3f4e5d8a6b0c1d2e3f4a5b }
    get:
      operationId: get-invoice
      summary: Get an invoice
      description: Returns one invoice, with its purchase order match and where it is in payment.
      tags: [Invoices]
      x-scope: read
      responses:
        "200": { description: "The invoice.", content: { application/json: { schema: { $ref: "#/components/schemas/Invoice" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      operationId: update-invoice
      summary: Update an invoice
      description: Updates an invoice that is `in_review` or `action_required`. Approved invoices can't be changed.
      tags: [Invoices]
      x-scope: invoices:write
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/InvoiceUpdate" }
      responses:
        "200": { description: "The updated invoice.", content: { application/json: { schema: { $ref: "#/components/schemas/Invoice" } } } }
        "400": { description: "`unknown_field` (including `currency` and `creator_id`, which can't be changed), `invalid_parameter`, `invalid_json` or `invalid_content_type`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { description: "`invoice_not_editable`, `duplicate_invoice`, `idempotency_key_reused` or `idempotency_key_in_use`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }

  /invoices/{id}/file:
    parameters:
      - { name: id, in: path, required: true, description: "The invoice's id.", schema: { type: string }, example: inv_4b1e7d2a9c3f4e5d8a6b0c1d2e3f4a5b }
    get:
      operationId: get-invoice-file
      summary: Get the invoice file
      description: Returns a signed link to the invoice file. The link expires after 5 minutes.
      tags: [Invoices]
      x-scope: read
      responses:
        "200": { description: "A signed link.", content: { application/json: { schema: { $ref: "#/components/schemas/FileLink" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "404": { $ref: "#/components/responses/NotFound" }

  /purchase_orders:
    get:
      operationId: list-purchase-orders
      summary: List purchase orders
      description: Returns the purchase orders synced from your purchase order spreadsheet.
      tags: [Purchase orders]
      x-scope: read
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/StartingAfter"
        - { name: po_number, in: query, description: "Exact purchase order number.", schema: { type: string } }
        - { name: status, in: query, description: "`open` or `closed`.", schema: { type: string, enum: [open, closed] } }
      responses:
        "200": { description: "A page of purchase orders.", content: { application/json: { schema: { $ref: "#/components/schemas/PurchaseOrderList" } } } }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorised" }

  /purchase_orders/{id}:
    get:
      operationId: get-purchase-order
      summary: Get a purchase order
      description: Returns one purchase order, with how much has been invoiced against it.
      tags: [Purchase orders]
      x-scope: read
      parameters:
        - { name: id, in: path, required: true, description: "The purchase order's id.", schema: { type: string }, example: po_2d4f6a8b0c1e3a5b7d9f1a2b3c4d5e6f }
      responses:
        "200": { description: "The purchase order.", content: { application/json: { schema: { $ref: "#/components/schemas/PurchaseOrder" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "404": { $ref: "#/components/responses/NotFound" }

  /payment_runs:
    get:
      operationId: list-payment-runs
      summary: List payment runs
      description: Returns your payment runs, newest first.
      tags: [Payment runs]
      x-scope: read
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/StartingAfter"
        - { name: sort, in: query, description: "One of `created_at`, `updated_at`, `total_amount`. Prefix with `-` for descending.", schema: { type: string, default: "-created_at" } }
        - { name: status, in: query, description: "Only runs with this status.", schema: { $ref: "#/components/schemas/PaymentRunStatus" } }
        - $ref: "#/components/parameters/CreatedAfter"
        - $ref: "#/components/parameters/CreatedBefore"
      responses:
        "200": { description: "A page of payment runs.", content: { application/json: { schema: { $ref: "#/components/schemas/PaymentRunList" } } } }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorised" }
    post:
      operationId: prepare-payment-run
      summary: Prepare a payment run
      description: Prepares a run from approved invoices, by id or by rules. Invoices that can't be paid are left out, each with a reason in `skipped`. The run waits for a person to approve it in StrideHQ; no key can approve, fund or pay. `Idempotency-Key` is required.
      tags: [Payment runs]
      x-scope: payment_runs:prepare
      parameters:
        - $ref: "#/components/parameters/IdempotencyKeyRequired"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PaymentRunCreate" }
      responses:
        "201": { description: "The run, `pending_approval`, with the invoices that were left out in `skipped`.", content: { application/json: { schema: { $ref: "#/components/schemas/PaymentRun" } } } }
        "400": { description: "`unknown_field`, `invalid_parameter`, `idempotency_key_required`, `invalid_json` or `invalid_content_type`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "403": { description: "`organisation_inactive`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "409": { description: "`invoice_already_in_run`, `idempotency_key_reused` or `idempotency_key_in_use`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "422": { description: "`nothing_to_pay` (every invoice was skipped; the list is in `error.skipped`) or `own_account_required`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "429": { $ref: "#/components/responses/RateLimited" }

  /payment_runs/{id}:
    get:
      operationId: get-payment-run
      summary: Get a payment run
      description: Returns one run with its totals, fees, what was skipped and how much has been received.
      tags: [Payment runs]
      x-scope: read
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200": { description: "The run.", content: { application/json: { schema: { $ref: "#/components/schemas/PaymentRun" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "404": { $ref: "#/components/responses/NotFound" }

  /payment_runs/{id}/withdraw:
    post:
      operationId: withdraw-payment-run
      summary: Withdraw a payment run
      description: Withdraws a run that is still `pending_approval`. Its invoices go back to `approved`.
      tags: [Payment runs]
      x-scope: payment_runs:prepare
      parameters:
        - $ref: "#/components/parameters/RunId"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200": { description: "The run, `cancelled`.", content: { application/json: { schema: { $ref: "#/components/schemas/PaymentRun" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { description: "`run_not_withdrawable` (the run isn't `pending_approval`), `idempotency_key_reused` or `idempotency_key_in_use`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }

  /payment_runs/{id}/remittance_advice:
    get:
      operationId: get-remittance-advice
      summary: Get remittance advice
      description: Returns a signed link to the run's remittance advice PDF, once it has paid payments. The link expires after 5 minutes.
      tags: [Payment runs]
      x-scope: read
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200": { description: "A signed link.", content: { application/json: { schema: { $ref: "#/components/schemas/FileLink" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { description: "`remittance_not_available`: the run isn't funded yet.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }

  /payments:
    get:
      operationId: list-payments
      summary: List payments
      description: Returns payments, one per creator per run, newest first.
      tags: [Payments]
      x-scope: read
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/StartingAfter"
        - { name: payment_run_id, in: query, description: "Only payments in this run.", schema: { type: string } }
        - { name: creator_id, in: query, description: "Only payments to this creator.", schema: { type: string } }
        - { name: status, in: query, description: "Only payments with this status.", schema: { $ref: "#/components/schemas/PaymentStatus" } }
      responses:
        "200": { description: "A page of payments.", content: { application/json: { schema: { $ref: "#/components/schemas/PaymentList" } } } }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorised" }

  /payments/{id}:
    get:
      operationId: get-payment
      summary: Get a payment
      description: Returns one payment, with the exchange rate used and why it failed, if it did.
      tags: [Payments]
      x-scope: read
      parameters:
        - { name: id, in: path, required: true, description: "The payment's id.", schema: { type: string }, example: pay_7c9e1a3b5d7f4a2c8e0b1d3f5a7c9e1b }
      responses:
        "200": { description: "The payment.", content: { application/json: { schema: { $ref: "#/components/schemas/Payment" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "404": { $ref: "#/components/responses/NotFound" }

  /webhook_endpoints:
    get:
      operationId: list-webhook-endpoints
      x-planned: true
      summary: List webhook endpoints
      description: Returns your webhook endpoints.
      tags: [Webhooks]
      x-scope: read
      responses:
        "200": { description: "Your endpoints.", content: { application/json: { schema: { $ref: "#/components/schemas/WebhookEndpointList" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
    post:
      operationId: create-webhook-endpoint
      x-planned: true
      summary: Create a webhook endpoint
      description: Adds an endpoint. The signing `secret` is only returned here, once.
      tags: [Webhooks]
      x-scope: webhooks:manage
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/WebhookEndpointCreate" }
      responses:
        "201": { description: "The endpoint, with its secret.", content: { application/json: { schema: { $ref: "#/components/schemas/WebhookEndpointWithSecret" } } } }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorised" }

  /webhook_endpoints/{id}:
    parameters:
      - $ref: "#/components/parameters/EndpointId"
    get:
      operationId: get-webhook-endpoint
      x-planned: true
      summary: Get a webhook endpoint
      description: Returns one endpoint. The secret isn't included.
      tags: [Webhooks]
      x-scope: read
      responses:
        "200": { description: "The endpoint.", content: { application/json: { schema: { $ref: "#/components/schemas/WebhookEndpoint" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      operationId: update-webhook-endpoint
      x-planned: true
      summary: Update a webhook endpoint
      description: Changes the address, the events it receives, or turns it on or off. Use this to re-enable an endpoint that was disabled after failed deliveries.
      tags: [Webhooks]
      x-scope: webhooks:manage
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/WebhookEndpointUpdate" }
      responses:
        "200": { description: "The updated endpoint.", content: { application/json: { schema: { $ref: "#/components/schemas/WebhookEndpoint" } } } }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      operationId: delete-webhook-endpoint
      x-planned: true
      summary: Delete a webhook endpoint
      description: Stops deliveries straight away. The delivery log is kept for 30 days.
      tags: [Webhooks]
      x-scope: webhooks:manage
      responses:
        "200": { description: "Deleted.", content: { application/json: { schema: { $ref: "#/components/schemas/Deleted" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "404": { $ref: "#/components/responses/NotFound" }

  /webhook_endpoints/{id}/test:
    post:
      operationId: test-webhook-endpoint
      x-planned: true
      summary: Send a test event
      description: Sends a `test.ping` event to the endpoint straight away and returns the delivery result.
      tags: [Webhooks]
      x-scope: webhooks:manage
      parameters:
        - $ref: "#/components/parameters/EndpointId"
      responses:
        "200": { description: "The delivery result.", content: { application/json: { schema: { $ref: "#/components/schemas/Delivery" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "404": { $ref: "#/components/responses/NotFound" }

  /events:
    get:
      operationId: list-events
      x-planned: true
      summary: List events
      description: Returns events from the last 30 days, newest first.
      tags: [Events]
      x-scope: read
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/StartingAfter"
        - { name: type, in: query, description: "Only events of this type, for example `payment_run.funded`.", schema: { type: string } }
      responses:
        "200": { description: "A page of events.", content: { application/json: { schema: { $ref: "#/components/schemas/EventList" } } } }
        "400": { $ref: "#/components/responses/InvalidRequest" }
        "401": { $ref: "#/components/responses/Unauthorised" }

  /events/{id}:
    get:
      operationId: get-event
      x-planned: true
      summary: Get an event
      description: Returns one event, with the object as it was when the event happened.
      tags: [Events]
      x-scope: read
      parameters:
        - $ref: "#/components/parameters/EventId"
      responses:
        "200": { description: "The event.", content: { application/json: { schema: { $ref: "#/components/schemas/Event" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "404": { $ref: "#/components/responses/NotFound" }

  /events/{id}/resend:
    post:
      operationId: resend-event
      x-planned: true
      summary: Resend an event
      description: Delivers an event up to 30 days old again, to every enabled endpoint subscribed to it, or to one endpoint.
      tags: [Events]
      x-scope: webhooks:manage
      parameters:
        - $ref: "#/components/parameters/EventId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                endpoint_id: { type: string, description: "Send to this endpoint only.", example: we_5e7a9c1b3d5f4a6c8e0a2b4c6d8e0f1a }
      responses:
        "200": { description: "The delivery results.", content: { application/json: { schema: { $ref: "#/components/schemas/DeliveryList" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }
        "404": { $ref: "#/components/responses/NotFound" }

  /reference/currencies:
    get:
      operationId: list-currencies
      summary: List currencies
      description: Every currency StrideHQ can pay out in, with whether each is switched on for your organisation. Others can be switched on on request.
      tags: [Reference data]
      x-scope: read
      responses:
        "200": { description: "The currencies.", content: { application/json: { schema: { $ref: "#/components/schemas/CurrencyList" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }

  /reference/countries:
    get:
      operationId: list-countries
      summary: List countries
      description: Every country StrideHQ can pay creators in, with whether each is switched on for your organisation.
      tags: [Reference data]
      x-scope: read
      responses:
        "200": { description: "The countries.", content: { application/json: { schema: { $ref: "#/components/schemas/CountryList" } } } }
        "401": { $ref: "#/components/responses/Unauthorised" }

  # Placeholder addresses: the spec lists these helpers but not their paths yet.
  /test_helpers/payment_runs/{id}/approve:
    post:
      operationId: test-approve-run
      x-planned: true
      summary: Approve a run
      description: Sandbox only. Approves a `pending_approval` run as if a person had approved it in StrideHQ.
      tags: [Sandbox test helpers]
      x-scope: payment_runs:prepare
      x-sandbox-only: true
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200": { description: "The run, `awaiting_funds`.", content: { application/json: { schema: { $ref: "#/components/schemas/PaymentRun" } } } }
        "404": { $ref: "#/components/responses/NotFound" }
  /test_helpers/payment_runs/{id}/fund:
    post:
      operationId: test-fund-run
      x-planned: true
      summary: Fund a run
      description: Sandbox only. Records the transfer for an `awaiting_funds` run. Send `amount` to fund part of it.
      tags: [Sandbox test helpers]
      x-scope: payment_runs:prepare
      x-sandbox-only: true
      parameters:
        - $ref: "#/components/parameters/RunId"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                amount: { type: integer, description: "Amount received in minor units. Defaults to the full amount to fund.", example: 252500 }
      responses:
        "200": { description: "The run, `funded` or `partially_funded`.", content: { application/json: { schema: { $ref: "#/components/schemas/PaymentRun" } } } }
        "404": { $ref: "#/components/responses/NotFound" }
  /test_helpers/payment_runs/{id}/complete:
    post:
      operationId: test-complete-run
      x-planned: true
      summary: Complete a run
      description: Sandbox only. Releases and pays every payment in a `funded` run.
      tags: [Sandbox test helpers]
      x-scope: payment_runs:prepare
      x-sandbox-only: true
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200": { description: "The run, `completed`.", content: { application/json: { schema: { $ref: "#/components/schemas/PaymentRun" } } } }
        "404": { $ref: "#/components/responses/NotFound" }
  /test_helpers/payment_runs/{id}/fail:
    post:
      operationId: test-fail-run
      x-planned: true
      summary: Fail a run
      description: Sandbox only. Fails every payment in a `funded` run.
      tags: [Sandbox test helpers]
      x-scope: payment_runs:prepare
      x-sandbox-only: true
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200": { description: "The run, `failed`.", content: { application/json: { schema: { $ref: "#/components/schemas/PaymentRun" } } } }
        "404": { $ref: "#/components/responses/NotFound" }
  /test_helpers/payments/{id}/fail:
    post:
      operationId: test-fail-payment
      x-planned: true
      summary: Fail a payment
      description: Sandbox only. Fails one payment, with the reason you choose.
      tags: [Sandbox test helpers]
      x-scope: payment_runs:prepare
      x-sandbox-only: true
      parameters:
        - { name: id, in: path, required: true, description: "The payment's id.", schema: { type: string }, example: pay_7c9e1a3b5d7f4a2c8e0b1d3f5a7c9e1b }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                failure_reason: { $ref: "#/components/schemas/FailureReason" }
      responses:
        "200": { description: "The payment, `failed`.", content: { application/json: { schema: { $ref: "#/components/schemas/Payment" } } } }
        "404": { $ref: "#/components/responses/NotFound" }

components:
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
      description: "An API key from Settings > Developers: `shq_test_` for the sandbox, `shq_live_` for production."

  parameters:
    Limit: { name: limit, in: query, description: "How many to return. 1 to 100.", schema: { type: integer, minimum: 1, maximum: 100, default: 25 } }
    StartingAfter: { name: starting_after, in: query, description: "The last id from the previous page. Use it when `has_more` is true.", schema: { type: string } }
    CreatedAfter: { name: created_after, in: query, description: "Only those created after this time.", schema: { type: string, format: date-time } }
    CreatedBefore: { name: created_before, in: query, description: "Only those created before this time.", schema: { type: string, format: date-time } }
    IdempotencyKey: { name: Idempotency-Key, in: header, description: "A unique value, such as a UUID. The same key and body within 24 hours returns the original response.", schema: { type: string }, example: 5f2b9c1e-3a7d-4e8f-9b0c-1d2e3f4a5b6c }
    IdempotencyKeyRequired: { name: Idempotency-Key, in: header, required: true, description: "A unique value, such as a UUID. The same key and body within 24 hours returns the original response.", schema: { type: string }, example: 5f2b9c1e-3a7d-4e8f-9b0c-1d2e3f4a5b6c }
    RunId: { name: id, in: path, required: true, description: "The payment run's id.", schema: { type: string }, example: run_3e5a7c9b1d2f4a6c8e0b2d4f6a8c0e1d }
    EndpointId: { name: id, in: path, required: true, description: "The webhook endpoint's id.", schema: { type: string }, example: we_5e7a9c1b3d5f4a6c8e0a2b4c6d8e0f1a }
    EventId: { name: id, in: path, required: true, description: "The event's id.", schema: { type: string }, example: evt_9a1c3e5b7d9f4b6a8c0e2a4c6e8a0b2d }

  responses:
    InvalidRequest: { description: "`unknown_field`, `invalid_parameter` or `invalid_cursor`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
    Unauthorised: { description: "`missing_api_key`, `invalid_api_key`, `revoked_api_key` or `wrong_environment`.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
    NotFound: { description: "`resource_not_found`: no such object in your organisation.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
    RateLimited: { description: "`rate_limited`. Wait for `Retry-After` seconds.", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }

  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            type: { type: string, example: not_found }
            code: { type: string, example: resource_not_found }
            message: { type: string, example: No such creator. }
            param: { type: [string, "null"], example: null }
            request_id: { type: string, example: req_6b8d0f2a4c6e4a8b }

    Organisation:
      type: object
      properties:
        id: { type: string, example: org_1a3c5e7b9d0f4a2c6e8b0d2f4a6c8e0b }
        object: { type: string, const: organisation, example: organisation }
        name: { type: string, example: Brightside Studio }
        base_currency: { type: string, example: GBP }
        environment: { type: string, enum: [sandbox, production], example: sandbox }
        fee_rate: { type: string, description: "StrideHQ's transaction fee, as a percentage of each payment run.", example: "2.50" }
        features: { type: array, items: { type: string }, description: "For example ai_clients, ai_client_approvals.", example: [] }
        created_at: { type: string, format: date-time, example: "2026-02-17T21:07:43.453Z" }

    CreatorStatus: { type: string, enum: [onboarding, active, on_hold], description: "`onboarding`: details still being collected. `active`: details complete. `on_hold`: your team has stopped payments." }
    Creator:
      type: object
      properties:
        id: { type: string, example: cre_8f3a2c1d9b7e4f60a1b2c3d4e5f60718 }
        object: { type: string, const: creator, example: creator }
        name: { type: string, example: Maya Chen }
        legal_name: { type: [string, "null"], example: Maya Chen }
        email: { type: string, example: maya@example.com }
        type: { type: string, enum: [individual, agency, company], example: individual }
        country: { type: string, description: "ISO 3166-1 alpha-2.", example: GB }
        tax_status: { type: [string, "null"], example: self_employed }
        vat_registered: { type: boolean, example: false }
        status: { $ref: "#/components/schemas/CreatorStatus", example: active }
        can_be_paid: { type: boolean, description: "Whether the creator could be included in a payment run right now.", example: true }
        cannot_be_paid_reasons: { type: array, items: { type: string }, description: "For example `bank_details_missing`, `tax_status_missing`, `sanctions_check_pending`.", example: [] }
        bank_account_last4: { type: [string, "null"], description: "Null until bank details are added in StrideHQ.", example: "4821" }
        payout_currency: { type: string, example: GBP }
        external_id: { type: [string, "null"], example: talent-1042 }
        metadata: { $ref: "#/components/schemas/Metadata" }
        created_at: { type: string, format: date-time, example: "2026-10-01T09:30:00Z" }
        updated_at: { type: string, format: date-time, example: "2026-10-14T16:05:00Z" }
    CreatorCreate:
      type: object
      required: [name, email]
      properties:
        name: { type: string, description: "The name the creator works under.", example: Maya Chen }
        email: { type: string, format: email, description: "Stored in lower case. Must be unique in your organisation.", example: maya@example.com }
        legal_name: { type: string, description: "Their legal name, if different.", example: Maya Chen }
        country: { type: string, description: "ISO 3166-1 alpha-2. Must be switched on for your organisation.", example: GB }
        payout_currency: { type: string, description: "ISO 4217. Must be switched on for your organisation.", example: GBP }
        external_id: { type: string, description: "Your own reference, up to 100 characters. Unique in your organisation.", example: talent-1042 }
        metadata: { $ref: "#/components/schemas/Metadata" }
    CreatorUpdate:
      type: object
      properties:
        name: { type: string, example: Maya Chen }
        email: { type: string, format: email, example: maya@example.com }
        legal_name: { type: string, example: Maya Chen }
        country: { type: string, example: GB }
        payout_currency: { type: string, example: GBP }
        external_id: { type: string, example: talent-1042 }
        metadata: { $ref: "#/components/schemas/Metadata" }
    CreatorList: { type: object, properties: { object: { type: string, example: list }, data: { type: array, items: { $ref: "#/components/schemas/Creator" } }, has_more: { type: boolean, example: false } } }

    InvoiceStatus: { type: string, enum: [in_review, action_required, awaiting_accounting_approval, approved, in_payment_run, paid, rejected] }
    Invoice:
      type: object
      properties:
        id: { type: string, example: inv_4b1e7d2a9c3f4e5d8a6b0c1d2e3f4a5b }
        object: { type: string, const: invoice, example: invoice }
        creator_id: { type: string, example: cre_8f3a2c1d9b7e4f60a1b2c3d4e5f60718 }
        invoice_number: { type: string, example: MC-0042 }
        invoice_date: { type: string, format: date, example: "2026-10-10" }
        due_date: { type: [string, "null"], format: date, example: "2026-11-09" }
        purchase_order_id: { type: [string, "null"], example: po_2d4f6a8b0c1e3a5b7d9f1a2b3c4d5e6f }
        po_match: { type: [string, "null"], enum: [matches, part_payment, amount_differs, not_found, null], example: matches }
        description: { type: [string, "null"], example: "Autumn campaign, 3 videos" }
        net_amount: { type: integer, description: "Minor units.", example: 250000 }
        vat_amount: { type: integer, description: "Minor units.", example: 0 }
        total_amount: { type: integer, description: "Minor units.", example: 250000 }
        currency: { type: string, example: GBP }
        status: { $ref: "#/components/schemas/InvoiceStatus", example: approved }
        action_required_reason: { type: [string, "null"], enum: [bank_details_missing, address_missing, could_not_read, null], example: null }
        rejection_reason: { type: [string, "null"], example: null }
        payment_run_id: { type: [string, "null"], example: null }
        payment_id: { type: [string, "null"], example: null }
        has_file: { type: boolean, example: true }
        external_id: { type: [string, "null"], example: null }
        metadata: { $ref: "#/components/schemas/Metadata" }
        created_at: { type: string, format: date-time, example: "2026-10-10T11:20:00Z" }
        updated_at: { type: string, format: date-time, example: "2026-10-12T08:45:00Z" }
    InvoiceCreate:
      type: object
      required: [creator_id, invoice_number, total_amount, currency]
      properties:
        creator_id: { type: string, description: "The creator who sent the invoice. Add them first with Create a creator.", example: cre_8f3a2c1d9b7e4f60a1b2c3d4e5f60718 }
        invoice_number: { type: string, description: "Up to 100 characters. Unique per creator.", example: MC-0042 }
        invoice_date: { type: string, format: date, example: "2026-10-10" }
        due_date: { type: string, format: date, example: "2026-11-09" }
        po_number: { type: string, description: "Matched against your purchase orders.", example: PO-2026-118 }
        description: { type: string, example: "Autumn campaign, 3 videos" }
        net_amount: { type: integer, description: "Minor units. Optional; if you send it with vat_amount, they must add up to total_amount.", example: 250000 }
        vat_amount: { type: integer, description: "Minor units. Optional; 0 if neither it nor net_amount is sent.", example: 0 }
        total_amount: { type: integer, description: "Minor units.", example: 250000 }
        currency: { type: string, description: "ISO 4217.", example: GBP }
        external_id: { type: string, description: "Your own reference, up to 100 characters. Unique in your organisation.", example: inv-77810 }
        metadata: { $ref: "#/components/schemas/Metadata" }
    InvoiceUpdate:
      type: object
      properties:
        invoice_number: { type: string, example: MC-0042 }
        invoice_date: { type: string, format: date, example: "2026-10-10" }
        due_date: { type: string, format: date, example: "2026-11-09" }
        po_number: { type: string, example: PO-2026-118 }
        description: { type: string, example: "Autumn campaign, 3 videos" }
        total_amount: { type: integer, description: "Minor units. VAT stays the same; the net amount changes.", example: 250000 }
        metadata: { $ref: "#/components/schemas/Metadata" }
    InvoiceList: { type: object, properties: { object: { type: string, example: list }, data: { type: array, items: { $ref: "#/components/schemas/Invoice" } }, has_more: { type: boolean, example: false } } }

    PurchaseOrder:
      type: object
      properties:
        id: { type: string, example: po_2d4f6a8b0c1e3a5b7d9f1a2b3c4d5e6f }
        object: { type: string, const: purchase_order, example: purchase_order }
        po_number: { type: string, example: PO-2026-118 }
        brand: { type: string, example: Northwind Drinks }
        campaign: { type: string, example: Autumn launch }
        creator_name: { type: string, description: "As written in your spreadsheet. Not linked to a creator.", example: Maya Chen }
        agreed_amount: { type: integer, description: "Minor units.", example: 500000 }
        amount_invoiced: { type: integer, description: "Minor units.", example: 250000 }
        remaining_amount: { type: integer, description: "Minor units.", example: 250000 }
        currency: { type: string, example: GBP }
        status: { type: string, enum: [open, closed], example: open }
        created_at: { type: string, format: date-time, example: "2026-09-20T10:00:00Z" }
    PurchaseOrderList: { type: object, properties: { object: { type: string, example: list }, data: { type: array, items: { $ref: "#/components/schemas/PurchaseOrder" } }, has_more: { type: boolean, example: false } } }

    PaymentRunStatus: { type: string, enum: [pending_approval, awaiting_funds, partially_funded, funded, paying, completed, partially_completed, failed, cancelled, expired] }
    PaymentRun:
      type: object
      properties:
        id: { type: string, example: run_3e5a7c9b1d2f4a6c8e0b2d4f6a8c0e1d }
        object: { type: string, const: payment_run, example: payment_run }
        reference: { type: string, description: "Use it as the bank transfer reference when you fund the run.", example: PR-BRIGHT-26-014 }
        status: { $ref: "#/components/schemas/PaymentRunStatus", example: pending_approval }
        created_by_type: { type: string, enum: [user, agent, api, ai_client], example: api }
        invoice_ids: { type: array, items: { type: string }, example: [inv_4b1e7d2a9c3f4e5d8a6b0c1d2e3f4a5b] }
        skipped:
          type: array
          description: Invoices left out of the run, each with a reason. Only in the response to Prepare a payment run.
          items:
            type: object
            properties:
              invoice_id: { type: string }
              code: { type: string }
              message: { type: string }
          example:
            - { invoice_id: inv_0a2c4e6b8d1f4a3c5e7b9d0f2a4c6e8b, code: bank_details_missing, message: The creator hasn't added bank details. }
        funding_currency: { type: string, example: GBP }
        totals:
          type: array
          description: What creators receive, per payout currency. Minor units.
          items:
            type: object
            properties:
              currency: { type: string }
              amount: { type: integer }
          example:
            - { currency: GBP, amount: 250000 }
        subtotal: { type: integer, description: "The payments in the run. Minor units.", example: 250000 }
        fee_rate: { type: string, example: "2.50" }
        transaction_fee: { type: integer, description: "StrideHQ's transaction fee: `fee_rate` percent of the run. Minor units.", example: 6250 }
        international_payment_fee: { type: integer, description: "Charge for international (SWIFT) payments, at cost. Zero when every payment uses a local route.", example: 0 }
        total_amount: { type: integer, description: "The amount to fund. Minor units.", example: 256250 }
        amount_received: { type: integer, example: 0 }
        amount_outstanding: { type: integer, example: 256250 }
        approved_by: { type: [string, "null"], example: null }
        approved_at: { type: [string, "null"], format: date-time, example: null }
        approval_url: { type: string, description: "Where a person approves the run in StrideHQ.", example: "https://app.stridehq.ai/payments/runs?tab=runs" }
        notes: { type: [string, "null"], description: "Notes shown to the approver.", example: "Weekly creator payments" }
        metadata: { $ref: "#/components/schemas/Metadata" }
        created_at: { type: string, format: date-time, example: "2026-10-29T09:00:00Z" }
        updated_at: { type: string, format: date-time, example: "2026-10-29T09:00:00Z" }
    PaymentRunCreate:
      type: object
      description: Send `invoice_ids`, or rules to pick approved invoices that are due. With no rules, every approved invoice that's due is included.
      properties:
        invoice_ids: { type: array, items: { type: string }, description: "Approved invoices to include, up to 200.", example: [inv_4b1e7d2a9c3f4e5d8a6b0c1d2e3f4a5b] }
        due_within_days: { type: integer, description: "Rule. Invoices due on or before this many days from today (0 to 365). An invoice with no due date counts as due today.", example: 7 }
        include_overdue: { type: boolean, description: "Rule. Include invoices past their due date. Default true.", example: true }
        currency: { type: string, description: "Rule. Only invoices paid in this currency (ISO 4217).", example: GBP }
        limit_amount: { type: integer, description: "Rule. Invoices are added oldest due date first until the next would go over this total, in minor units. Needs `currency` when the invoices are in more than one.", example: 1000000 }
        notes: { type: string, description: "Shown to the approver. Up to 500 characters.", example: Weekly creator payments }
        metadata: { $ref: "#/components/schemas/Metadata" }
    PaymentRunList: { type: object, properties: { object: { type: string, example: list }, data: { type: array, items: { $ref: "#/components/schemas/PaymentRun" } }, has_more: { type: boolean, example: false } } }

    PaymentStatus: { type: string, enum: [pending, on_hold, processing, sent, paid, failed] }
    FailureReason: { type: string, enum: [bank_details_invalid, account_closed, returned_by_bank, compliance_hold, other], example: returned_by_bank }
    Payment:
      type: object
      properties:
        id: { type: string, example: pay_7c9e1a3b5d7f4a2c8e0b1d3f5a7c9e1b }
        object: { type: string, const: payment, example: payment }
        payment_run_id: { type: string, example: run_3e5a7c9b1d2f4a6c8e0b2d4f6a8c0e1d }
        creator_id: { type: string, example: cre_8f3a2c1d9b7e4f60a1b2c3d4e5f60718 }
        invoice_id: { type: [string, "null"], example: inv_4b1e7d2a9c3f4e5d8a6b0c1d2e3f4a5b }
        amount: { type: integer, description: "What the creator receives. Minor units.", example: 250000 }
        currency: { type: string, example: GBP }
        funding_amount: { type: integer, example: 250000 }
        funding_currency: { type: string, example: GBP }
        fx_rate: { type: [string, "null"], example: null }
        status: { $ref: "#/components/schemas/PaymentStatus", example: paid }
        failure_reason: { oneOf: [{ $ref: "#/components/schemas/FailureReason" }, { type: "null" }], example: null }
        failure_message: { type: [string, "null"], example: null }
        paid_at: { type: [string, "null"], format: date-time, example: "2026-10-30T10:12:00Z" }
        bank_account_last4: { type: [string, "null"], example: "4821" }
        created_at: { type: string, format: date-time, example: "2026-10-29T09:00:00Z" }
    PaymentList: { type: object, properties: { object: { type: string, example: list }, data: { type: array, items: { $ref: "#/components/schemas/Payment" } }, has_more: { type: boolean, example: false } } }

    WebhookEndpoint:
      type: object
      properties:
        id: { type: string, example: we_5e7a9c1b3d5f4a6c8e0a2b4c6d8e0f1a }
        object: { type: string, const: webhook_endpoint, example: webhook_endpoint }
        url: { type: string, example: "https://example.com/webhooks/stridehq" }
        events: { type: array, items: { type: string }, example: [payment_run.funded, payment_run.completed, payment.failed] }
        enabled: { type: boolean, example: true }
        created_at: { type: string, format: date-time, example: "2026-10-29T09:00:00Z" }
    WebhookEndpointWithSecret:
      allOf:
        - $ref: "#/components/schemas/WebhookEndpoint"
        - type: object
          properties:
            secret: { type: string, description: "The signing secret. Shown only once.", example: whsec_demo_9f8e7d6c5b4a }
    WebhookEndpointCreate:
      type: object
      required: [url, events]
      properties:
        url: { type: string, description: "An HTTPS address.", example: "https://example.com/webhooks/stridehq" }
        events: { type: array, items: { type: string }, description: "The event types to send.", example: [payment_run.funded, payment_run.completed, payment.failed] }
    WebhookEndpointUpdate:
      type: object
      properties:
        url: { type: string, example: "https://example.com/webhooks/stridehq" }
        events: { type: array, items: { type: string }, example: [payment_run.funded] }
        enabled: { type: boolean, example: true }
    WebhookEndpointList: { type: object, properties: { object: { type: string, example: list }, data: { type: array, items: { $ref: "#/components/schemas/WebhookEndpoint" } }, has_more: { type: boolean, example: false } } }
    Delivery:
      type: object
      properties:
        endpoint_id: { type: string, example: we_5e7a9c1b3d5f4a6c8e0a2b4c6d8e0f1a }
        delivered: { type: boolean, example: true }
        status_code: { type: [integer, "null"], example: 200 }
        duration_ms: { type: integer, example: 184 }
    DeliveryList: { type: object, properties: { object: { type: string, example: list }, data: { type: array, items: { $ref: "#/components/schemas/Delivery" } } } }
    Event:
      type: object
      properties:
        id: { type: string, example: evt_9a1c3e5b7d9f4b6a8c0e2a4c6e8a0b2d }
        object: { type: string, const: event, example: event }
        type: { type: string, example: payment_run.funded }
        created_at: { type: string, format: date-time, example: "2026-10-30T08:02:00Z" }
        api_version: { type: string, example: v1 }
        data: { type: object, properties: { object: { type: object, example: { id: run_3e5a7c9b1d2f4a6c8e0b2d4f6a8c0e1d, object: payment_run, status: funded } } } }
    EventList: { type: object, properties: { object: { type: string, example: list }, data: { type: array, items: { $ref: "#/components/schemas/Event" } }, has_more: { type: boolean, example: false } } }

    FileLink:
      type: object
      properties:
        url: { type: string, example: "https://files.stridehq.ai/signed/demo" }
        expires_at: { type: string, format: date-time, example: "2026-10-30T10:05:00Z" }
    Deleted:
      type: object
      properties:
        id: { type: string, example: we_5e7a9c1b3d5f4a6c8e0a2b4c6d8e0f1a }
        object: { type: string, example: webhook_endpoint }
        deleted: { type: boolean, example: true }
    CurrencyList:
      type: object
      properties:
        object: { type: string, example: list }
        has_more: { type: boolean, example: false }
        data:
          type: array
          items:
            type: object
            properties:
              code: { type: string }
              name: { type: string }
              enabled: { type: boolean }
          example:
            - { code: GBP, name: British pound, enabled: true }
            - { code: JPY, name: Japanese yen, enabled: false }
    CountryList:
      type: object
      properties:
        object: { type: string, example: list }
        has_more: { type: boolean, example: false }
        data:
          type: array
          items:
            type: object
            properties:
              code: { type: string }
              name: { type: string }
              enabled: { type: boolean }
          example:
            - { code: GB, name: United Kingdom, enabled: true }
            - { code: JP, name: Japan, enabled: false }
    Metadata:
      type: object
      description: Up to 20 string keys (up to 40 characters) and values (up to 500 characters), for your use. StrideHQ never reads them. Send null to clear it.
      additionalProperties: { type: string }
      example: {}
