---
openapi: 3.0.3
info:
  title: PayItToday API
  version: 1.0.0
  description: |
    Account metadata privacy behavior:
    - Any metadata key containing `name` or `address` (case-insensitive, including nested keys)
      is stored encrypted at rest.
    - Account API responses rehydrate those encrypted values into the returned `metadata`
      payload so authorized clients receive the full metadata they submitted.
servers:
- url: https://uat.payittoday.com
  description: Production
tags:
- name: Accounts
  description: Account manifest and account operations
- name: Barcodes
  description: Barcode registration lookup and creation APIs
- name: Biller Participation Forms
  description: Biller Participation document automation and e-sign tracking
- name: Transactions
  description: Recent payment transactions
- name: Realtime Transaction Notifications
  description: Websocket discovery for realtime payment transaction notifications
- name: Jobs
  description: Async job status
- name: Embedded Assets
  description: Tokenized embeddable assets for client-site map and widget embedding
security:
- bearerAuth: []
paths:
  "/api/v1/billers":
    get:
      tags:
      - Accounts
      summary: List billers available to the API key
      description: Biller-scoped keys return a single biller; aggregator-scoped keys
        return billers inferred from the aggregator billing accounts. Each biller
        includes its billing accounts plus verbose payment-network details for those
        billing accounts.
      parameters:
      - in: query
        name: page
        schema:
          type: integer
      - in: query
        name: per_page
        schema:
          type: integer
          maximum: 200
      responses:
        '200':
          description: Biller list
          content:
            application/json:
              schema:
                type: object
                properties:
                  billers:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Biller"
                  meta:
                    "$ref": "#/components/schemas/PaginationMeta"
              examples:
                billerScopedKey:
                  summary: Biller-scoped key returns one biller with its billing accounts
                    and payment networks
                  value:
                    billers:
                    - id: 14
                      name: Example Utility One
                      external_biller_id: EXAMPLE_BILLER_001
                      name_slug: example-utility-one
                      payment_networks:
                      - id: 1
                        name: Example Network
                        slug: example-network
                        barcodes_enabled: true
                      billing_accounts:
                      - id: 22
                        account_slug: example-billing-account
                        network_identifier: EXAMPLE_NET_001
                        payment_network_id: 1
                        landing_page_url: https://api.example.com/barcode_requests/new/billing_accounts/example-billing-account
                    meta:
                      page: 1
                      per_page: 50
                      next_page:
                aggregatorScopedKey:
                  summary: Aggregator-scoped key returns billers inferred from accessible
                    billing accounts
                  value:
                    billers:
                    - id: 14
                      name: Example Utility One
                      external_biller_id: EXAMPLE_BILLER_001
                      name_slug: example-utility-one
                      payment_networks:
                      - id: 1
                        name: Example Network
                        slug: example-network
                        barcodes_enabled: true
                      billing_accounts:
                      - id: 22
                        account_slug: example-billing-account
                        network_identifier: EXAMPLE_NET_001
                        payment_network_id: 1
                        landing_page_url: https://api.example.com/barcode_requests/new/billing_accounts/example-billing-account
                    - id: 19
                      name: Example Utility Two
                      external_biller_id: EXAMPLE_BILLER_002
                      name_slug: example-utility-two
                      payment_networks:
                      - id: 2
                        name: Example Network Two
                        slug: example-network-two
                        barcodes_enabled: true
                      billing_accounts:
                      - id: 23
                        account_slug: example-billing-account-two
                        network_identifier: EXAMPLE_NET_002
                        payment_network_id: 2
                        landing_page_url: https://api.example.com/barcode_requests/new/billing_accounts/example-billing-account-two
                    meta:
                      page: 1
                      per_page: 50
                      next_page:
  "/api/v1/barcode_registrations":
    get:
      tags:
      - Barcodes
      summary: List barcode registrations
      description: |
        Lists barcode registrations visible to the authenticated API key.

        Scope behavior:
        - biller-scoped API keys automatically return barcode registrations for accessible billing accounts on that biller
        - aggregator-scoped API keys return barcode registrations across all accessible billing accounts by default
        - aggregator-scoped API keys may provide `biller_id` to narrow results to one biller

        Use `account_id` to look up existing barcode registrations assigned to a specific account.
      parameters:
      - in: query
        name: biller_id
        schema:
          type: integer
        description: Optional for biller-scoped API keys. Aggregator-scoped API keys
          may provide this to narrow results to one biller.
        examples:
          aggregatorScopedKey:
            summary: Aggregator-scoped request narrowed to one biller
            value: 14
      - in: query
        name: billing_account_id
        schema:
          type: integer
        description: Optional filter for one billing account.
      - in: query
        name: account_id
        schema:
          type: string
        description: Optional filter for one account id.
      - in: query
        name: barcode_number
        schema:
          type: string
        description: Optional filter for one barcode number.
      - in: query
        name: status
        schema:
          type: string
          enum:
          - active
          - suspended
          - expired
          - revoked
        description: Optional barcode registration status filter.
      - in: query
        name: page
        schema:
          type: integer
          minimum: 1
          default: 1
      - in: query
        name: per_page
        schema:
          type: integer
          minimum: 1
          maximum: 200
          default: 50
      responses:
        '200':
          description: Barcode registration list
          content:
            application/json:
              schema:
                type: object
                properties:
                  barcode_registrations:
                    type: array
                    items:
                      "$ref": "#/components/schemas/BarcodeRegistrationRecord"
                  meta:
                    "$ref": "#/components/schemas/PaginationMeta"
              examples:
                billerScopedKey:
                  summary: Biller-scoped key listing barcode registrations for its
                    own biller
                  value:
                    barcode_registrations:
                    - id: 4101
                      barcode_number: '555551111122222333334444455555'
                      status: active
                      account_id: ACC500
                      biller_id: 14
                      billing_account_id: 22
                      billing_account_slug: example-billing-account
                      payment_network_id: 1
                      barcode_image_url:
                      allocated_at: '2026-05-08T15:30:00Z'
                      suspended_at:
                      suspension_reason:
                      expires_at:
                      created_at: '2026-05-08T15:30:00Z'
                      updated_at: '2026-05-08T15:30:00Z'
                    meta:
                      page: 1
                      per_page: 50
                      next_page:
                aggregatorScopedKey:
                  summary: Aggregator-scoped key listing barcode registrations across
                    accessible billers
                  value:
                    barcode_registrations:
                    - id: 4201
                      barcode_number: '666661111122222333334444466666'
                      status: active
                      account_id: ACC600
                      biller_id: 14
                      billing_account_id: 22
                      billing_account_slug: example-billing-account
                      payment_network_id: 1
                      barcode_image_url:
                      allocated_at: '2026-05-08T16:00:00Z'
                      suspended_at:
                      suspension_reason:
                      expires_at:
                      created_at: '2026-05-08T16:00:00Z'
                      updated_at: '2026-05-08T16:00:00Z'
                    - id: 4200
                      barcode_number: '777771111122222333334444477777'
                      status: suspended
                      account_id: ACC700
                      biller_id: 19
                      billing_account_id: 23
                      billing_account_slug: example-billing-account-two
                      payment_network_id: 2
                      barcode_image_url:
                      allocated_at: '2026-05-08T14:00:00Z'
                      suspended_at: '2026-05-08T17:00:00Z'
                      suspension_reason: review
                      expires_at:
                      created_at: '2026-05-08T14:00:00Z'
                      updated_at: '2026-05-08T17:00:00Z'
                    meta:
                      page: 1
                      per_page: 50
                      next_page: 2
        '401':
          description: Missing or invalid token or missing required scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '403':
          description: API key is not allowed to access the requested biller scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
    post:
      tags:
      - Barcodes
      summary: Create or reuse a barcode registration for an account
      description: |
        Creates or reuses an active barcode registration for an existing account on one billing account.

        Scope behavior:
        - biller-scoped API keys may use any accessible billing account on that biller
        - aggregator-scoped API keys may use any accessible billing account across their authorized billers

        This endpoint returns the barcode registration payload directly, including `barcode_image_url`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/BarcodeRegistrationCreateRequest"
            examples:
              billerScopedKey:
                summary: Biller-scoped key creating a barcode registration
                value:
                  billing_account_id: 22
                  account_id: ACC300
              aggregatorScopedKey:
                summary: Aggregator-scoped key creating a barcode registration on
                  an accessible billing account
                value:
                  billing_account_id: 23
                  account_id: ACC301
      responses:
        '200':
          description: Barcode registration created or reused
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum:
                    - created
                    - existing
                  barcode_registration:
                    "$ref": "#/components/schemas/BarcodeRegistrationRecord"
              examples:
                created:
                  summary: New barcode registration created
                  value:
                    status: created
                    barcode_registration:
                      id: 5101
                      barcode_number: '999991111122222333334444499999'
                      status: active
                      account_id: ACC300
                      biller_id: 14
                      billing_account_id: 22
                      billing_account_slug: example-billing-account
                      payment_network_id: 1
                      barcode_image_url: https://cdn.example.com/barcodes/999991111122222333334444499999.png
                      allocated_at: '2026-05-08T18:00:00Z'
                      suspended_at:
                      suspension_reason:
                      expires_at:
                      created_at: '2026-05-08T18:00:00Z'
                      updated_at: '2026-05-08T18:00:00Z'
                existing:
                  summary: Existing active barcode registration reused
                  value:
                    status: existing
                    barcode_registration:
                      id: 5100
                      barcode_number: '999981111122222333334444499998'
                      status: active
                      account_id: ACC301
                      biller_id: 14
                      billing_account_id: 22
                      billing_account_slug: example-billing-account
                      payment_network_id: 1
                      barcode_image_url: https://cdn.example.com/barcodes/999981111122222333334444499998.png
                      allocated_at: '2026-05-08T17:30:00Z'
                      suspended_at:
                      suspension_reason:
                      expires_at:
                      created_at: '2026-05-08T17:30:00Z'
                      updated_at: '2026-05-08T17:30:00Z'
        '401':
          description: Missing or invalid token or missing required scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '403':
          description: API key is not allowed to access the requested billing account
            scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '404':
          description: Billing account or account not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '422':
          description: Barcode registration could not be created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
  "/api/v1/barcode_registrations/manifest":
    post:
      tags:
      - Barcodes
      summary: Create or reuse barcode registrations by manifest
      description: |
        Processes an inline manifest of barcode registration requests.

        Inline processing thresholds:
        - Synchronous processing is allowed only for small single-billing-account requests.
        - The server automatically queues the manifest when any of these are true:
          - the request spans multiple billing accounts
          - more than `250` account ids are provided
          - the raw request body exceeds `256 KB`
        - The server rejects inline barcode manifests and requires the upload endpoint when either of these hard limits is exceeded:
          - more than `2,000` account ids
          - raw request body larger than `5 MB`

        Biller-scoped keys may provide one top-level `billing_account_id`. Aggregator-scoped keys may either provide a top-level `billing_account_id` for a single-billing-account manifest or supply `billing_account_id` on each account entry to target multiple billing accounts.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                billing_account_id:
                  type: integer
                  description: Optional default billing account for all rows. Aggregator-scoped
                    multi-billing-account manifests must provide `billing_account_id`
                    on each row instead.
                accounts:
                  type: array
                  items:
                    "$ref": "#/components/schemas/BarcodeManifestAccountInput"
            examples:
              billerScopedManifest:
                summary: Biller-scoped manifest for one billing account
                value:
                  billing_account_id: 22
                  accounts:
                  - account_id: ACC400
                  - account_id: ACC401
              aggregatorScopedMultiBillingAccount:
                summary: Aggregator-scoped manifest spanning multiple billing accounts
                value:
                  accounts:
                  - billing_account_id: 22
                    account_id: ACC500
                  - billing_account_id: 23
                    account_id: ACC501
      responses:
        '200':
          description: Barcode manifest processed synchronously
          content:
            application/json:
              schema:
                type: object
                properties:
                  manifest_id:
                    type: string
                  status:
                    type: string
                    enum:
                    - completed
                    - partial
                  summary:
                    type: object
                    properties:
                      total:
                        type: integer
                      created:
                        type: integer
                      reused:
                        type: integer
                      failed:
                        type: integer
                  barcode_registrations:
                    type: array
                    items:
                      "$ref": "#/components/schemas/BarcodeRegistrationRecord"
                  errors:
                    type: array
                    items:
                      type: object
              examples:
                completed:
                  summary: Synchronous barcode manifest response
                  value:
                    manifest_id: 11111111-1111-4111-8111-111111111111
                    status: completed
                    summary:
                      total: 2
                      created: 2
                      reused: 0
                      failed: 0
                    barcode_registrations:
                    - id: 6101
                      barcode_number: '111111111122222333334444455555'
                      status: active
                      account_id: ACC400
                      biller_id: 14
                      billing_account_id: 22
                      billing_account_slug: example-billing-account
                      payment_network_id: 1
                      barcode_image_url: https://cdn.example.com/barcodes/111111111122222333334444455555.png
                      allocated_at: '2026-05-08T18:30:00Z'
                      suspended_at:
                      suspension_reason:
                      expires_at:
                      created_at: '2026-05-08T18:30:00Z'
                      updated_at: '2026-05-08T18:30:00Z'
                    errors: []
        '202':
          description: Barcode manifest queued automatically for asynchronous processing
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/QueuedBarcodeManifestResponse"
              examples:
                queued:
                  summary: Large or multi-billing-account barcode manifest queued
                    automatically
                  value:
                    manifest_id: 22222222-2222-4222-8222-222222222222
                    job_id: 33333333-3333-4333-8333-333333333333
                    status: queued
                    async_reason: multi_billing_account_manifest
        '401':
          description: Missing or invalid token or missing required scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '403':
          description: API key is not allowed to access the requested billing account
            scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '413':
          description: Inline barcode manifest exceeds hard limits and must use upload-based
            processing
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/BarcodeManifestUploadRequiredResponse"
  "/api/v1/barcode_registrations/manifest_uploads":
    post:
      tags:
      - Barcodes
      summary: Upload a large barcode manifest for background processing
      description: |
        Uploads a newline-delimited JSON manifest (`.jsonl` or `.ndjson`) for asynchronous barcode registration processing.

        Accepted file format:
        - file extension must be `.jsonl` or `.ndjson`
        - each line must be one JSON object
        - each JSON object must include `account_id`
        - each JSON object may include `billing_account_id`

        Line structures:
        - single-billing-account line:

          `{"account_id":"ACC100"}`

        - multi-billing-account aggregator line:

          `{"billing_account_id":22,"account_id":"ACC100"}`

        Biller-scoped API keys may provide a top-level `billing_account_id`. Aggregator-scoped keys may provide a top-level `billing_account_id` for single-billing-account uploads or per-line `billing_account_id` for multi-billing-account uploads.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: JSONL or NDJSON file. Each line must match the `BarcodeManifestUploadLine`
                    structure described in the schema section.
                billing_account_id:
                  type: integer
                  description: Optional default billing account for all lines.
            examples:
              billerScopedUpload:
                summary: Biller-scoped upload file
                value:
                  billing_account_id: 22
                  file: |
                    {"account_id":"ACC600"}
                    {"account_id":"ACC601"}
              aggregatorScopedMultiBillingAccount:
                summary: Aggregator-scoped upload spanning multiple billing accounts
                value:
                  file: |
                    {"billing_account_id":22,"account_id":"ACC700"}
                    {"billing_account_id":23,"account_id":"ACC701"}
      responses:
        '202':
          description: Upload accepted and queued for processing
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/BarcodeManifestUploadAcceptedResponse"
              examples:
                uploadAccepted:
                  summary: Upload accepted
                  value:
                    manifest_id: 44444444-4444-4444-8444-444444444444
                    job_id: 55555555-5555-4555-8555-555555555555
                    status: queued
                    status_url: https://api.example.com/api/v1/barcode_registrations/manifest_uploads/44444444-4444-4444-8444-444444444444
        '413':
          description: Uploaded file exceeds the maximum allowed size
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  filename:
                    type: string
                  max_bytes:
                    type: integer
  "/api/v1/barcode_registrations/manifest_uploads/{manifest_id}":
    get:
      tags:
      - Barcodes
      summary: Check status for an uploaded or auto-queued barcode manifest run
      parameters:
      - in: path
        name: manifest_id
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Barcode manifest run status
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/BarcodeManifestRunStatus"
              examples:
                completed:
                  summary: Completed uploaded barcode manifest
                  value:
                    manifest_id: 44444444-4444-4444-8444-444444444444
                    status: completed
                    source_type: upload
                    file_format: jsonl
                    async_reason: uploaded_manifest
                    default_billing_account_id: 22
                    requested_count: 2
                    billing_account_count: 1
                    summary:
                      total: 2
                      created: 1
                      reused: 1
                      failed: 0
                    errors: []
                    error_message:
                    started_at: '2026-05-08T18:00:00Z'
                    finished_at: '2026-05-08T18:00:02Z'
                    job_id: 55555555-5555-4555-8555-555555555555
  "/api/v1/accounts":
    get:
      tags:
      - Accounts
      summary: List accounts
      description: Returned account `metadata` is rehydrated for authorized clients.
        Keys containing `name` or `address` are stored encrypted at rest but are included
        in the response payload.
      parameters:
      - in: query
        name: biller_id
        schema:
          type: integer
        description: Optional for biller-scoped API keys; required for aggregator-scoped
          keys
        examples:
          aggregatorScopedKey:
            summary: Aggregator-scoped request must include the target biller id
            value: 14
      - in: query
        name: active
        schema:
          type: boolean
      - in: query
        name: query
        schema:
          type: string
      - in: query
        name: page
        schema:
          type: integer
      - in: query
        name: per_page
        schema:
          type: integer
          maximum: 200
      responses:
        '200':
          description: Account list
          content:
            application/json:
              schema:
                type: object
                properties:
                  accounts:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Account"
                  meta:
                    "$ref": "#/components/schemas/PaginationMeta"
              examples:
                billerScopedKey:
                  summary: Biller-scoped key listing accounts for its own biller
                  description: The returned `metadata` includes both operational keys
                    and decrypted sensitive keys such as names and addresses.
                  value:
                    accounts:
                    - account_id: ACC100
                      active: true
                      metadata:
                        name: Example Resident
                        MailingAddress:
                          line1: 100 Main St
                          city: Dothan
                      disabled_at:
                      disable_reason:
                    - account_id: ACC101
                      active: false
                      metadata:
                        name: Example Resident Two
                      disabled_at: '2026-05-08T14:30:00Z'
                      disable_reason: customer_request
                    meta:
                      page: 1
                      per_page: 50
                      next_page:
                aggregatorScopedKey:
                  summary: Aggregator-scoped key listing accounts for an explicit
                    `biller_id`
                  description: Aggregator-scoped responses also include decrypted
                    name/address keys in `metadata` when the requester is authorized
                    for that biller.
                  value:
                    accounts:
                    - account_id: ACC200
                      active: true
                      metadata:
                        service_address: 500 River Rd
                      disabled_at:
                      disable_reason:
                    - account_id: ACC201
                      active: true
                      metadata:
                        recipient_name: Example Recipient
                      disabled_at:
                      disable_reason:
                    meta:
                      page: 1
                      per_page: 50
                      next_page: 2
    post:
      tags:
      - Accounts
      summary: Create or update a single account
      description: Creates or updates a single account. Metadata keys containing `name`
        or `address` are stored encrypted at rest and returned in the response payload
        for authorized clients.
      requestBody:
        required: true
        description: Provide either an account object or top-level account_id/metadata.
          biller_id is optional for biller-scoped keys and required for aggregator-scoped
          keys. Sensitive metadata is stored encrypted at rest and rehydrated in account
          API responses.
        content:
          application/json:
            schema:
              type: object
              properties:
                biller_id:
                  type: integer
                  description: Optional for biller-scoped keys; required for aggregator-scoped
                    keys
                account:
                  "$ref": "#/components/schemas/AccountInput"
                account_id:
                  type: string
                  description: Optional alternative to account object
                metadata:
                  type: object
                  description: Optional alternative to account object
            examples:
              billerScopedKey:
                summary: Biller-scoped key can omit `biller_id`
                value:
                  account_id: ACC300
                  metadata:
                    name: Example Resident
                    MailingAddress:
                      line1: 200 Oak St
              aggregatorScopedKey:
                summary: Aggregator-scoped key must provide `biller_id`
                value:
                  biller_id: 14
                  account:
                    account_id: ACC301
                    metadata:
                      recipient_name: Example Recipient
                      service_address: 900 Cedar Ave
      responses:
        '200':
          description: Account created or updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum:
                    - created
                    - updated
                  account:
                    "$ref": "#/components/schemas/Account"
              examples:
                billerScopedKey:
                  summary: Biller-scoped create response includes sensitive metadata
                  value:
                    status: created
                    account:
                      account_id: ACC300
                      active: true
                      metadata:
                        name: Example Resident
                        MailingAddress:
                          line1: 200 Oak St
                      disabled_at:
                      disable_reason:
                aggregatorScopedKey:
                  summary: Aggregator-scoped create response includes sensitive metadata
                  value:
                    status: created
                    account:
                      account_id: ACC301
                      active: true
                      metadata:
                        recipient_name: Example Recipient
                        service_address: 900 Cedar Ave
                      disabled_at:
                      disable_reason:
  "/api/v1/accounts/manifest":
    post:
      tags:
      - Accounts
      summary: Upsert or diff accounts by manifest
      description: |
        Processes an inline manifest of accounts.

        Inline processing thresholds:
        - Synchronous processing is allowed only for small biller-scoped `upsert` manifests.
        - The server automatically queues the manifest when any of these are true:
          - `mode=diff`
          - the manifest spans multiple billers
          - more than `250` accounts are provided
          - the raw request body exceeds `256 KB`
        - The server rejects inline manifests and requires the upload endpoint when either of these hard limits is exceeded:
          - more than `2,000` accounts
          - raw request body larger than `5 MB`

        Biller-scoped keys may omit `biller_id`. Aggregator-scoped keys may either provide a top-level `biller_id` for a single-biller manifest or supply `biller_id` on each account to post one manifest across multiple billers.
      requestBody:
        required: true
        description: mode=upsert adds/updates accounts; mode=diff also disables accounts
          missing from the manifest. Sensitive metadata is stored encrypted at rest
          and rehydrated in subsequent account API responses.
        content:
          application/json:
            schema:
              type: object
              properties:
                biller_id:
                  type: integer
                  description: Optional for biller-scoped keys. For aggregator-scoped
                    keys, use this when all accounts in the manifest belong to one
                    biller.
                mode:
                  type: string
                  enum:
                  - upsert
                  - diff
                  description: upsert adds/updates accounts; diff also disables accounts
                    missing from the manifest
                  default: upsert
                accounts:
                  type: array
                  items:
                    "$ref": "#/components/schemas/ManifestAccountInput"
            examples:
              billerScopedUpsert:
                summary: Biller-scoped key can omit `biller_id`
                value:
                  mode: upsert
                  accounts:
                  - account_id: ACC100
                    metadata:
                      name: Example Resident
                  - account_id: ACC200
                    metadata:
                      MailingAddress:
                        line1: 10 Maple Ave
              aggregatorScopedSingleBiller:
                summary: Aggregator-scoped key targeting one biller with top-level
                  `biller_id`
                value:
                  biller_id: 14
                  mode: upsert
                  accounts:
                  - account_id: ACC300
                    metadata:
                      recipient_name: Example Recipient
                  - account_id: ACC301
                    metadata:
                      service_address: 900 Cedar Ave
              aggregatorScopedMultiBiller:
                summary: Aggregator-scoped key posting one manifest across multiple
                  billers
                value:
                  mode: upsert
                  accounts:
                  - biller_id: 14
                    account_id: ACC400
                    metadata:
                      recipient_name: Example Recipient One
                  - biller_id: 19
                    account_id: ACC401
                    metadata:
                      service_address: 120 Pine Rd
              billerScopedDiff:
                summary: Diff mode disables missing accounts within the targeted biller
                  scope
                value:
                  mode: diff
                  accounts:
                  - account_id: ACC100
                    metadata:
                      name: Example Resident
      responses:
        '200':
          description: Manifest result
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ManifestResponse"
              examples:
                completed:
                  summary: Completed multi-biller manifest
                  value:
                    manifest_id: 11111111-1111-4111-8111-111111111111
                    status: completed
                    summary:
                      total: 2
                      created: 2
                      updated: 0
                      disabled: 0
                      failed: 0
                    errors: []
        '207':
          description: Partial manifest result
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ManifestResponse"
        '202':
          description: Manifest queued automatically for asynchronous processing
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/QueuedManifestResponse"
              examples:
                queued:
                  summary: Large or multi-biller manifest queued automatically
                  value:
                    manifest_id: 22222222-2222-4222-8222-222222222222
                    job_id: 33333333-3333-4333-8333-333333333333
                    status: queued
                    async_reason: multi_biller_manifest
        '413':
          description: Inline manifest exceeds hard limits and must use upload-based
            processing
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ManifestUploadRequiredResponse"
              examples:
                tooLarge:
                  summary: Inline manifest exceeds hard limits
                  value:
                    error: Manifest payload exceeds inline processing limits
                    upload_endpoint: "/api/v1/accounts/manifest_uploads"
                    max_accounts: 2000
                    max_bytes: 5242880
  "/api/v1/accounts/manifest_uploads":
    post:
      tags:
      - Accounts
      summary: Upload a large account manifest for background processing
      description: |
        Uploads a newline-delimited JSON manifest (`.jsonl` or `.ndjson`) for asynchronous processing.

        Accepted file format:
        - file extension must be `.jsonl` or `.ndjson`
        - each line must be one JSON object
        - each JSON object must follow this structure:

          `{"account_id":"ACC100","metadata":{...}}`

        - for aggregator multi-biller uploads, each line must follow this structure:

          `{"biller_id":14,"account_id":"ACC100","metadata":{...}}`

        Allowed keys per line:
        - `account_id` (required)
        - `metadata` (optional object)
        - `biller_id` (optional for biller-scoped uploads, required per line for aggregator multi-biller uploads)

        Biller targeting rules:
        - biller-scoped API keys may omit `biller_id` in every row
        - aggregator-scoped API keys may provide a top-level `biller_id` for single-biller uploads
        - aggregator-scoped API keys must provide `biller_id` on each row for multi-biller uploads

        Use this endpoint for manifests that exceed the inline `/api/v1/accounts/manifest` limits.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: JSONL or NDJSON file. Each line must match the `ManifestUploadLine`
                    data structure described in the schema section.
                mode:
                  type: string
                  enum:
                  - upsert
                  - diff
                biller_id:
                  type: integer
                  description: Optional default biller for all rows. Biller-scoped
                    keys may omit this. Aggregator-scoped keys may provide this for
                    single-biller uploads.
            examples:
              billerScopedUpload:
                summary: Biller-scoped upload file
                description: Biller-scoped keys can omit `biller_id` entirely and
                  submit one JSON object per line.
                value:
                  mode: upsert
                  file: |
                    {"account_id":"ACC100","metadata":{"name":"Example Resident"}}
                    {"account_id":"ACC101","metadata":{"MailingAddress":{"line1":"10 Maple Ave"}}}
              aggregatorScopedSingleBillerUpload:
                summary: Aggregator-scoped upload for one biller
                description: Aggregator-scoped keys can supply a top-level `biller_id`
                  when all rows belong to one biller.
                value:
                  mode: upsert
                  biller_id: 14
                  file: |
                    {"account_id":"ACC300","metadata":{"recipient_name":"Example Recipient"}}
                    {"account_id":"ACC301","metadata":{"service_address":"900 Cedar Ave"}}
              aggregatorScopedMultiBillerUpload:
                summary: Aggregator-scoped upload spanning multiple billers
                description: Aggregator-scoped keys must supply `biller_id` on each
                  line when one file spans multiple billers.
                value:
                  mode: upsert
                  file: |
                    {"biller_id":14,"account_id":"ACC400","metadata":{"recipient_name":"Example Recipient One"}}
                    {"biller_id":19,"account_id":"ACC401","metadata":{"service_address":"120 Pine Rd"}}
              diffUpload:
                summary: Diff upload file
                description: Diff mode disables missing accounts within the targeted
                  biller scope represented by the upload.
                value:
                  mode: diff
                  file: |
                    {"account_id":"ACC500","metadata":{"name":"Example Resident"}}
                    {"account_id":"ACC501","metadata":{"MailingAddress":{"line1":"200 Oak St"}}}
      responses:
        '202':
          description: Upload accepted and queued for processing
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ManifestUploadAcceptedResponse"
              examples:
                uploadAccepted:
                  summary: Upload accepted
                  value:
                    manifest_id: 44444444-4444-4444-8444-444444444444
                    job_id: 55555555-5555-4555-8555-555555555555
                    status: queued
                    status_url: https://api.example.com/api/v1/accounts/manifest_uploads/44444444-4444-4444-8444-444444444444
        '413':
          description: Uploaded file exceeds the maximum allowed size
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  filename:
                    type: string
                  max_bytes:
                    type: integer
  "/api/v1/accounts/manifest_uploads/{manifest_id}":
    get:
      tags:
      - Accounts
      summary: Check status for an uploaded or auto-queued manifest run
      parameters:
      - in: path
        name: manifest_id
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Manifest run status
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/AccountManifestRunStatus"
              examples:
                completed:
                  summary: Completed uploaded manifest
                  value:
                    manifest_id: 44444444-4444-4444-8444-444444444444
                    status: completed
                    mode: upsert
                    source_type: upload
                    file_format: jsonl
                    async_reason: uploaded_manifest
                    default_biller_id: 14
                    account_count: 2
                    biller_count: 1
                    summary:
                      total: 2
                      created: 1
                      updated: 1
                      disabled: 0
                      failed: 0
                    errors: []
                    error_message:
                    started_at: '2026-05-08T16:00:00Z'
                    finished_at: '2026-05-08T16:00:02Z'
                    job_id: 55555555-5555-4555-8555-555555555555
  "/api/v1/accounts/bulk_disable":
    post:
      tags:
      - Accounts
      summary: Disable accounts in bulk
      description: |
        Disables accounts inline or queues the request automatically for background processing.

        Inline processing thresholds:
        - Synchronous processing is allowed only for small single-biller requests.
        - The server automatically queues the request when any of these are true:
          - the request spans multiple billers
          - more than `250` account ids are provided
          - the raw request body exceeds `256 KB`
        - The server rejects inline bulk-disable requests and requires the upload endpoint when either of these hard limits is exceeded:
          - more than `2,000` account ids
          - raw request body larger than `5 MB`

        Biller-scoped keys may omit `biller_id`. Aggregator-scoped keys may either provide a top-level `biller_id` for a single-biller request or supply `biller_id` on each account entry to disable accounts across multiple billers.
      requestBody:
        required: true
        description: Use `account_ids` for single-biller requests. Use `accounts`
          with per-entry `biller_id` for multi-biller aggregator requests.
        content:
          application/json:
            schema:
              type: object
              properties:
                biller_id:
                  type: integer
                  description: Optional for biller-scoped keys. For aggregator-scoped
                    keys, use this when all account ids belong to one biller.
                account_ids:
                  type: array
                  items:
                    type: string
                accounts:
                  type: array
                  items:
                    "$ref": "#/components/schemas/BulkDisableAccountInput"
                reason:
                  type: string
            examples:
              billerScopedInline:
                summary: Biller-scoped key disabling accounts for its own biller
                value:
                  account_ids:
                  - ACC100
                  - ACC200
                  - ACC999
                  reason: cleanup
              aggregatorScopedSingleBiller:
                summary: Aggregator-scoped key targeting one biller with top-level
                  `biller_id`
                value:
                  biller_id: 14
                  account_ids:
                  - ACC300
                  - ACC301
                  - ACC399
                  reason: customer_request
              aggregatorScopedMultiBiller:
                summary: Aggregator-scoped key disabling accounts across multiple
                  billers
                value:
                  accounts:
                  - biller_id: 14
                    account_id: ACC400
                  - biller_id: 19
                    account_id: ACC401
                  reason: compliance_hold
      responses:
        '200':
          description: Bulk disable result
          content:
            application/json:
              schema:
                type: object
                properties:
                  requested:
                    type: integer
                  disabled:
                    type: integer
                  missing:
                    type: array
                    items:
                      type: string
              examples:
                completed:
                  summary: Completed inline bulk disable
                  value:
                    requested: 3
                    disabled: 2
                    missing:
                    - ACC999
        '202':
          description: Bulk disable queued automatically for asynchronous processing
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/QueuedBulkDisableResponse"
              examples:
                queued:
                  summary: Multi-biller bulk disable queued automatically
                  value:
                    run_id: 66666666-6666-4666-8666-666666666666
                    job_id: 77777777-7777-4777-8777-777777777777
                    status: queued
                    async_reason: multi_biller_request
        '413':
          description: Inline bulk disable exceeds hard limits and must use upload-based
            processing
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/BulkDisableUploadRequiredResponse"
              examples:
                tooLarge:
                  summary: Inline bulk disable exceeds hard limits
                  value:
                    error: Bulk disable payload exceeds inline processing limits
                    upload_endpoint: "/api/v1/accounts/bulk_disable_uploads"
                    max_accounts: 2000
                    max_bytes: 5242880
  "/api/v1/accounts/bulk_disable_uploads":
    post:
      tags:
      - Accounts
      summary: Upload a large bulk-disable request for background processing
      description: |
        Uploads a newline-delimited JSON file (`.jsonl` or `.ndjson`) for bulk account disable processing.

        Accepted file format:
        - file extension must be `.jsonl` or `.ndjson`
        - each line must be one JSON object
        - each JSON object must include `account_id`
        - each JSON object may include `biller_id`

        Line structures:
        - single-biller line:

          `{"account_id":"ACC100"}`

        - multi-biller aggregator line:

          `{"biller_id":14,"account_id":"ACC100"}`

        Biller targeting rules:
        - biller-scoped API keys may omit `biller_id` in every line
        - aggregator-scoped API keys may provide a top-level `biller_id` for single-biller uploads
        - aggregator-scoped API keys must provide `biller_id` on each line for multi-biller uploads
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: JSONL or NDJSON file. Each line must match the `BulkDisableUploadLine`
                    data structure described in the schema section.
                biller_id:
                  type: integer
                  description: Optional default biller for all lines. Biller-scoped
                    keys may omit this. Aggregator-scoped keys may provide this for
                    single-biller uploads.
                reason:
                  type: string
            examples:
              billerScopedUpload:
                summary: Biller-scoped upload file
                description: Biller-scoped keys can omit `biller_id` entirely and
                  submit one JSON object per line.
                value:
                  reason: cleanup
                  file: |
                    {"account_id":"ACC100"}
                    {"account_id":"ACC101"}
              aggregatorScopedSingleBillerUpload:
                summary: Aggregator-scoped upload for one biller
                description: Aggregator-scoped keys can supply a top-level `biller_id`
                  when all lines belong to one biller.
                value:
                  biller_id: 14
                  reason: customer_request
                  file: |
                    {"account_id":"ACC300"}
                    {"account_id":"ACC301"}
              aggregatorScopedMultiBillerUpload:
                summary: Aggregator-scoped upload spanning multiple billers
                description: Aggregator-scoped keys must supply `biller_id` on each
                  line when one file spans multiple billers.
                value:
                  reason: compliance_hold
                  file: |
                    {"biller_id":14,"account_id":"ACC400"}
                    {"biller_id":19,"account_id":"ACC401"}
      responses:
        '202':
          description: Upload accepted and queued for processing
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/BulkDisableUploadAcceptedResponse"
              examples:
                uploadAccepted:
                  summary: Upload accepted
                  value:
                    run_id: 88888888-8888-4888-8888-888888888888
                    job_id: 99999999-9999-4999-8999-999999999999
                    status: queued
                    status_url: https://api.example.com/api/v1/accounts/bulk_disable_uploads/88888888-8888-4888-8888-888888888888
        '413':
          description: Uploaded file exceeds the maximum allowed size
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  filename:
                    type: string
                  max_bytes:
                    type: integer
  "/api/v1/accounts/bulk_disable_uploads/{run_id}":
    get:
      tags:
      - Accounts
      summary: Check status for an uploaded or auto-queued bulk disable run
      parameters:
      - in: path
        name: run_id
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Bulk disable run status
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/AccountBulkDisableRunStatus"
              examples:
                completed:
                  summary: Completed uploaded bulk disable
                  value:
                    run_id: 88888888-8888-4888-8888-888888888888
                    status: completed
                    source_type: upload
                    file_format: jsonl
                    async_reason: uploaded_bulk_disable
                    default_biller_id: 14
                    reason: cleanup
                    requested_count: 2
                    biller_count: 1
                    summary:
                      requested: 2
                      disabled: 1
                      missing: 1
                      failed: 0
                    errors: []
                    error_message:
                    started_at: '2026-05-08T16:00:00Z'
                    finished_at: '2026-05-08T16:00:02Z'
                    job_id: 99999999-9999-4999-8999-999999999999
  "/api/v1/accounts/{account_id}":
    get:
      tags:
      - Accounts
      summary: Lookup a single account
      description: Looks up a single account and returns the full metadata payload
        for authorized clients, including sensitive name/address keys that are stored
        encrypted at rest. Biller-scoped keys may omit `biller_id`. Aggregator-scoped
        keys must supply the target `biller_id`.
      parameters:
      - in: path
        name: account_id
        required: true
        schema:
          type: string
      - in: query
        name: biller_id
        schema:
          type: integer
        description: Optional for biller-scoped API keys; required for aggregator-scoped
          keys
        examples:
          aggregatorScopedKey:
            summary: Aggregator-scoped request must include the target biller id
            value: 14
      responses:
        '200':
          description: Account lookup
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Account"
              examples:
                billerScopedKey:
                  summary: Biller-scoped key looking up an account for its own biller
                  value:
                    account_id: ACC500
                    active: true
                    metadata:
                      name: Example Resident
                      MailingAddress:
                        line1: 100 Main St
                    disabled_at:
                    disable_reason:
                aggregatorScopedKey:
                  summary: Aggregator-scoped key looking up an account with explicit
                    `biller_id`
                  value:
                    account_id: ACC501
                    active: true
                    metadata:
                      recipient_name: Example Recipient
                      service_address: 500 River Rd
                    disabled_at:
                    disable_reason:
  "/api/v1/accounts/{account_id}/disable":
    post:
      tags:
      - Accounts
      summary: Disable an account
      description: Disables a single account immediately and returns the updated account
        payload. Biller-scoped keys may omit `biller_id`. Aggregator-scoped keys must
        supply the target `biller_id`.
      parameters:
      - in: path
        name: account_id
        required: true
        schema:
          type: string
      requestBody:
        description: biller_id is optional for biller-scoped keys and required for
          aggregator-scoped keys.
        content:
          application/json:
            schema:
              type: object
              properties:
                biller_id:
                  type: integer
                  description: Optional for biller-scoped keys; required for aggregator-scoped
                    keys
                reason:
                  type: string
            examples:
              billerScopedKey:
                summary: Biller-scoped key disabling an account for its own biller
                value:
                  reason: customer_request
              aggregatorScopedKey:
                summary: Aggregator-scoped key disabling an account with explicit
                  `biller_id`
                value:
                  biller_id: 14
                  reason: aggregator_request
      responses:
        '200':
          description: Account disabled
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Account"
              examples:
                billerScopedKey:
                  summary: Biller-scoped disable response
                  value:
                    account_id: ACC600
                    active: false
                    metadata:
                      name: Example Resident
                    disabled_at: '2026-05-08T17:00:00Z'
                    disable_reason: customer_request
                aggregatorScopedKey:
                  summary: Aggregator-scoped disable response
                  value:
                    account_id: ACC601
                    active: false
                    metadata:
                      recipient_name: Example Recipient
                    disabled_at: '2026-05-08T17:00:00Z'
                    disable_reason: aggregator_request
  "/api/v1/biller_participation_requests":
    post:
      tags:
      - Biller Participation Forms
      security:
      - bearerAuth: []
      summary: Submit a biller participation request
      description: |
        Creates a participation intake request and captures requested signer and banking details.

        Envelope dispatch behavior:
        - Envelope sending is queued asynchronously after request creation.
        - PayItToday prepopulates and maps signer/template data before provider dispatch.
        - Signer invitation emails are sent by the signature provider (for example DocuSign), not by PayItToday.
        - Initial `signing.status` can be `not_started` until dispatch completes.
        - You can poll request status via `GET /api/v1/biller_participation_requests/{id}`.
        - You can poll hosted status events via `GET /api/v1/biller_participation_requests/{id}/events`.

        Auth and scope:
        - `Authorization: Bearer <api_key_token>`
        - API key must include either `biller_participation` or any account-management scope (`account.read`, `account.write`, `account.manifest`, `account.bulk_disable`).

        Event delivery:
        - Request responses include `event_stream.polling` and `event_stream.websocket`.
        - Use `GET /api/v1/biller_participation_requests/{id}/events` for cursor-based polling.
        - Use the returned websocket subscription payload to receive real-time event updates for that request.
        - Websocket clients may reconnect with `from_cursor` to replay missed events.
        - After subscription confirmation, Action Cable data frames follow `ParticipationRealtimeEventWebsocketFrame`; the pushed `message` body matches `ParticipationRealtimeEvent`.

        Flexible metadata:
        - `meta` accepts an arbitrary JSON object.
        - Use it for forward-compatible fields and biller-specific supplemental context.
        - `meta` is echoed in request responses and participation event payloads.
        - Expected `meta` keys per aggregator can be discovered via `GET /api/v1/aggregator_profiles/expected_meta`.
        - Required document keys per aggregator can be discovered via `GET /api/v1/aggregator_profiles/document_requirements`.

        Address and banking usage:
        - `address` and `owners[].address` are accepted as structured intake payload data.
        - `banking` builds the internal participation banking profile (contact and financial institution details).
        - Address and banking fields are stored for workflow processing and are not currently echoed in response bodies.

        Participation request routing and template/signer behavior are configured server-side.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ParticipationRequestCreate"
            examples:
              full_intake:
                summary: Full intake payload with address, meta, and banking
                value:
                  legal_name: Acme Services LLC
                  entity_type: LLC
                  state_of_incorporation: TX
                  date_of_incorporation: '2020-05-11'
                  public_or_private: private
                  federal_ein: 12-3456789
                  url: https://acme.example
                  meta:
                    source_campaign: q2-onboarding
                    external_org_id: org_12345
                    underwriting_tier: fast_track
                    sein: 12-3456789
                    trade_names_dba: Acme DBA
                    previous_names: Old Acme Corp
                    program_code: BILLER-001
                  address:
                    address_line_1: 123 Main St
                    city: Austin
                    state: TX
                    zip_code: '78701'
                  owners:
                  - first_name: Jane
                    last_name: Doe
                    date_of_birth: '1985-08-22'
                    email: jane.owner@acme.example
                    role: owner
                    address:
                      address_line_1: 123 Main St
                      city: Austin
                      state: TX
                      zip_code: '78701'
                  banking:
                    contact_name: Taylor Manager
                    contact_phone_number: "+1-512-555-1010"
                    financial_institution:
                      name: First Community Bank
                      phone_number: "+1-512-555-2000"
                      routing_number: '111000025'
                      account_number: '000123456789'
                      currency: USD
                      contact_name: Sam Banker
                      contact_phone: "+1-512-555-2121"
                      contact_email: onboarding@firstcommunity.example
      responses:
        '201':
          description: Request accepted
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ParticipationRequest"
              examples:
                accepted_request:
                  summary: Accepted participation request with polling and websocket
                    event stream
                  value:
                    request_id: 71ab6fb0-74da-4f82-8d8d-73de1536f83d
                    aggregator_id: 12
                    biller_id:
                    intake_status: submitted
                    acceptance_status: pending
                    signing_status: not_started
                    meta:
                      source_campaign: q2-onboarding
                      external_org_id: org_12345
                      underwriting_tier: fast_track
                    documents: []
                    event_stream:
                      polling:
                        url: "/api/v1/biller_participation_requests/71ab6fb0-74da-4f82-8d8d-73de1536f83d/events"
                        latest_cursor: 4421
                      websocket:
                        schema_version: '2026-05-01'
                        transport:
                          url: wss://api.example.com/cable
                          protocol: actioncable-v1-json
                          authentication:
                            type: bearer_header
                            header: Authorization
                            scheme: Bearer
                          replay:
                            cursor_parameter: from_cursor
                            delivery: at_least_once
                            ordering: per_request
                        events:
                          topic: biller_participation_requests
                        subscription:
                          channel: BillerParticipationRequestsChannel
                          identifier:
                            channel: BillerParticipationRequestsChannel
                            request_id: 71ab6fb0-74da-4f82-8d8d-73de1536f83d
                            from_cursor:
                    signing:
                      provider_key:
                      status: not_started
                      next_action_url:
                      provider_payload: {}
        '401':
          description: Missing/invalid token or missing required scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '422':
          description: Validation errors
          content:
            application/json:
              schema:
                type: object
                properties:
                  errors:
                    type: array
                    items:
                      type: string
  "/api/v1/biller_participation_requests/{id}":
    get:
      tags:
      - Biller Participation Forms
      security:
      - bearerAuth: []
      summary: Get a participation request
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
        description: External participation request id
      responses:
        '200':
          description: Request details
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ParticipationRequest"
              examples:
                requestDetails:
                  summary: Participation request details with normalized websocket
                    discovery
                  value:
                    request_id: 71ab6fb0-74da-4f82-8d8d-73de1536f83d
                    aggregator_id: 12
                    biller_id:
                    intake_status: submitted
                    acceptance_status: pending
                    signing_status: sent
                    meta:
                      source_campaign: q2-onboarding
                    documents:
                    - key: msa
                      name: Master Service Agreement
                      status: available
                      url: https://docs.example.com/71ab6fb0-74da-4f82-8d8d-73de1536f83d/msa.pdf
                      expires_at:
                    event_stream:
                      polling:
                        url: "/api/v1/biller_participation_requests/71ab6fb0-74da-4f82-8d8d-73de1536f83d/events"
                        latest_cursor: 4422
                      websocket:
                        schema_version: '2026-05-01'
                        transport:
                          url: wss://api.example.com/cable
                          protocol: actioncable-v1-json
                          authentication:
                            type: bearer_header
                            header: Authorization
                            scheme: Bearer
                          replay:
                            cursor_parameter: from_cursor
                            delivery: at_least_once
                            ordering: per_request
                        events:
                          topic: biller_participation_requests
                        subscription:
                          channel: BillerParticipationRequestsChannel
                          identifier:
                            channel: BillerParticipationRequestsChannel
                            request_id: 71ab6fb0-74da-4f82-8d8d-73de1536f83d
                            from_cursor:
                    signing:
                      provider_key: testsign
                      status: sent
                      next_action_url: https://sign.example.com/71ab6fb0-74da-4f82-8d8d-73de1536f83d
                      provider_payload: {}
        '401':
          description: Missing/invalid token or missing required scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '403':
          description: API key is not allowed to access this request
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '404':
          description: Request not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
  "/api/v1/biller_participation_requests/{id}/documents":
    get:
      tags:
      - Biller Participation Forms
      security:
      - bearerAuth: []
      summary: Get participation request documents
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
        description: External participation request id
      responses:
        '200':
          description: Document links for this request
          content:
            application/json:
              schema:
                type: object
                properties:
                  request_id:
                    type: string
                  documents:
                    type: array
                    items:
                      "$ref": "#/components/schemas/ParticipationDocument"
        '401':
          description: Missing/invalid token or missing required scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '403':
          description: API key is not allowed to access this request
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '404':
          description: Request not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
    post:
      tags:
      - Biller Participation Forms
      security:
      - bearerAuth: []
      summary: Upload an externally signed participation document
      description: |
        Accepts a signed PDF supplied by an external system and attaches it to the participation request.
        Uploaded documents satisfy configured aggregator document requirements when their `document_key` matches the profile configuration.
        These uploads are tracked separately from DocuSign-native signing events; they can be included later in completed-document package envelopes.
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
        description: External participation request id
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              "$ref": "#/components/schemas/ParticipationDocumentUploadRequest"
      responses:
        '201':
          description: Signed document uploaded
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ParticipationDocumentUploadResponse"
        '200':
          description: Idempotent retry returned the existing uploaded document
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ParticipationDocumentUploadResponse"
        '401':
          description: Missing/invalid token or missing required scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '403':
          description: API key is not allowed to access this request
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '404':
          description: Request not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '422':
          description: Invalid document key, file, content type, or metadata
          content:
            application/json:
              schema:
                type: object
                properties:
                  errors:
                    type: array
                    items:
                      type: string
  "/api/v1/biller_participation_requests/{id}/events":
    get:
      tags:
      - Biller Participation Forms
      security:
      - bearerAuth: []
      summary: Poll hosted participation status events
      description: |
        Returns participation lifecycle events for this request. Use `cursor` to fetch only newer events.
        The event payload includes `meta`, current statuses, and signing context. The same payload shape is used for websocket event delivery, with `cursor`, `request_id`, and `replayed` wrapped around it.
        Action Cable websocket data frames follow `ParticipationRealtimeEventWebsocketFrame`; the `message` field matches `ParticipationRealtimeEvent`.
      parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
        description: External participation request id
      - in: query
        name: cursor
        required: false
        schema:
          type: integer
        description: Return events with cursor greater than this value.
      - in: query
        name: limit
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 200
          default: 50
        description: Maximum number of events to return.
      responses:
        '200':
          description: Hosted event feed
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ParticipationEventFeed"
              examples:
                status_changed_event:
                  summary: Status changed event with websocket-compatible payload
                  value:
                    request_id: 71ab6fb0-74da-4f82-8d8d-73de1536f83d
                    next_cursor: 4422
                    events:
                    - cursor: 4422
                      event_id: 4f722c80-9f15-445d-90af-4e2d06a4cf18
                      event_type: status_changed
                      occurred_at: '2026-04-23T21:10:29Z'
                      changed_statuses:
                      - signing_status
                      payload:
                        event_id: 4f722c80-9f15-445d-90af-4e2d06a4cf18
                        event_type: status_changed
                        request_id: 71ab6fb0-74da-4f82-8d8d-73de1536f83d
                        trigger: status_changed
                        changed_statuses:
                        - signing_status
                        occurred_at: '2026-04-23T21:10:29Z'
                        meta:
                          source_campaign: q2-onboarding
                          external_org_id: org_12345
                        intake_status: submitted
                        acceptance_status: pending
                        signing_status: sent
                        signed_at:
                        signing:
                          provider_key: docusign
                          provider_envelope_id: env-100
                          next_action_url: https://signing.example.com/envelope/env-100
        '401':
          description: Missing/invalid token or missing required scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '403':
          description: API key is not allowed to access this request
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '404':
          description: Request not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
  "/api/v1/aggregator_profiles/expected_meta":
    get:
      tags:
      - Biller Participation Forms
      security:
      - bearerAuth: []
      summary: Discover expected meta fields for an aggregator
      description: |
        Returns the `meta` field keys that the aggregator's participation profile expects
        based on its configured `field_mappings`. API consumers should include these keys
        in the `meta: {}` payload when submitting a biller participation request.

        The aggregator is inferred from the API key's scope.
      responses:
        '200':
          description: Expected meta fields for the authenticated aggregator
          content:
            application/json:
              schema:
                type: object
                properties:
                  aggregator_id:
                    type: integer
                  aggregator_name:
                    type: string
                  expected_meta_keys:
                    type: array
                    items:
                      type: object
                      properties:
                        key:
                          type: string
                          description: Meta field name the aggregator expects in the
                            API request payload.
                        type:
                          type: string
                          enum:
                          - string
                          - array
                          - boolean
                          description: Expected data type for this meta field. Defaults
                            to string.
                    description: Sorted list of meta field objects, each with a key
                      name and expected type.
              examples:
                configured:
                  summary: Aggregator with configured field_mappings and meta_field_types
                  value:
                    aggregator_id: 1
                    aggregator_name: Example Aggregator
                    expected_meta_keys:
                    - key: external_org_id
                      type: string
                    - key: program_code
                      type: array
                    - key: sein
                      type: string
                    - key: underwriting_tier
                      type: string
                noProfile:
                  summary: Aggregator without a configured profile
                  value:
                    aggregator_id: 1
                    aggregator_name: Example Aggregator
                    expected_meta_keys: []
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '403':
          description: API key not scoped to an aggregator
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
  "/api/v1/aggregator_profiles/document_requirements":
    get:
      tags:
      - Biller Participation Forms
      security:
      - bearerAuth: []
      summary: Discover required participation document keys for an aggregator
      description: |
        Returns the document keys configured on the authenticated aggregator's participation profile.
        API consumers can use this endpoint to determine which signed documents may be uploaded to
        `POST /api/v1/biller_participation_requests/{id}/documents` and whether each document is required.

        The aggregator is inferred from the API key's scope.
      responses:
        '200':
          description: Document requirements for the authenticated aggregator
          content:
            application/json:
              schema:
                type: object
                properties:
                  aggregator_id:
                    type: integer
                  aggregator_name:
                    type: string
                  document_requirements:
                    type: array
                    items:
                      "$ref": "#/components/schemas/AggregatorDocumentRequirement"
              examples:
                configured:
                  summary: Aggregator with configured participation documents
                  value:
                    aggregator_id: 1
                    aggregator_name: Example Aggregator
                    document_requirements:
                    - key: bank_letter
                      display_name: Bank Letter Authorization
                      required: true
                      sources:
                      - external_upload
                      content_types:
                      - application/pdf
                    - key: owner_agreement
                      display_name: Owner Agreement
                      required: true
                      sources:
                      - provider
                      content_types:
                      - application/pdf
                noProfile:
                  summary: Aggregator without a configured profile
                  value:
                    aggregator_id: 1
                    aggregator_name: Example Aggregator
                    document_requirements: []
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '403':
          description: API key not scoped to an aggregator
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
  "/api/v1/realtime_transaction_notifications":
    get:
      tags:
      - Realtime Transaction Notifications
      summary: Discover websocket details for realtime transaction notifications
      description: |
        Returns the websocket transport metadata and the concrete subscription options available to the authenticated API key.

        This endpoint is discovery-only. It does not create or update any stored configuration.

        Transport notes:
        - Connect to the returned `transport.url`
        - Send `Authorization: Bearer <token>` during the websocket handshake
        - Subscribe with an Action Cable `subscribe` command whose `identifier` is the JSON-stringified `identifier` object from one selected `subscription_options` entry
        - `from_cursor` is optional and allows replay of missed payment events before live streaming begins
        - After subscription confirmation, Action Cable data frames follow `RealtimeTransactionNotificationWebsocketFrame`; the pushed `message` body matches `RealtimeTransactionNotificationMessage`

        Supported subscription scopes:
        - `portfolio`: one feed for everything the API key is authorized to see
        - `biller`: one feed for a single biller
        - `payment_network`: one feed for a single payment network across the API key's authorized billers
        - `billing_account`: one feed for a single billing account
      responses:
        '200':
          description: Realtime transaction notifications discovery payload
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/RealtimeTransactionNotificationsDiscovery"
              examples:
                aggregatorChoices:
                  summary: Aggregator-scoped key with multiple subscription choices
                  value:
                    schema_version: '2026-05-01'
                    transport:
                      url: wss://api.example.com/cable
                      protocol: actioncable-v1-json
                      authentication:
                        type: bearer_header
                        header: Authorization
                        scheme: Bearer
                      replay:
                        cursor_parameter: from_cursor
                        delivery: at_least_once
                        ordering: per_subscription_scope
                    events:
                      topic: realtime_transaction_notifications
                      event_types:
                      - payment_applied
                      - payment_voided
                      - payment_failed
                      - payment_declined
                    subscription_options:
                    - scope: portfolio
                      channel: RealtimeTransactionNotificationsPortfolioChannel
                      resource:
                        type: aggregator
                        id: 7
                      counts:
                        billers: 3
                        payment_networks: 2
                        billing_accounts: 4
                      identifier:
                        channel: RealtimeTransactionNotificationsPortfolioChannel
                        from_cursor:
                    - scope: biller
                      channel: RealtimeTransactionNotificationsBillerChannel
                      resource:
                        type: biller
                        id: 14
                        external_biller_id: EXAMPLE_BILLER_001
                        name: Example Utility One
                        slug: example-utility-one
                      counts:
                        payment_networks: 1
                      identifier:
                        channel: RealtimeTransactionNotificationsBillerChannel
                        biller_id: 14
                        external_biller_id: EXAMPLE_BILLER_001
                        from_cursor:
                    - scope: payment_network
                      channel: RealtimeTransactionNotificationsPaymentNetworkChannel
                      resource:
                        type: payment_network
                        id: 1
                        name: Example Network
                        slug: example-network
                      counts:
                        billers: 2
                        billing_accounts: 4
                      identifier:
                        channel: RealtimeTransactionNotificationsPaymentNetworkChannel
                        payment_network_id: 1
                        payment_network_slug: example-network
                        from_cursor:
                    - scope: billing_account
                      channel: RealtimeTransactionNotificationsBillingAccountChannel
                      resource:
                        type: billing_account
                        id: 22
                        account_slug: example-billing-account
                        biller_id: 14
                        external_biller_id: EXAMPLE_BILLER_001
                        payment_network_id: 1
                        payment_network_slug: example-network
                      identifier:
                        channel: RealtimeTransactionNotificationsBillingAccountChannel
                        billing_account_id: 22
                        billing_account_slug: example-billing-account
                        biller_id: 14
                        external_biller_id: EXAMPLE_BILLER_001
                        payment_network_id: 1
                        payment_network_slug: example-network
                        from_cursor:
                billerScopedKey:
                  summary: Biller-scoped key with a single biller and related subscription
                    choices
                  value:
                    schema_version: '2026-05-01'
                    transport:
                      url: wss://api.example.com/cable
                      protocol: actioncable-v1-json
                      authentication:
                        type: bearer_header
                        header: Authorization
                        scheme: Bearer
                      replay:
                        cursor_parameter: from_cursor
                        delivery: at_least_once
                        ordering: per_subscription_scope
                    events:
                      topic: realtime_transaction_notifications
                      event_types:
                      - payment_applied
                      - payment_voided
                      - payment_failed
                      - payment_declined
                    subscription_options:
                    - scope: portfolio
                      channel: RealtimeTransactionNotificationsPortfolioChannel
                      resource:
                        type: biller
                        id: 14
                      counts:
                        billers: 1
                        payment_networks: 1
                        billing_accounts: 1
                      identifier:
                        channel: RealtimeTransactionNotificationsPortfolioChannel
                        from_cursor:
                    - scope: biller
                      channel: RealtimeTransactionNotificationsBillerChannel
                      resource:
                        type: biller
                        id: 14
                        external_biller_id: EXAMPLE_BILLER_001
                        name: Example Utility One
                        slug: example-utility-one
                      counts:
                        payment_networks: 1
                      identifier:
                        channel: RealtimeTransactionNotificationsBillerChannel
                        biller_id: 14
                        external_biller_id: EXAMPLE_BILLER_001
                        from_cursor:
                    - scope: payment_network
                      channel: RealtimeTransactionNotificationsPaymentNetworkChannel
                      resource:
                        type: payment_network
                        id: 1
                        name: Example Network
                        slug: example-network
                      counts:
                        billers: 1
                        billing_accounts: 1
                      identifier:
                        channel: RealtimeTransactionNotificationsPaymentNetworkChannel
                        payment_network_id: 1
                        payment_network_slug: example-network
                        from_cursor:
                    - scope: billing_account
                      channel: RealtimeTransactionNotificationsBillingAccountChannel
                      resource:
                        type: billing_account
                        id: 22
                        account_slug: example-billing-account
                        biller_id: 14
                        external_biller_id: EXAMPLE_BILLER_001
                        payment_network_id: 1
                        payment_network_slug: example-network
                      identifier:
                        channel: RealtimeTransactionNotificationsBillingAccountChannel
                        billing_account_id: 22
                        billing_account_slug: example-billing-account
                        biller_id: 14
                        external_biller_id: EXAMPLE_BILLER_001
                        payment_network_id: 1
                        payment_network_slug: example-network
                        from_cursor:
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '403':
          description: Missing required scope or unauthorized biller/aggregator access
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
  "/api/v1/transactions":
    get:
      tags:
      - Transactions
      summary: List recent transactions
      description: |
        Lists payment transactions visible to the authenticated API key.

        Scope behavior:
        - biller-scoped API keys automatically return transactions for that biller
        - aggregator-scoped API keys return transactions across all accessible billers by default
        - aggregator-scoped API keys may provide `biller_id` to narrow the response to one biller

        Results are ordered newest first.
      parameters:
      - in: query
        name: biller_id
        schema:
          type: integer
        description: Optional for biller-scoped API keys. Aggregator-scoped API keys
          may provide this to narrow results to one biller.
        examples:
          aggregatorScopedKey:
            summary: Aggregator-scoped request narrowed to one biller
            value: 14
      - in: query
        name: billing_account_id
        schema:
          type: integer
        description: Optional filter for one billing account.
      - in: query
        name: account_id
        schema:
          type: string
        description: Optional filter for one account id.
      - in: query
        name: status
        schema:
          type: string
          enum:
          - pending
          - success
          - failed
          - void
          - declined
        description: Optional transaction status filter.
      - in: query
        name: from
        schema:
          type: string
          format: date-time
        description: Optional inclusive lower bound on `created_at`.
      - in: query
        name: to
        schema:
          type: string
          format: date-time
        description: Optional inclusive upper bound on `created_at`.
      - in: query
        name: page
        schema:
          type: integer
      - in: query
        name: per_page
        schema:
          type: integer
          maximum: 200
      responses:
        '200':
          description: Transaction list
          content:
            application/json:
              schema:
                type: object
                properties:
                  transactions:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Transaction"
                  meta:
                    "$ref": "#/components/schemas/PaginationMeta"
              examples:
                billerScopedKey:
                  summary: Biller-scoped key listing transactions for its own biller
                  value:
                    transactions:
                    - id: 9101
                      account_id: ACC500
                      biller_id: 14
                      billing_account_id: 22
                      payment_network_id: 1
                      net_amount: 47.05
                      status: success
                      paid_at: '2026-05-08T16:45:00Z'
                      created_at: '2026-05-08T16:45:31Z'
                    - id: 9100
                      account_id: ACC501
                      biller_id: 14
                      billing_account_id: 22
                      payment_network_id: 1
                      net_amount: 12.05
                      status: failed
                      paid_at:
                      created_at: '2026-05-08T15:14:00Z'
                    meta:
                      page: 1
                      per_page: 50
                      next_page:
                aggregatorScopedKey:
                  summary: Aggregator-scoped key listing transactions across accessible
                    billers
                  value:
                    transactions:
                    - id: 9201
                      account_id: ACC600
                      biller_id: 14
                      billing_account_id: 22
                      payment_network_id: 1
                      net_amount: 19.05
                      status: success
                      paid_at: '2026-05-08T17:00:00Z'
                      created_at: '2026-05-08T17:00:20Z'
                    - id: 9200
                      account_id: ACC700
                      biller_id: 19
                      billing_account_id: 23
                      payment_network_id: 2
                      net_amount: 0.0
                      status: failed
                      paid_at:
                      created_at: '2026-05-08T16:10:00Z'
                    meta:
                      page: 1
                      per_page: 50
                      next_page: 2
        '400':
          description: Invalid filter value such as an unparsable time
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '401':
          description: Missing or invalid token or missing required scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '403':
          description: API key is not allowed to access the requested biller scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
  "/api/v1/embedded_assets":
    get:
      tags:
      - Embedded Assets
      summary: List embedded assets
      description: |
        Returns all active embedded assets visible to the authenticated API key.
        Aggregator-scoped keys see their own assets plus unscoped global assets.
        Biller-scoped keys see assets scoped to their biller plus unscoped assets.
      security:
      - bearerAuth: []
      responses:
        '200':
          description: Paginated list of embedded assets
          content:
            application/json:
              schema:
                type: object
                properties:
                  embedded_assets:
                    type: array
                    items:
                      "$ref": "#/components/schemas/EmbeddedAsset"
              example:
                embedded_assets:
                - token: abc123def456
                  name: My Location Map
                  template_slug: location-map
                  configuration: {}
                  resolved_configuration:
                    mode: nationwide
                    default_zoom: 4
                    default_center:
                    - -98.35
                    - 39.5
                  url: https://payittoday-v2-production.fly.dev/e/abc123def456
                  active: true
                  aggregator_id:
                  aggregator_name:
                  biller_id:
                  biller_name:
                  billing_account_id:
                  billing_account_slug:
                  created_at: '2026-01-01T00:00:00Z'
                  updated_at: '2026-01-01T00:00:00Z'
        '401':
          description: Missing or invalid token or missing required scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '403':
          description: API key is not allowed to access the requested scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
  "/api/v1/embedded_assets/{token}":
    get:
      tags:
      - Embedded Assets
      summary: Get an embedded asset
      security:
      - bearerAuth: []
      parameters:
      - in: path
        name: token
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Asset details
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/EmbeddedAsset"
        '401':
          description: Missing or invalid token or missing required scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        '404':
          description: Asset not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
  "/api/v1/jobs/{job_id}":
    get:
      tags:
      - Jobs
      summary: Get async job status
      parameters:
      - in: path
        name: job_id
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Job status
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/JobStatus"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
  schemas:
    Biller:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        external_biller_id:
          type: string
          nullable: true
        name_slug:
          type: string
          nullable: true
        payment_networks:
          type: array
          items:
            "$ref": "#/components/schemas/BillerPaymentNetwork"
        billing_accounts:
          type: array
          items:
            "$ref": "#/components/schemas/BillerBillingAccount"
    BillerBillingAccount:
      type: object
      properties:
        id:
          type: integer
        account_slug:
          type: string
        network_identifier:
          type: string
          nullable: true
        payment_network_id:
          type: integer
          nullable: true
        landing_page_url:
          type: string
          format: uri
          nullable: true
    BillerPaymentNetwork:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        slug:
          type: string
        barcodes_enabled:
          type: boolean
    BarcodeRegistrationRecord:
      type: object
      properties:
        id:
          type: integer
        barcode_number:
          type: string
        status:
          type: string
          enum:
          - active
          - suspended
          - expired
          - revoked
        account_id:
          type: string
          nullable: true
        biller_id:
          type: integer
          nullable: true
        billing_account_id:
          type: integer
          nullable: true
        billing_account_slug:
          type: string
          nullable: true
        payment_network_id:
          type: integer
          nullable: true
        barcode_image_url:
          type: string
          format: uri
          nullable: true
        allocated_at:
          type: string
          format: date-time
          nullable: true
        suspended_at:
          type: string
          format: date-time
          nullable: true
        suspension_reason:
          type: string
          nullable: true
        expires_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    BarcodeRegistrationCreateRequest:
      type: object
      properties:
        billing_account_id:
          type: integer
        account_id:
          type: string
      required:
      - billing_account_id
      - account_id
    BarcodeManifestAccountInput:
      type: object
      properties:
        billing_account_id:
          type: integer
          description: Optional when a top-level `billing_account_id` is provided.
            Required per row for multi-billing-account manifests.
        account_id:
          type: string
      required:
      - account_id
    BarcodeManifestUploadLine:
      type: object
      description: One JSON object per line inside a `.jsonl` or `.ndjson` barcode
        manifest upload file.
      properties:
        billing_account_id:
          type: integer
          description: Optional for single-billing-account uploads with a top-level
            default. Required on each line for multi-billing-account uploads.
        account_id:
          type: string
      required:
      - account_id
    AccountInput:
      type: object
      properties:
        account_id:
          type: string
        metadata:
          type: object
          description: Name/address keys are stored encrypted at rest and rehydrated
            in account API responses for authorized clients.
      required:
      - account_id
    ManifestAccountInput:
      type: object
      properties:
        biller_id:
          type: integer
          description: Optional for biller-scoped keys and for aggregator single-biller
            manifests with a top-level `biller_id`. Required on each account for aggregator
            multi-biller manifests.
        account_id:
          type: string
        metadata:
          type: object
          description: Name/address keys are stored encrypted at rest and rehydrated
            in account API responses for authorized clients.
      required:
      - account_id
    ManifestUploadLine:
      type: object
      description: One JSON object per line inside a `.jsonl` or `.ndjson` manifest
        upload file.
      properties:
        biller_id:
          type: integer
          description: Optional for biller-scoped uploads and for aggregator uploads
            with a top-level `biller_id`. Required on each line for aggregator multi-biller
            uploads.
        account_id:
          type: string
        metadata:
          type: object
          description: Name/address keys are stored encrypted at rest and rehydrated
            in account API responses for authorized clients.
      required:
      - account_id
    BulkDisableAccountInput:
      type: object
      properties:
        biller_id:
          type: integer
          description: Optional for biller-scoped requests and for aggregator requests
            with a top-level `biller_id`. Required on each entry for aggregator multi-biller
            requests.
        account_id:
          type: string
      required:
      - account_id
    BulkDisableUploadLine:
      type: object
      description: One JSON object per line inside a `.jsonl` or `.ndjson` bulk-disable
        upload file.
      properties:
        biller_id:
          type: integer
          description: Optional for biller-scoped uploads and for aggregator uploads
            with a top-level `biller_id`. Required on each line for aggregator multi-biller
            uploads.
        account_id:
          type: string
      required:
      - account_id
    Account:
      type: object
      properties:
        account_id:
          type: string
        active:
          type: boolean
        metadata:
          type: object
          description: Full metadata payload returned to authorized clients, including
            name/address keys that are stored encrypted at rest.
        disabled_at:
          type: string
          format: date-time
          nullable: true
        disable_reason:
          type: string
          nullable: true
    ManifestResponse:
      type: object
      properties:
        manifest_id:
          type: string
        status:
          type: string
        summary:
          type: object
          properties:
            total:
              type: integer
            created:
              type: integer
            updated:
              type: integer
            disabled:
              type: integer
            failed:
              type: integer
        errors:
          type: array
          items:
            type: object
    QueuedManifestResponse:
      type: object
      properties:
        manifest_id:
          type: string
        job_id:
          type: string
        status:
          type: string
          enum:
          - queued
        async_reason:
          type: string
          enum:
          - diff_mode
          - multi_biller_manifest
          - account_count_threshold
          - payload_size_threshold
    ManifestUploadRequiredResponse:
      type: object
      properties:
        error:
          type: string
        upload_endpoint:
          type: string
        max_accounts:
          type: integer
        max_bytes:
          type: integer
    ManifestUploadAcceptedResponse:
      type: object
      properties:
        manifest_id:
          type: string
        job_id:
          type: string
        status:
          type: string
          enum:
          - queued
        status_url:
          type: string
          format: uri
    AccountManifestRunStatus:
      type: object
      properties:
        manifest_id:
          type: string
        status:
          type: string
          enum:
          - queued
          - processing
          - completed
          - partial
          - failed
        mode:
          type: string
          enum:
          - upsert
          - diff
        source_type:
          type: string
          enum:
          - inline
          - upload
        file_format:
          type: string
          enum:
          - jsonl
          - ndjson
        async_reason:
          type: string
          nullable: true
        default_biller_id:
          type: integer
          nullable: true
        account_count:
          type: integer
        biller_count:
          type: integer
        summary:
          type: object
          properties:
            total:
              type: integer
            created:
              type: integer
            updated:
              type: integer
            disabled:
              type: integer
            failed:
              type: integer
        errors:
          type: array
          items:
            type: object
        error_message:
          type: string
          nullable: true
        started_at:
          type: string
          format: date-time
          nullable: true
        finished_at:
          type: string
          format: date-time
          nullable: true
        job_id:
          type: string
          nullable: true
    QueuedBulkDisableResponse:
      type: object
      properties:
        run_id:
          type: string
        job_id:
          type: string
        status:
          type: string
          enum:
          - queued
        async_reason:
          type: string
          enum:
          - multi_biller_request
          - account_count_threshold
          - payload_size_threshold
    BulkDisableUploadRequiredResponse:
      type: object
      properties:
        error:
          type: string
        upload_endpoint:
          type: string
        max_accounts:
          type: integer
        max_bytes:
          type: integer
    BulkDisableUploadAcceptedResponse:
      type: object
      properties:
        run_id:
          type: string
        job_id:
          type: string
        status:
          type: string
          enum:
          - queued
        status_url:
          type: string
          format: uri
    AccountBulkDisableRunStatus:
      type: object
      properties:
        run_id:
          type: string
        status:
          type: string
          enum:
          - queued
          - processing
          - completed
          - partial
          - failed
        source_type:
          type: string
          enum:
          - inline
          - upload
        file_format:
          type: string
          enum:
          - jsonl
          - ndjson
        async_reason:
          type: string
          nullable: true
        default_biller_id:
          type: integer
          nullable: true
        reason:
          type: string
          nullable: true
        requested_count:
          type: integer
        biller_count:
          type: integer
        summary:
          type: object
          properties:
            requested:
              type: integer
            disabled:
              type: integer
            missing:
              type: integer
            failed:
              type: integer
        errors:
          type: array
          items:
            type: object
        error_message:
          type: string
          nullable: true
        started_at:
          type: string
          format: date-time
          nullable: true
        finished_at:
          type: string
          format: date-time
          nullable: true
        job_id:
          type: string
          nullable: true
    QueuedBarcodeManifestResponse:
      type: object
      properties:
        manifest_id:
          type: string
        job_id:
          type: string
        status:
          type: string
          enum:
          - queued
        async_reason:
          type: string
          enum:
          - multi_billing_account_manifest
          - account_count_threshold
          - payload_size_threshold
    BarcodeManifestUploadRequiredResponse:
      type: object
      properties:
        error:
          type: string
        upload_endpoint:
          type: string
        max_accounts:
          type: integer
        max_bytes:
          type: integer
    BarcodeManifestUploadAcceptedResponse:
      type: object
      properties:
        manifest_id:
          type: string
        job_id:
          type: string
        status:
          type: string
          enum:
          - queued
        status_url:
          type: string
          format: uri
    BarcodeManifestRunStatus:
      type: object
      properties:
        manifest_id:
          type: string
        status:
          type: string
          enum:
          - queued
          - processing
          - completed
          - partial
          - failed
        source_type:
          type: string
          enum:
          - inline
          - upload
        file_format:
          type: string
          enum:
          - jsonl
          - ndjson
        async_reason:
          type: string
          nullable: true
        default_billing_account_id:
          type: integer
          nullable: true
        requested_count:
          type: integer
        billing_account_count:
          type: integer
        summary:
          type: object
          properties:
            total:
              type: integer
            created:
              type: integer
            reused:
              type: integer
            failed:
              type: integer
        errors:
          type: array
          items:
            type: object
        error_message:
          type: string
          nullable: true
        started_at:
          type: string
          format: date-time
          nullable: true
        finished_at:
          type: string
          format: date-time
          nullable: true
        job_id:
          type: string
          nullable: true
    Transaction:
      type: object
      properties:
        id:
          type: integer
        account_id:
          type: string
          nullable: true
        biller_id:
          type: integer
          nullable: true
        billing_account_id:
          type: integer
        payment_network_id:
          type: integer
          nullable: true
        net_amount:
          type: number
          format: float
        status:
          type: string
        paid_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
    RealtimeTransactionNotificationsDiscovery:
      type: object
      properties:
        schema_version:
          type: string
        transport:
          "$ref": "#/components/schemas/RealtimeWebsocketTransport"
        events:
          "$ref": "#/components/schemas/RealtimeTransactionNotificationEvents"
        subscription_options:
          type: array
          items:
            "$ref": "#/components/schemas/RealtimeTransactionSubscriptionOption"
    RealtimeWebsocketTransport:
      type: object
      properties:
        url:
          type: string
          format: uri
        protocol:
          type: string
          example: actioncable-v1-json
        authentication:
          type: object
          properties:
            type:
              type: string
              example: bearer_header
            header:
              type: string
              example: Authorization
            scheme:
              type: string
              example: Bearer
        replay:
          type: object
          properties:
            cursor_parameter:
              type: string
              example: from_cursor
            delivery:
              type: string
              example: at_least_once
            ordering:
              type: string
              example: per_subscription_scope
    RealtimeTransactionNotificationEvents:
      type: object
      description: Websocket subscriptions on this topic deliver Action Cable data
        frames whose `message` field matches `RealtimeTransactionNotificationMessage`.
      properties:
        topic:
          type: string
          enum:
          - realtime_transaction_notifications
        event_types:
          type: array
          items:
            type: string
            enum:
            - payment_applied
            - payment_voided
            - payment_failed
            - payment_declined
    RealtimeTransactionNotificationWebsocketFrame:
      type: object
      properties:
        identifier:
          type: string
          description: JSON-stringified subscription identifier echoed by Action Cable
            on data frames.
          example: '{"channel":"RealtimeTransactionNotificationsBillerChannel","biller_id":14,"external_biller_id":"EXAMPLE_BILLER_001","from_cursor":null}'
        message:
          "$ref": "#/components/schemas/RealtimeTransactionNotificationMessage"
    RealtimeTransactionNotificationMessage:
      type: object
      properties:
        topic:
          type: string
          enum:
          - realtime_transaction_notifications
        event_type:
          type: string
          enum:
          - payment_applied
          - payment_voided
          - payment_failed
          - payment_declined
        cursor:
          type: integer
        occurred_at:
          type: string
          format: date-time
        replayed:
          type: boolean
        payment_event:
          "$ref": "#/components/schemas/RealtimeTransactionNotificationPayload"
      example:
        topic: realtime_transaction_notifications
        event_type: payment_applied
        cursor: 9101
        occurred_at: '2026-05-08T16:45:31Z'
        replayed: false
        payment_event:
          cursor: 9101
          event_type: payment_applied
          account:
            id: 55
            account_id: ACC500
          account_id: ACC500
          account_record_id: 55
          barcode_registration_id: 88
          biller_id: 14
          external_biller_id: EXAMPLE_BILLER_001
          biller_name: Example Utility One
          biller_slug: example-utility-one
          billing_account_id: 22
          billing_account_slug: example-billing-account
          payment_network: example-network
          provider_transaction_id: LIVE-PORT-1
          status: success
          voided: false
          amount_paid: 50.0
          fee_amount: 2.95
          net_amount: 47.05
          paid_at: '2026-05-08T16:45:00Z'
          created_at: '2026-05-08T16:45:31Z'
    RealtimeTransactionNotificationPayload:
      type: object
      properties:
        account:
          "$ref": "#/components/schemas/RealtimeTransactionNotificationAccountRef"
        account_id:
          type: string
          nullable: true
        account_record_id:
          type: integer
        barcode_registration_id:
          type: integer
          nullable: true
        biller_id:
          type: integer
          nullable: true
        external_biller_id:
          type: string
          nullable: true
        biller_name:
          type: string
          nullable: true
        biller_slug:
          type: string
          nullable: true
        billing_account_id:
          type: integer
        billing_account_slug:
          type: string
          nullable: true
        payment_network:
          type: string
          nullable: true
        provider_transaction_id:
          type: string
          nullable: true
        status:
          type: string
          enum:
          - pending
          - success
          - failed
          - void
          - declined
        voided:
          type: boolean
        amount_paid:
          type: number
          format: float
        fee_amount:
          type: number
          format: float
          nullable: true
        net_amount:
          type: number
          format: float
          nullable: true
        paid_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        cursor:
          type: integer
        event_type:
          type: string
          enum:
          - payment_applied
          - payment_voided
          - payment_failed
          - payment_declined
    RealtimeTransactionNotificationAccountRef:
      type: object
      properties:
        id:
          type: integer
        account_id:
          type: string
          nullable: true
    RealtimeTransactionSubscriptionOption:
      type: object
      properties:
        scope:
          type: string
          enum:
          - portfolio
          - biller
          - payment_network
          - billing_account
        channel:
          type: string
        resource:
          type: object
          additionalProperties: true
        counts:
          type: object
          additionalProperties: true
          nullable: true
        identifier:
          "$ref": "#/components/schemas/RealtimeTransactionSubscriptionIdentifier"
    RealtimeTransactionSubscriptionIdentifier:
      type: object
      properties:
        channel:
          type: string
        biller_id:
          type: integer
          nullable: true
        external_biller_id:
          type: string
          nullable: true
        payment_network_id:
          type: integer
          nullable: true
        payment_network_slug:
          type: string
          nullable: true
        billing_account_id:
          type: integer
          nullable: true
        billing_account_slug:
          type: string
          nullable: true
        from_cursor:
          type: integer
          nullable: true
    PaginationMeta:
      type: object
      properties:
        page:
          type: integer
        per_page:
          type: integer
        next_page:
          type: integer
          nullable: true
    JobResponse:
      type: object
      properties:
        job_id:
          type: string
        status:
          type: string
          enum:
          - queued
    JobStatus:
      type: object
      properties:
        id:
          type: string
        job_class:
          type: string
        status:
          type: string
        output:
          type: string
          nullable: true
        started_at:
          type: string
          format: date-time
          nullable: true
        finished_at:
          type: string
          format: date-time
          nullable: true
        error_class:
          type: string
          nullable: true
        error_message:
          type: string
          nullable: true
    ErrorResponse:
      type: object
      properties:
        error:
          type: string
    ParticipationRequestCreate:
      type: object
      properties:
        legal_name:
          type: string
        entity_type:
          type: string
        state_of_incorporation:
          type: string
        date_of_incorporation:
          type: string
          format: date
        public_or_private:
          type: string
        federal_ein:
          type: string
        url:
          type: string
          format: uri
        meta:
          type: object
          additionalProperties: true
          description: Flexible payload for biller-specific supplemental data. Echoed
            in request responses and status event payloads. Discover expected keys
            for an aggregator via `GET /api/v1/aggregator_profiles/expected_meta`.
        other_names:
          type: array
          items:
            type: string
        previously_used_names:
          type: array
          items:
            type: string
        address:
          type: object
          description: Business address captured with the request payload.
          properties:
            address_line_1:
              type: string
            address_line_2:
              type: string
            city:
              type: string
            state:
              type: string
            zip_code:
              type: string
          additionalProperties: true
        owners:
          type: array
          items:
            type: object
            properties:
              first_name:
                type: string
              last_name:
                type: string
              email:
                type: string
                format: email
              date_of_birth:
                type: string
                format: date
              required:
                type: boolean
              role:
                type: string
              template_key:
                type: string
              email_source_path:
                type: string
                description: Dot path resolved against the submission payload when
                  `email` is omitted.
              metadata:
                type: object
                additionalProperties: true
              address:
                type: object
                properties:
                  address_line_1:
                    type: string
                  address_line_2:
                    type: string
                  city:
                    type: string
                  state:
                    type: string
                  zip_code:
                    type: string
                additionalProperties: true
        banking:
          type: object
          description: Optional banking intake fields used to build the participation
            banking profile.
          properties:
            contact_name:
              type: string
            contact_phone_number:
              type: string
            financial_institution:
              type: object
              properties:
                name:
                  type: string
                phone_number:
                  type: string
                  description: Main phone number for the financial institution.
                routing_number:
                  type: string
                account_number:
                  type: string
                currency:
                  type: string
                contact_phone:
                  type: string
                contact_email:
                  type: string
                  format: email
                contact_name:
                  type: string
              additionalProperties: true
          additionalProperties: true
      required:
      - legal_name
    ParticipationRequest:
      type: object
      properties:
        request_id:
          type: string
        aggregator_id:
          type: integer
          nullable: true
        biller_id:
          type: integer
          nullable: true
        intake_status:
          type: string
          enum:
          - submitted
          - in_review
          - underwriting
          - approved
          - rejected
          - cancelled
        acceptance_status:
          type: string
          enum:
          - pending
          - accepted
          - rescinded
        signing_status:
          type: string
          enum:
          - not_started
          - sent
          - viewed
          - completed
          - declined
          - expired
          - voided
          - failed
          - pending_required_documents
        meta:
          type: object
          additionalProperties: true
        event_stream:
          "$ref": "#/components/schemas/ParticipationEventStream"
        documents:
          type: array
          items:
            "$ref": "#/components/schemas/ParticipationDocument"
        signing:
          "$ref": "#/components/schemas/ParticipationSigning"
    ParticipationEventStream:
      type: object
      properties:
        polling:
          "$ref": "#/components/schemas/ParticipationEventPollingStream"
        websocket:
          "$ref": "#/components/schemas/ParticipationEventWebsocketStream"
    ParticipationEventPollingStream:
      type: object
      properties:
        url:
          type: string
        latest_cursor:
          type: integer
          nullable: true
    ParticipationEventWebsocketStream:
      type: object
      properties:
        schema_version:
          type: string
        transport:
          type: object
          properties:
            url:
              type: string
              format: uri
            protocol:
              type: string
              example: actioncable-v1-json
            authentication:
              type: object
              properties:
                type:
                  type: string
                  example: bearer_header
                header:
                  type: string
                  example: Authorization
                scheme:
                  type: string
                  example: Bearer
            replay:
              type: object
              properties:
                cursor_parameter:
                  type: string
                  example: from_cursor
                delivery:
                  type: string
                  example: at_least_once
                ordering:
                  type: string
                  example: per_request
        events:
          type: object
          description: Websocket subscriptions on this topic deliver Action Cable
            data frames whose `message` field matches `ParticipationRealtimeEvent`.
          properties:
            topic:
              type: string
              enum:
              - biller_participation_requests
        subscription:
          "$ref": "#/components/schemas/ParticipationEventWebsocketSubscription"
    ParticipationEventWebsocketSubscription:
      type: object
      properties:
        channel:
          type: string
          example: BillerParticipationRequestsChannel
        identifier:
          type: object
          properties:
            channel:
              type: string
              example: BillerParticipationRequestsChannel
            request_id:
              type: string
            from_cursor:
              type: integer
              nullable: true
    ParticipationEventFeed:
      type: object
      properties:
        request_id:
          type: string
        events:
          type: array
          items:
            "$ref": "#/components/schemas/ParticipationEvent"
        next_cursor:
          type: integer
          nullable: true
    ParticipationEvent:
      type: object
      properties:
        cursor:
          type: integer
        event_id:
          type: string
        event_type:
          type: string
          enum:
          - created
          - status_changed
        occurred_at:
          type: string
          format: date-time
        changed_statuses:
          type: array
          items:
            type: string
            enum:
            - intake_status
            - acceptance_status
            - signing_status
        payload:
          "$ref": "#/components/schemas/ParticipationStatusEventPayload"
    ParticipationRealtimeEvent:
      type: object
      properties:
        topic:
          type: string
          enum:
          - biller_participation_requests
        request_id:
          type: string
        cursor:
          type: integer
        event_id:
          type: string
        event_type:
          type: string
          enum:
          - created
          - status_changed
        occurred_at:
          type: string
          format: date-time
        changed_statuses:
          type: array
          items:
            type: string
            enum:
            - intake_status
            - acceptance_status
            - signing_status
        replayed:
          type: boolean
        payload:
          "$ref": "#/components/schemas/ParticipationStatusEventPayload"
      example:
        topic: biller_participation_requests
        request_id: 71ab6fb0-74da-4f82-8d8d-73de1536f83d
        cursor: 4422
        event_id: 4f722c80-9f15-445d-90af-4e2d06a4cf18
        event_type: status_changed
        occurred_at: '2026-04-23T21:10:29Z'
        changed_statuses:
        - signing_status
        replayed: false
        payload:
          event_id: 4f722c80-9f15-445d-90af-4e2d06a4cf18
          event_type: status_changed
          request_id: 71ab6fb0-74da-4f82-8d8d-73de1536f83d
          trigger: status_changed
          changed_statuses:
          - signing_status
          occurred_at: '2026-04-23T21:10:29Z'
          meta:
            source_campaign: q2-onboarding
            external_org_id: org_12345
          intake_status: submitted
          acceptance_status: pending
          signing_status: sent
          signed_at:
          signing:
            provider_key: docusign
            provider_envelope_id: env-100
            next_action_url: https://signing.example.com/envelope/env-100
    ParticipationRealtimeEventWebsocketFrame:
      type: object
      properties:
        identifier:
          type: string
          description: JSON-stringified subscription identifier echoed by Action Cable
            on data frames.
          example: '{"channel":"BillerParticipationRequestsChannel","request_id":"71ab6fb0-74da-4f82-8d8d-73de1536f83d","from_cursor":null}'
        message:
          "$ref": "#/components/schemas/ParticipationRealtimeEvent"
    ParticipationStatusEventPayload:
      type: object
      properties:
        event_id:
          type: string
        event_type:
          type: string
          enum:
          - created
          - status_changed
        request_id:
          type: string
        trigger:
          type: string
          enum:
          - created
          - status_changed
        changed_statuses:
          type: array
          items:
            type: string
            enum:
            - intake_status
            - acceptance_status
            - signing_status
        occurred_at:
          type: string
          format: date-time
        meta:
          type: object
          additionalProperties: true
        intake_status:
          type: string
          enum:
          - submitted
          - in_review
          - underwriting
          - approved
          - rejected
          - cancelled
        acceptance_status:
          type: string
          enum:
          - pending
          - accepted
          - rescinded
        signing_status:
          type: string
          enum:
          - not_started
          - sent
          - viewed
          - completed
          - declined
          - expired
          - voided
          - failed
          - pending_required_documents
        signed_at:
          type: string
          format: date-time
          nullable: true
        signing:
          "$ref": "#/components/schemas/ParticipationEventSigningContext"
    ParticipationEventSigningContext:
      type: object
      properties:
        provider_key:
          type: string
          nullable: true
        provider_envelope_id:
          type: string
          nullable: true
        next_action_url:
          type: string
          format: uri
          nullable: true
    ParticipationDocument:
      type: object
      properties:
        key:
          type: string
          nullable: true
        name:
          type: string
          nullable: true
        status:
          type: string
          enum:
          - unavailable
          - available
          - expired
        source:
          type: string
          enum:
          - provider
          - external_upload
          - completion_package
        url:
          type: string
          nullable: true
          format: uri
        expires_at:
          type: string
          nullable: true
          format: date-time
        uploaded_at:
          type: string
          nullable: true
          format: date-time
        checksum_sha256:
          type: string
          nullable: true
    AggregatorDocumentRequirement:
      type: object
      properties:
        key:
          type: string
          description: Document key accepted by the participation document upload
            endpoint.
        display_name:
          type: string
          nullable: true
        required:
          type: boolean
          description: Whether the document is required before participation signing
            can complete.
        sources:
          type: array
          items:
            type: string
          description: Allowed sources that may satisfy this document requirement,
            such as `provider` or `external_upload`.
        content_types:
          type: array
          items:
            type: string
          description: Accepted MIME types for uploads. PDF is the default.
        metadata:
          type: object
          additionalProperties: true
          nullable: true
    ParticipationDocumentUploadRequest:
      type: object
      required:
      - document_key
      - file
      properties:
        document_key:
          type: string
          description: Configured participation document key, such as `bank_letter`.
        file:
          type: string
          format: binary
          description: Signed PDF document.
        display_name:
          type: string
        signed_at:
          type: string
          format: date-time
        signer_name:
          type: string
        signer_email:
          type: string
          format: email
        source:
          type: string
          default: external_upload
        idempotency_key:
          type: string
          description: Optional key used to return the existing uploaded document
            on safe retries.
        metadata:
          type: string
          description: Optional JSON object encoded as a string when sent as multipart
            form data.
    ParticipationDocumentUploadResponse:
      type: object
      properties:
        request_id:
          type: string
        document:
          "$ref": "#/components/schemas/ParticipationDocument"
        completion:
          type: object
          properties:
            required_documents_available:
              type: boolean
            missing_document_keys:
              type: array
              items:
                type: string
            signing_status:
              type: string
              enum:
              - not_started
              - sent
              - viewed
              - completed
              - declined
              - expired
              - voided
              - failed
              - pending_required_documents
            completion_envelopes_queued:
              type: boolean
    ParticipationSigning:
      type: object
      properties:
        provider_key:
          type: string
          nullable: true
        status:
          type: string
        next_action_url:
          type: string
          nullable: true
          format: uri
        provider_payload:
          type: object
    EmbeddedAsset:
      type: object
      properties:
        token:
          type: string
          description: Unique public embed URL token
          example: abc123def456ghi789
        name:
          type: string
          description: Human-readable name
          example: Acme Corp Location Map
        template_slug:
          type: string
          description: Slug of the embeddable asset template
          example: location-map
        configuration:
          type: object
          description: Runtime configuration overrides
          example: {}
        resolved_configuration:
          type: object
          description: Final merged configuration (template defaults + asset overrides)
          example:
            mode: nationwide
            default_zoom: 4
            default_center:
            - -98.35
            - 39.5
        url:
          type: string
          format: uri
          description: Public embed URL
          example: https://payittoday-v2-production.fly.dev/e/abc123def456ghi789
        active:
          type: boolean
          description: Whether the embed is active
        aggregator_id:
          type: integer
          nullable: true
          description: Scope to this aggregator's billers
        aggregator_name:
          type: string
          nullable: true
        biller_id:
          type: integer
          nullable: true
          description: Scope to this biller's retailer chains
        biller_name:
          type: string
          nullable: true
        billing_account_id:
          type: integer
          nullable: true
          description: Scope to this billing account
        billing_account_slug:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
