FUSE Health Affiliate Program API

Affiliate/referral module. Requires a plan that includes Affiliates, resolved from the key's clinic (403 AFFILIATES_TIER_REQUIRED otherwise). The brand pays its affiliates directly from its own account, by the method it chooses; FUSE never sends affiliate payments. Payouts here are the brand's record of what it owes and has paid.

GitHub OpenAPI

Operations 20

GET /api/v1/affiliate-program Affiliate program config for the clinic #
PUT /api/v1/affiliate-program Update affiliate program config (rates, cookie window, payout thresholds) #
GET /api/v1/affiliate-program/margin-estimate Estimated margin impact of the current commission config #
GET /api/v1/affiliate-program/affiliates List the clinic's affiliates #
PATCH /api/v1/affiliate-program/affiliates/{membershipId} Update per-affiliate rate overrides / payout details #
POST /api/v1/affiliate-program/affiliates/invite Invite an affiliate by email #
POST /api/v1/affiliate-program/affiliates/{membershipId}/activate Reactivate an affiliate #
POST /api/v1/affiliate-program/affiliates/{membershipId}/deactivate Deactivate an affiliate #
GET /api/v1/affiliate-program/commission-report Affiliate commission report (read-only) #
GET /api/v1/affiliate-program/payouts List affiliate payouts #
POST /api/v1/affiliate-program/payouts/generate Generate the next payout batch from accrued commissions #
GET /api/v1/affiliate-program/payouts/{id}/commissions Commissions included in a payout #
POST /api/v1/affiliate-program/payouts/{id}/approve Approve a pending payout #
POST /api/v1/affiliate-program/payouts/{id}/mark-paid Record that the brand has paid a payout from its own account #
POST /api/v1/affiliate-program/payouts/{payoutId}/exclude-commissions Exclude specific commissions from a payout #
GET /api/v1/affiliate-program/tax-documents List affiliate W-9 / tax documents for review #
POST /api/v1/affiliate-program/tax-documents/{docId}/verify Mark a tax document verified #
POST /api/v1/affiliate-program/tax-documents/{docId}/reject Reject a tax document (reason required) #
POST /api/v1/affiliate-program/tiers Create a commission tier #
DELETE /api/v1/affiliate-program/tiers/{tierId} Delete a commission tier #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/fusehealth-com:fusehealth-com-affiliate-program-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

fusehealth-com-affiliate-program-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: FUSE Brand API (External) Affiliate Program API
  version: 1.0.0
  description: 'Server-to-server API exposing the FUSE brand-admin surface so a single brand/tenant can build its own admin frontend. Every request is authenticated with an API key bound to exactly one clinic; all data is auto-scoped to that clinic and cannot reach another brand''s data. Isolation is enforced by application-layer clinic filtering on every query.


    COVERAGE: 218 operations across 180 paths, and EVERY ONE is available to every brand. There is no per-clinic enablement step. What governs access: your API key and its scopes, the clinic the key belongs to (every read and write is filtered to it), your plan''s entitlements for the two commercially-gated areas — Affiliates (`AFFILIATES_TIER_REQUIRED`) and CRM Connect (`CRM_TIER_REQUIRED`) — and rate limits.


    AUTH: the external /api/v1 surface uses an API key (`x-api-key`, ApiKeyAuth). Key management (/api-keys) and /auth/signin live at the API root and use a brand-admin JWT (BearerAuth); /auth/signin itself is unauthenticated.


    SCOPES: every key carries scopes; read-only is the default and mutating endpoints require the `write` scope (403 INSUFFICIENT_SCOPE otherwise). Keys default to a 1-year expiry.


    RATE LIMITING: every endpoint may additionally return `429 Too Many Requests` with a `Retry-After` header; 429 is not repeated on each operation below.


    PLATFORM FIELDS: product cost, fees, pharmacy routing, doctor questions and FUSE approvals are set by FUSE, not by the brand. Any write (POST/PUT/PATCH/DELETE) whose JSON body carries one of those fields at any depth — for example `pharmacyPrice`, `shippingFeeUsd`, `coolerFeeUsd`, `pharmacyId`, `preferredPharmacyId`, `pharmacyProvider`, `requiredDoctorQuestions`, `facilitationFee`, `placeholderPrice`, `legalBusinessName`, `isTemplate` or `tenantManagerApproved` — is refused with `403 PLATFORM_FIELD`, whose `fields` array lists the offending paths; remove them and resend. (One exception: `POST /api/v1/products` still accepts the `pharmacyPrice` its schema requires, and ignores it.) 403 PLATFORM_FIELD is not repeated on each operation below.'
servers:
- url: https://api-dev.fusehealth.com
  description: Development
- url: https://api.fusehealth.com
  description: Production
security:
- ApiKeyAuth: []
tags:
- name: Affiliate Program
  description: Affiliate/referral module. Requires a plan that includes Affiliates, resolved from the key's clinic (403 AFFILIATES_TIER_REQUIRED otherwise). The brand pays its affiliates directly from its own account, by the method it chooses; FUSE never sends affiliate payments. Payouts here are the brand's record of what it owes and has paid.
paths:
  /api/v1/affiliate-program:
    get:
      tags:
      - Affiliate Program
      summary: Affiliate program config for the clinic
      operationId: getApiV1Affiliateprogram
      description: Affiliate program config for the clinic.
      security:
      - ApiKeyAuth: []
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
    put:
      tags:
      - Affiliate Program
      summary: Update affiliate program config (rates, cookie window, payout thresholds)
      operationId: putApiV1Affiliateprogram
      description: Update affiliate program config (rates, cookie window, payout thresholds).
      security:
      - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                affiliateProgramEnabled:
                  type: boolean
                affiliateCommissionMode:
                  type: string
                affiliateFirstOrderRate:
                  type: integer
                affiliateDefaultRate:
                  type: integer
                affiliateCommissionDurationMonths:
                  type: integer
                affiliateCookieWindowDays:
                  type: integer
                affiliateMinimumPayoutThreshold:
                  type: integer
                affiliatePayoutFrequency:
                  type: string
                affiliateMaxCount:
                  type: integer
            example:
              affiliateProgramEnabled: true
              affiliateCommissionMode: percentage
              affiliateFirstOrderRate: 20
              affiliateDefaultRate: 10
              affiliateCommissionDurationMonths: 12
              affiliateCookieWindowDays: 30
              affiliateMinimumPayoutThreshold: 50
              affiliatePayoutFrequency: monthly
              affiliateMaxCount: 100
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /api/v1/affiliate-program/margin-estimate:
    get:
      tags:
      - Affiliate Program
      summary: Estimated margin impact of the current commission config
      operationId: getApiV1AffiliateprogramMarginestimate
      description: Estimated margin impact of the current commission config.
      security:
      - ApiKeyAuth: []
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/affiliate-program/affiliates:
    get:
      tags:
      - Affiliate Program
      summary: List the clinic's affiliates
      operationId: getApiV1AffiliateprogramAffiliates
      description: List the clinic's affiliates.
      security:
      - ApiKeyAuth: []
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/affiliate-program/affiliates/{membershipId}:
    patch:
      tags:
      - Affiliate Program
      summary: Update per-affiliate rate overrides / payout details
      operationId: patchApiV1AffiliateprogramAffiliatesByMembershipId
      description: Update per-affiliate rate overrides / payout details.
      security:
      - ApiKeyAuth: []
      parameters:
      - name: membershipId
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                firstOrderRate:
                  type: integer
                commissionRate:
                  type: integer
                payoutContactMethod:
                  type: string
                payoutNotes:
                  type: string
                commissionAfterDeactivation:
                  type: boolean
                  description: While the affiliate is deactivated, whether orders that still carry them (renewals of customers they already referred) keep earning them commission. Default false. A removed affiliate never earns new commission.
            example:
              firstOrderRate: 25
              commissionRate: 12
              payoutContactMethod: email
              payoutNotes: '...'
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/affiliate-program/affiliates/invite:
    post:
      tags:
      - Affiliate Program
      summary: Invite an affiliate by email
      operationId: postApiV1AffiliateprogramAffiliatesInvite
      description: Invite one affiliate by email. Creates an affiliate login with an active membership for this brand and emails the affiliate, on the brand's behalf, an invitation with a one-time link (valid 7 days) to set their own password; no password is ever emailed or returned. An affiliate this brand removed earlier is re-attached and re-invited; inviting an affiliate of this brand who has not set up their account yet resends the invitation with a new link (200). Refused when the brand is at its affiliate cap (400), for a disposable address (400), and for an address that already belongs to a user (409). Requires a signed-in brand owner or admin behind the key (the FUSE AI connector); an ordinary API key gets 403. Returns the membership id, its status, whether the email was sent and whether it was a re-invite.
      security:
      - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - email
              properties:
                email:
                  type: string
                  format: email
            example:
              email: partner@example.com
      responses:
        '201':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          description: The address already belongs to a user or an affiliate.
  /api/v1/affiliate-program/affiliates/{membershipId}/activate:
    post:
      tags:
      - Affiliate Program
      summary: Reactivate an affiliate
      operationId: postApiV1AffiliateprogramAffiliatesByMembershipIdActivate
      description: Set one affiliate's membership to active, so their referral link credits new orders to them again. No change when already active; 409 for a terminated membership.
      security:
      - ApiKeyAuth: []
      parameters:
      - name: membershipId
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/affiliate-program/affiliates/{membershipId}/deactivate:
    post:
      tags:
      - Affiliate Program
      summary: Deactivate an affiliate
      operationId: postApiV1AffiliateprogramAffiliatesByMembershipIdDeactivate
      description: 'Suspend one affiliate''s membership: their referral link stops crediting new orders to them. Commissions already recorded and payouts are not changed; pending commissions are still paid. By default they earn no commission on new or renewal orders from now on; send keepCommission true to let renewals of customers they already referred keep earning them commission. Deactivating an affiliate who is already deactivated changes only that choice.'
      security:
      - ApiKeyAuth: []
      parameters:
      - name: membershipId
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                keepCommission:
                  type: boolean
                  description: Keep paying commission on renewals of customers this affiliate already referred while they are deactivated. Default false.
            example:
              keepCommission: false
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/affiliate-program/commission-report:
    get:
      tags:
      - Affiliate Program
      summary: Affiliate commission report (read-only)
      operationId: getApiV1AffiliateprogramCommissionreport
      description: 'A read-only report of the affiliate commissions this brand has recorded, over a date range of when each was earned (UTC days, inclusive). With groupBy none (the default) it returns one row per commission, paged: its id, the affiliate membership id, amount, rate applied, type, status, the payout it is in and when it was earned and became payable — never the sale or customer behind it. With groupBy affiliate, payout, status or month it returns a count and dollar total per group instead. Totals (count, amount and a per-status breakdown) are always included.'
      security:
      - ApiKeyAuth: []
      parameters:
      - in: query
        name: from
        required: false
        schema:
          type: string
          format: date
      - in: query
        name: to
        required: false
        schema:
          type: string
          format: date
      - in: query
        name: groupBy
        required: false
        schema:
          type: string
          enum:
          - none
          - affiliate
          - payout
          - status
          - month
      - in: query
        name: affiliateMembershipId
        required: false
        schema:
          type: string
      - in: query
        name: status
        required: false
        schema:
          type: string
          enum:
          - pending
          - included_in_payout
          - paid
          - refunded
      - in: query
        name: limit
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 500
      - in: query
        name: offset
        required: false
        schema:
          type: integer
          minimum: 0
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /api/v1/affiliate-program/payouts:
    get:
      tags:
      - Affiliate Program
      summary: List affiliate payouts
      operationId: getApiV1AffiliateprogramPayouts
      description: List affiliate payouts. The brand pays its affiliates directly from its own account; FUSE does not send affiliate payments. A `paid` payout is one the brand has recorded as paid.
      security:
      - ApiKeyAuth: []
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/affiliate-program/payouts/generate:
    post:
      tags:
      - Affiliate Program
      summary: Generate the next payout batch from accrued commissions
      operationId: postApiV1AffiliateprogramPayoutsGenerate
      description: Generate the next payout batch from accrued commissions.
      security:
      - ApiKeyAuth: []
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /api/v1/affiliate-program/payouts/{id}/commissions:
    get:
      tags:
      - Affiliate Program
      summary: Commissions included in a payout
      operationId: getApiV1AffiliateprogramPayoutsByIdCommissions
      description: Commissions included in a payout.
      security:
      - ApiKeyAuth: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/affiliate-program/payouts/{id}/approve:
    post:
      tags:
      - Affiliate Program
      summary: Approve a pending payout
      operationId: postApiV1AffiliateprogramPayoutsByIdApprove
      description: Approve a pending payout.
      security:
      - ApiKeyAuth: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/affiliate-program/payouts/{id}/mark-paid:
    post:
      tags:
      - Affiliate Program
      summary: Record that the brand has paid a payout from its own account
      operationId: postApiV1AffiliateprogramPayoutsByIdMarkpaid
      description: Records a payment the brand has already sent the affiliate from its own account, by whatever method it chose, and emails the affiliate that the brand marked it paid. FUSE does not send the money; this call moves no funds.
      security:
      - ApiKeyAuth: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                notes:
                  type: string
            example:
              notes: Paid via ACH
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/affiliate-program/payouts/{payoutId}/exclude-commissions:
    post:
      tags:
      - Affiliate Program
      summary: Exclude specific commissions from a payout
      operationId: postApiV1AffiliateprogramPayoutsByPayoutIdExcludecommissions
      description: Exclude specific commissions from a payout.
      security:
      - ApiKeyAuth: []
      parameters:
      - name: payoutId
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                commissionIds:
                  type: array
                  items:
                    type: string
            example:
              commissionIds:
              - <commission-uuid>
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/affiliate-program/tax-documents:
    get:
      tags:
      - Affiliate Program
      summary: List affiliate W-9 / tax documents for review
      operationId: getApiV1AffiliateprogramTaxdocuments
      description: List affiliate W-9 / tax documents for review.
      security:
      - ApiKeyAuth: []
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/affiliate-program/tax-documents/{docId}/verify:
    post:
      tags:
      - Affiliate Program
      summary: Mark a tax document verified
      operationId: postApiV1AffiliateprogramTaxdocumentsByDocIdVerify
      description: Mark a tax document verified.
      security:
      - ApiKeyAuth: []
      parameters:
      - name: docId
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/affiliate-program/tax-documents/{docId}/reject:
    post:
      tags:
      - Affiliate Program
      summary: Reject a tax document (reason required)
      operationId: postApiV1AffiliateprogramTaxdocumentsByDocIdReject
      description: Reject a tax document (reason required).
      security:
      - ApiKeyAuth: []
      parameters:
      - name: docId
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                reason:
                  type: string
            example:
              reason: Illegible — please resubmit
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/affiliate-program/tiers:
    post:
      tags:
      - Affiliate Program
      summary: Create a commission tier
      operationId: postApiV1AffiliateprogramTiers
      description: Create a commission tier.
      security:
      - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                minActiveReferrals:
                  type: integer
                percentage:
                  type: integer
                label:
                  type: string
            example:
              minActiveReferrals: 5
              percentage: 15
              label: Silver
      responses:
        '201':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /api/v1/affiliate-program/tiers/{tierId}:
    delete:
      tags:
      - Affiliate Program
      summary: Delete a commission tier
      operationId: deleteApiV1AffiliateprogramTiersByTierId
      description: Delete a commission tier.
      security:
      - ApiKeyAuth: []
      parameters:
      - name: tierId
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                  data: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  responses:
    Forbidden:
      description: Authenticated but not permitted (insufficient scope, wrong role, or FUSE feature not enabled)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    NotFound:
      description: Resource not found (or not owned by the key's clinic)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    BadRequest:
      description: Bad request / business-rule failure (may include a `code`)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    Unauthorized:
      description: Missing, invalid, inactive, or expired API key (or JWT)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
  schemas:
    ErrorEnvelope:
      description: 'Failure shape. NOTE: most modules use `message`; programs endpoints use `error`. Read both. Some errors include a machine-readable `code` and/or a `data` payload.'
      type: object
      properties:
        success:
          type: boolean
          enum:
          - false
        message:
          type: string
        error:
          type: string
        code:
          type: string
        data: {}
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: Clinic-scoped API key. Keep server-side; never expose in a browser or app bundle.
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: FUSE brand-admin JWT (from POST /auth/signin). Used ONLY by the key-management endpoints — the external /api/v1 surface never accepts JWTs, and API keys never work on key management.