{
  "openapi": "3.1.0",
  "info": {
    "title": "BoltClaw API",
    "version": "1.22.0",
    "summary": "Pages, conversations, social publishing and referrals for one BoltClaw workspace.",
    "description": "Everything the BoltClaw dashboard does with your link-in-bio pages, the conversations people\nstart on them, your connected social accounts and your referral programs, from your own\nsystems.\n\n## Base URL\n\n`https://api.boltclaw.ai/v1`. `https://boltclaw.ai/api/v1` remains a supported alias: it\nreaches the same gateway and the same operations, so an integration written against it keeps\nworking. Use `api.boltclaw.ai` for anything new: it is the API's own host, one hop shorter,\nand the address every example in this reference uses.\n\n## Authentication\n\nSend your API key as `Authorization: Bearer bc_live_...`. Create one in the dashboard under\nAPI keys; it is shown once. A key carries scopes, and each operation names the scope it needs\n(`x-scope`). A key without it is answered `403 INSUFFICIENT_SCOPE`.\n\n## Your key decides the workspace\n\nEvery key belongs to one workspace, so no operation takes a workspace parameter.\n`GET /workspace` tells you which one. Another workspace's page, post, thread or program\nanswers `404 NOT_FOUND`, never `403`: \"not yours\" and \"not there\" are the same answer, so\nnothing confirms that somebody else's object exists.\n\n## Rate limits\n\nEach key may make a number of requests a minute set by its organisation's plan, shared by\nevery operation. Every response after the key is checked carries `X-RateLimit-Limit`,\n`X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix seconds). Over the limit is\n`429 RATE_LIMITED` with `Retry-After`. Two operations draw on a second, smaller budget:\n`POST /pages` (10 an hour by default, because a create is the one call that can tell you a\nslug is taken) and `POST /inquiries/{id}/messages` (60 an hour by default, because each one\nreaches a person). Both are set per plan; `GET /usage` returns the numbers that apply to you,\nas `api_pages_created_per_hour` and `api_messages_per_hour`.\n\n## Errors\n\nEvery failure is `{ \"error\": \"...\", \"code\": \"...\", \"request_id\": \"...\" }`. Branch on `code`\n(the `ErrorCode` table lists every one); `error` is a sentence for a person and its wording\nmay change. A validation error also carries `fields`, each named field with the rule it\nbroke. Treat an unknown `code` as a generic failure of its HTTP status class.\n\n## Request ids\n\nEvery response carries `X-Request-Id`, and every error body carries the same value as\n`request_id`. Quote it when you contact support. Send your own `X-Request-Id` (a UUID) and\nit is used instead, so your logs and ours share one id.\n\n## Pagination\n\nA list with `next_cursor` is paged: pass it back as `cursor` for the next page, and stop\nwhen it is `null`. Cursors are opaque; do not build one. `limit` is clamped, not rejected.\nSome lists are short by nature and answer `{ \"data\": [...] }` with no cursor at all\n(a page's links, a conversation's messages, webhooks, the webhook event catalog, a\nwebhook's delivery attempts); three always answer `next_cursor: null` (accounts,\nautomations, API keys).\n\n## Idempotency\n\nAny `POST` accepts an `Idempotency-Key` header (1 to 255 printable ASCII characters; a UUID\nworks). Repeat the same request with the same key within 24 hours and you get the first\nanswer again, with `Idempotent-Replay: true`, instead of a second page, message or post.\nThe same key with a different request is `409 IDEMPOTENCY_KEY_CONFLICT`; the same key while\nthe first request is still running is `409 IDEMPOTENCY_KEY_IN_FLIGHT`. Only a success is\nkept: after a refusal, fix the request and retry with the same key. Keys are scoped to your\nworkspace and the operation.\n\n## Versioning\n\n`/v1` changes only by addition: new operations, new optional fields, new error codes, new\nenum values in responses. Tolerate fields you do not know. Removing or renaming anything,\nor changing a status code, ships as `/v2`, with `/v1` kept alongside.\n\n## Not in this API\n\nA key cannot block a person, or read payout details or exports. A person does each in the\ndashboard.\n\nManaging keys and webhooks IS here, on `keys:write` and `webhooks:write`, so that rotating a\ncredential on a schedule is a cron job rather than somebody clicking. Both are refused to an\nAI assistant's key, which cannot carry either scope at all: a credential acting on its own\nshould not be able to issue credentials or point your data at a new address.\n",
    "contact": {
      "name": "BoltClaw support",
      "url": "https://boltclaw.ai/support"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://boltclaw.ai/terms"
    }
  },
  "servers": [
    {
      "url": "https://api.boltclaw.ai/v1",
      "description": "Production. https://boltclaw.ai/api/v1 remains a supported alias that reaches the same gateway."
    }
  ],
  "externalDocs": {
    "description": "The API reference",
    "url": "https://boltclaw.ai/docs/api"
  },
  "tags": [
    {
      "name": "Meta",
      "description": "Your key and its workspace."
    },
    {
      "name": "API keys",
      "description": "The workspace's API keys, and what each may do."
    },
    {
      "name": "Pages",
      "description": "Link-in-bio pages, served at boltclaw.ai/{slug}."
    },
    {
      "name": "Links",
      "description": "The links on a page, in display order."
    },
    {
      "name": "Conversations",
      "description": "Threads people start from a page, and the messages in them."
    },
    {
      "name": "Webhooks",
      "description": "The endpoints registered to receive events. Read only here."
    },
    {
      "name": "Social accounts",
      "description": "Connected network accounts. Connecting one happens in the dashboard."
    },
    {
      "name": "Posts",
      "description": "Posts to one or more connected accounts, now, scheduled, or as drafts."
    },
    {
      "name": "Media",
      "description": "Images and videos for posts, uploaded in two steps."
    },
    {
      "name": "Inbox",
      "description": "Comments, mentions and direct messages from every connected account."
    },
    {
      "name": "Automations",
      "description": "Comment-to-DM rules that reply to people and capture leads."
    },
    {
      "name": "Leads",
      "description": "Contact details people chose to give."
    },
    {
      "name": "Usage",
      "description": "The organisation's plan, allowances and platform charges."
    },
    {
      "name": "Referrals",
      "description": "Referral programs, partners, conversions, commissions and payouts."
    },
    {
      "name": "Agents",
      "description": "Device sign-in, approvals, and a key acting on itself."
    },
    {
      "name": "Logs",
      "description": "What assistants did here, and what everybody did here."
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/ping": {
      "get": {
        "operationId": "ping",
        "tags": [
          "Meta"
        ],
        "summary": "Check a key",
        "description": "The cheapest authenticated call: confirms the key works and returns the scopes it carries.\nNeeds no scope. Use it during setup and as a liveness probe.\n",
        "x-scope": "none",
        "x-function": "api-meta",
        "x-rate-limit-class": "read",
        "responses": {
          "200": {
            "description": "The key is valid.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ping"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/workspace": {
      "get": {
        "operationId": "getWorkspace",
        "tags": [
          "Meta"
        ],
        "summary": "Read the key's workspace",
        "description": "The workspace this key belongs to: its id and its name. Needs no scope. The id appears in\nno other response; it is how you tell two keys apart.\n",
        "x-scope": "none",
        "x-function": "api-meta",
        "x-rate-limit-class": "read",
        "responses": {
          "200": {
            "description": "The key's workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workspace"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/keys": {
      "get": {
        "operationId": "listApiKeys",
        "tags": [
          "API keys"
        ],
        "summary": "List API keys",
        "description": "Every key this workspace holds, newest first, including revoked and expired ones: the row\nis the record that a credential was issued here and stopped working. Never the keys\nthemselves, which are not stored.\n\nRead `lost_scopes` on each row. Anything in it means the person who created that key no\nlonger holds the permission a scope stands on, so the key is quietly doing less than it\nwas issued to do.\n",
        "x-scope": "keys:read",
        "x-permission": "api_keys.manage",
        "x-function": "api-keys",
        "x-rate-limit-class": "read",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit100"
          }
        ],
        "responses": {
          "200": {
            "description": "The workspace's keys. Never paged; `next_cursor` is always null.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createApiKey",
        "tags": [
          "API keys"
        ],
        "summary": "Create an API key",
        "description": "Issues a key for this workspace and returns it ONCE, as `secret`. Only a hash is stored,\nso no endpoint can produce it again: store it where your servers read their secrets, and\nrotate it if you lose it.\n\n`Idempotency-Key` is required. Minting a credential is the one operation where a retried\nnetwork timeout cannot be told apart from a request that never landed, and the only safe\nmove without a key would be to create a second credential. A replay answers the same body\nwith `secret: null`.\n\nWhat you asked for is not necessarily what you get. The scopes are bounded twice: by the\nscopes the calling key itself carries, and by the permissions the person who created the\ncalling key still holds. Anything left off is listed in `dropped`, and the key is still\ncreated. Nothing left at all is `403 INSUFFICIENT_SCOPE`.\n\nThe expiry is clamped to the calling key's own, so a child never outlives its parent. The\norganisation's plan sets how many live keys a workspace may hold (`402 PLAN_LIMIT`, and\nthe message names the number).\n",
        "x-scope": "keys:write",
        "x-permission": "api_keys.manage",
        "x-function": "api-keys",
        "x-rate-limit-class": "write",
        "x-agent-consequence": "Issues a working credential for this workspace, returned once.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyRequired"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApiKeyCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created. `secret` is the key, and this is the only response that carries it.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              },
              "Location": {
                "$ref": "#/components/headers/Location"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/keys/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "patch": {
        "operationId": "updateApiKey",
        "tags": [
          "API keys"
        ],
        "summary": "Rename a key, or change its scopes",
        "description": "Changes the name, the scopes, or both. The key itself is untouched, so whatever holds it\nkeeps working: narrowing a key no longer means replacing it everywhere it is deployed.\n\n`scopes` is the new set in full, not a list to add. Adding one is bounded exactly as\ncreating a key is, and anything left off is in `dropped`. Removing one is always allowed.\n",
        "x-scope": "keys:write",
        "x-permission": "api_keys.manage",
        "x-function": "api-keys",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Changes what a live credential may do, at once.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApiKeyUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The key, as it now is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyUpdated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "revokeApiKey",
        "tags": [
          "API keys"
        ],
        "summary": "Revoke a key",
        "description": "Stops the key working, at once and for good. The row stays, so a credential that turns up\nin a log next month is still identifiable as one that was issued here and switched off.\n\nRevoking a key twice is `404`, like a key that does not exist and like another\nworkspace's: the second call would otherwise move the revocation time and erase when it\nhappened.\n",
        "x-scope": "keys:write",
        "x-permission": "api_keys.manage",
        "x-function": "api-keys",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Stops a credential working at once, and anything using it fails.",
        "responses": {
          "200": {
            "description": "The key, now revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKey"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/keys/{id}/rotate": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "rotateApiKey",
        "tags": [
          "API keys"
        ],
        "summary": "Rotate a key",
        "description": "Issues a replacement with the same scopes and the same expiry, and retires this one. The\nreplacement is returned ONCE, as `secret`.\n\nTHE REPLACEMENT HAS A NEW ID. A caller that stores the id as well as the value has to\nstore both again. `rotated_from` on the new key names the old one.\n\n`grace_seconds` keeps the old key working for a while, so the new one can reach every\ndeployment before anything breaks. Zero, the default, stops it at once, which is what you\nwant when you are rotating because the old value leaked. The answer says exactly when the\nold key stops, in `previous_valid_until`.\n\n`Idempotency-Key` is required, for the same reason it is on creating a key. A replay\nanswers `secret: null`.\n",
        "x-scope": "keys:write",
        "x-permission": "api_keys.manage",
        "x-function": "api-keys",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Issues a replacement credential and retires this one, which stops working.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyRequired"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApiKeyRotate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The replacement. `secret` is the new key, and the only response carrying it.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              },
              "Location": {
                "$ref": "#/components/headers/Location"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyRotated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/pages": {
      "get": {
        "operationId": "listPages",
        "tags": [
          "Pages"
        ],
        "summary": "List pages",
        "description": "The workspace's pages, newest first.",
        "x-scope": "pages:read",
        "x-permission": "pages.view",
        "x-function": "public-api",
        "x-rate-limit-class": "read",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit200"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of pages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createPage",
        "tags": [
          "Pages"
        ],
        "summary": "Create a page",
        "description": "Creates a page at boltclaw.ai/{slug}. Slugs are global: `409 CONFLICT` means the name is\ntaken, by anyone. A reserved slug is `400`. The organisation's plan sets how many pages it\nmay hold and publish (`402 PLAN_LIMIT`). This operation draws on its own budget of 10\nattempts an hour per key (`429`).\n",
        "x-scope": "pages:write",
        "x-permission": "pages.create",
        "x-function": "public-api",
        "x-rate-limit-class": "write",
        "x-makes-public": true,
        "x-agent-consequence": "Creates a page; with published true it is live at boltclaw.ai/{slug} at once.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PageCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Page"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/pages/{slug}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        }
      ],
      "get": {
        "operationId": "getPage",
        "tags": [
          "Pages"
        ],
        "summary": "Read a page",
        "description": "One of the workspace's pages, published or not.",
        "x-scope": "pages:read",
        "x-permission": "pages.view",
        "x-function": "public-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Page"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "updatePage",
        "tags": [
          "Pages"
        ],
        "summary": "Update a page",
        "description": "Changes the fields you send. The slug cannot change: it is the page's public address, and\nrenaming it would break every link and QR code pointing at it. Publishing a page counts\nagainst the plan's published-page allowance (`402 PLAN_LIMIT`).\n",
        "x-scope": "pages:write",
        "x-permission": "pages.edit",
        "x-function": "public-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "page.unpublish",
        "x-boltclaw-approval-when": "published is false",
        "x-cross-tenant": 404,
        "x-makes-public": true,
        "x-agent-consequence": "Changes a page; on a published page the change is live at once, and published false takes it offline.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PageUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Page"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deletePage",
        "tags": [
          "Pages"
        ],
        "summary": "Delete a page",
        "description": "Deletes the page, its links and its statistics. People who messaged it keep a read-only\ncopy of their own conversation; your access to those conversations ends. Needs its own\nscope, `pages:delete`, which `pages:write` does not include.\n",
        "x-scope": "pages:delete",
        "x-permission": "pages.delete",
        "x-function": "public-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "page.delete",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Permanently deletes the page, its links and its statistics, and ends access to its conversations.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageDeleted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/pages/{slug}/links": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        }
      ],
      "get": {
        "operationId": "listLinks",
        "tags": [
          "Links"
        ],
        "summary": "List a page's links",
        "description": "Every link on the page, in display order, with no cursor. Links are unlimited in practice:\na page holds at most 1,000 links, a safety ceiling the same on every plan\n(`links_per_page` in `GET /usage`). A page that already had more when its ceiling was\nlowered keeps every one of them, and all of them are listed here.\n",
        "x-scope": "pages:read",
        "x-permission": "pages.view",
        "x-function": "public-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The links.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createLink",
        "tags": [
          "Links"
        ],
        "summary": "Add a link",
        "description": "Adds a link to the page. A page holds at most 1,000 links, a safety ceiling the same on\nevery plan (`links_per_page` in `GET /usage`): adding one to a page at its ceiling is\n`409 CONFLICT`. A page that had more when its ceiling was lowered keeps them all, and you can\nstill edit, reorder and remove them, but nothing is added to it until it is under the\nceiling. A `label` longer than your organisation's `link_label_chars` (120 by default) is\n`400`.\n",
        "x-scope": "pages:write",
        "x-permission": "pages.edit",
        "x-function": "public-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-makes-public": true,
        "x-agent-consequence": "Adds a link to the page; on a published page it is visible at once.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LinkCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Link"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/pages/{slug}/stats": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        }
      ],
      "get": {
        "operationId": "getPageStats",
        "tags": [
          "Pages"
        ],
        "summary": "Read a page's views and clicks",
        "description": "Views and link clicks between `from` and `to` (the last 30 days by default), counted by\nBoltClaw itself. The answer starts no earlier than your plan's analytics history allows\n(`analytics_days` in `GET /usage`: 30 days on Free), whatever `from` says; the window\nactually used is echoed back, so a `from` later than the one you sent means the plan's\nhistory ended there. Nothing older is deleted: a larger plan sees it at once.\n",
        "x-scope": "pages:read",
        "x-permission": "analytics.view",
        "x-function": "public-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "An ISO-8601 date or timestamp. Default 30 days before `to`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "An ISO-8601 date or timestamp, later than `from`. Default now.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The counts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageStats"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/links/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "patch": {
        "operationId": "updateLink",
        "tags": [
          "Links"
        ],
        "summary": "Update a link",
        "description": "Changes the fields you send.",
        "x-scope": "pages:write",
        "x-permission": "pages.edit",
        "x-function": "public-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-makes-public": true,
        "x-agent-consequence": "Changes a link; on a published page the change is visible at once.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LinkUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated link.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Link"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteLink",
        "tags": [
          "Links"
        ],
        "summary": "Remove a link",
        "description": "Removes the link from its page. Ordinary page editing, so `pages:write` is enough.",
        "x-scope": "pages:write",
        "x-permission": "pages.edit",
        "x-function": "public-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "link.delete",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Removes the link from the page at once.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/inquiries": {
      "get": {
        "operationId": "listInquiries",
        "tags": [
          "Conversations"
        ],
        "summary": "List conversations",
        "description": "Conversations on the workspace's pages, most recently active first. Poll with\n`since=<your last poll>` to get every thread that has had a message since.\n",
        "x-scope": "inquiries:read",
        "x-permission": "messages.view",
        "x-function": "public-api",
        "x-rate-limit-class": "read",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit200"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Only threads with a message at or after this ISO-8601 date or timestamp.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "slug",
            "in": "query",
            "required": false,
            "description": "Only threads on this page.",
            "schema": {
              "$ref": "#/components/schemas/Slug"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of conversations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InquiryList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/inquiries/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "patch": {
        "operationId": "updateInquiry",
        "tags": [
          "Conversations"
        ],
        "summary": "Set a conversation's status",
        "description": "The status in your inbox, and nothing else. The words cannot be edited.",
        "x-scope": "inquiries:write",
        "x-permission": "messages.manage",
        "x-function": "public-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Changes the conversation's status in the creator's inbox.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InquiryUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The conversation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Inquiry"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/inquiries/{id}/messages": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listInquiryMessages",
        "tags": [
          "Conversations"
        ],
        "summary": "Read a conversation",
        "description": "The messages, oldest first. Reading them does not mark them read; call\n`POST /inquiries/{id}/read` when a person has actually opened the thread.\n",
        "x-scope": "inquiries:read",
        "x-permission": "messages.view",
        "x-function": "public-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit200"
          }
        ],
        "responses": {
          "200": {
            "description": "The messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "sendInquiryMessage",
        "tags": [
          "Conversations"
        ],
        "summary": "Reply in a conversation",
        "description": "Sends a message to the person who wrote in, from your page; they are emailed. A closed\nconversation is `409 CONFLICT`. Limited to 60 an hour per key and 30 an hour per\nconversation by default (`429`), the same per-conversation number the dashboard is held\nto; your plan's numbers are `api_messages_per_hour` and `thread_messages_per_hour` in\n`GET /usage`.\n",
        "x-scope": "inquiries:write",
        "x-permission": "messages.reply",
        "x-function": "public-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "conversation.reply",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Sends a message to a real person as the creator's page, and emails it to them.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sent.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageSent"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/inquiries/{id}/close": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "setInquiryClosed",
        "tags": [
          "Conversations"
        ],
        "summary": "Close or re-open a conversation",
        "description": "While closed, neither side can write. Re-opening can collide with a newer open thread from\nthe same person on the same page (`409 CONFLICT`). Closing is not blocking; blocking a\nperson happens in the dashboard.\n",
        "x-scope": "inquiries:write",
        "x-permission": "messages.manage",
        "x-function": "public-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Closes or re-opens the conversation; while it is closed neither side can write.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InquiryClose"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The conversation.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Inquiry"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/inquiries/{id}/read": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "markInquiryRead",
        "tags": [
          "Conversations"
        ],
        "summary": "Mark a conversation read",
        "description": "Marks every unread message from the person who wrote in as read, which they see as a read\nreceipt. Call it when a person opens the thread, never from a background poll. Takes no\nbody; a second call marks nothing.\n",
        "x-scope": "inquiries:write",
        "x-permission": "messages.view",
        "x-function": "public-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Tells the person who wrote in that their messages were read.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "How many messages were marked.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MarkedRead"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/webhooks/events": {
      "get": {
        "operationId": "listWebhookEventTypes",
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhook event types",
        "description": "Every event an endpoint can subscribe to, with the object each one is about. Read this\nrather than hard-coding the list: an event added later appears here, and an endpoint that\nsubscribed to everything receives it without being changed.\n",
        "x-scope": "webhooks:read",
        "x-permission": "webhooks.manage",
        "x-function": "api-webhooks",
        "x-rate-limit-class": "read",
        "responses": {
          "200": {
            "description": "The catalog, in the order the dashboard shows it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEventTypeList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/webhooks": {
      "get": {
        "operationId": "listWebhooks",
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhook endpoints",
        "description": "The endpoints registered for this workspace, to see where events go and why one did not\narrive.\n\nNeeds `webhooks:read`. A key issued with `inquiries:read` before this operation had a scope\nof its own is still accepted here, and that acceptance will be withdrawn: issue new keys\nwith `webhooks:read`.\n",
        "x-scope": "webhooks:read",
        "x-any-scopes": [
          "webhooks:read",
          "inquiries:read"
        ],
        "x-permission": "webhooks.manage",
        "x-function": "api-webhooks",
        "x-rate-limit-class": "read",
        "responses": {
          "200": {
            "description": "The endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Register a webhook endpoint",
        "description": "Register a URL and receive the signing secret, once.\n\nThe URL must be `https` and must resolve to a public host. Loopback, private ranges and\nbare IP addresses are refused here and again at every delivery, because a hostname that is\npublic today can point somewhere else tomorrow.\n\n`events` left out means every event, now and in future, which is what you want unless you\nhave a reason not to. An explicit list is validated against the catalog and an unknown name\nis refused rather than dropped. `page_slugs` left out means every page in the workspace;\nevents that are not about a page are only ever sent to an endpoint that covers all of them.\n\n`Idempotency-Key` is required. Minting a credential is the one operation where a retried\nnetwork timeout cannot be told apart from a request that never landed, and the only safe\nmove without a key would be to create a second endpoint. A replay answers the same body\nwith `secret: null`.\n\nFive endpoints per workspace by default (`409`); your plan's number is\n`webhook_endpoints` in `GET /usage`.\n",
        "x-scope": "webhooks:write",
        "x-permission": "webhooks.manage",
        "x-function": "api-webhooks",
        "x-rate-limit-class": "write",
        "x-agent-consequence": "Starts sending this workspace's events to an address outside Bolt Claw.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyRequired"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created. `secret` is the signing secret, and this is the only response that carries it.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              },
              "Location": {
                "$ref": "#/components/headers/Location"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/webhooks/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Read one webhook endpoint",
        "description": "One endpoint's configuration and its delivery health. Never its signing secret.",
        "x-scope": "webhooks:read",
        "x-permission": "webhooks.manage",
        "x-function": "api-webhooks",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "updateWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Change a webhook endpoint",
        "description": "Change the URL, the events, the pages, the description, or switch the endpoint on and off.\nSend only what you want to change.\n\n`enabled: false` is a pause: the endpoint and its signing secret survive, and events stop.\nThat is what you want while a receiver is being redeployed: deleting the endpoint would\ndestroy the secret every other environment is still using. `enabled: true` also clears the\nfailure count, or the next single failure would switch it off again however long it had\nbeen working in between.\n\nEvents missed while an endpoint is off are not replayed.\n",
        "x-scope": "webhooks:write",
        "x-permission": "webhooks.manage",
        "x-function": "api-webhooks",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The endpoint as it now is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete a webhook endpoint",
        "description": "Deletes the endpoint, its queued deliveries and its delivery log. The signing secret goes\nwith it and cannot be recovered.\n\nTo stop events without losing any of that, send `enabled: false` instead.\n",
        "x-scope": "webhooks:write",
        "x-permission": "webhooks.manage",
        "x-function": "api-webhooks",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Deletes the endpoint, its queued deliveries, its delivery log and its signing secret.",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDeleted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/webhooks/{id}/rotate-secret": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "rotateWebhookSecret",
        "tags": [
          "Webhooks"
        ],
        "summary": "Rotate a signing secret",
        "description": "Replace the signing secret and receive the new one, once. The endpoint keeps its id, its\nURL and its subscriptions.\n\n**There is no grace window, and that is deliberate.** Rotating an API key leaves the old\none working for a while because many systems hold it and we are the ones verifying it. A\nsigning secret is verified by one receiver you control, so a window in which two secrets\nboth validate is a window in which a leaked secret still validates, which is the only\nreason anybody rotates one in a hurry. The old secret stops at once: paste the new one into\nyour receiver, and the deliveries in between fail their signature check and are retried.\n\n`Idempotency-Key` is required, for the same reason it is on registering an endpoint. A\nreplay answers `secret: null`.\n",
        "x-scope": "webhooks:write",
        "x-permission": "webhooks.manage",
        "x-function": "api-webhooks",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Stops the current signing secret working immediately and issues a replacement, returned once.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyRequired"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmptyBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rotated. `secret` is the new signing secret, and the only response carrying it.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSecretRotated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/webhooks/{id}/deliveries": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listWebhookDeliveries",
        "tags": [
          "Webhooks"
        ],
        "summary": "List delivery attempts",
        "description": "One row per attempt, newest first: the exact body that was signed, the status your endpoint\nanswered with, up to 512 bytes of that answer, how long it took, and why it failed when it\ndid.\n\n`delivery_id` is stable across every attempt of one occurrence, and is the value the\n`X-BoltClaw-Delivery` header carries. Key your idempotency off it.\n\nAttempts are kept for 30 days. The reason for a failure is one of a fixed set of sentences,\nnever your own server's words.\n",
        "x-scope": "webhooks:read",
        "x-permission": "webhooks.manage",
        "x-function": "api-webhooks",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit100"
          },
          {
            "name": "event",
            "in": "query",
            "required": false,
            "description": "Only attempts for this event. An unknown name is `400`.",
            "schema": {
              "$ref": "#/components/schemas/WebhookEvent"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The attempts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDeliveryList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/webhooks/{id}/deliveries/{delivery_id}/redeliver": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        },
        {
          "name": "delivery_id",
          "in": "path",
          "required": true,
          "description": "The `delivery_id` of an attempt. Anything that is not one of yours is `404`.",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "post": {
        "operationId": "redeliverWebhookDelivery",
        "tags": [
          "Webhooks"
        ],
        "summary": "Send a delivery again",
        "description": "Queue the same body again, signed afresh. The retry ladder starts over, and the body is the\none that was frozen when the event happened, so your receiver sees the event it already\nknows how to deduplicate rather than a second, different fact.\n\nSixty redeliveries per workspace per hour (`429`). A paused endpoint is `409`.\n",
        "x-scope": "webhooks:write",
        "x-permission": "webhooks.manage",
        "x-function": "api-webhooks",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmptyBody"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued. The delivery has not happened yet.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookQueued"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/webhooks/{id}/test": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "sendWebhookTestEvent",
        "tags": [
          "Webhooks"
        ],
        "summary": "Send a test event",
        "description": "Queue a `webhook.test` delivery to this one endpoint, signed exactly as a real event is, so\nyou can prove your receiver verifies a signature before a real event arrives to tell you it\ndoes not.\n\nIt is sent whatever the endpoint has subscribed to and whatever pages it covers: a test you\nhave to subscribe to first cannot tell you whether your subscription works. A paused\nendpoint is `409`.\n",
        "x-scope": "webhooks:write",
        "x-permission": "webhooks.manage",
        "x-function": "api-webhooks",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmptyBody"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued. The delivery has not happened yet.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookTestQueued"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/accounts": {
      "get": {
        "operationId": "listAccounts",
        "tags": [
          "Social accounts"
        ],
        "summary": "List connected accounts",
        "description": "Every connected account and what its network supports. Never paged (`next_cursor` is always null).",
        "x-scope": "social:read",
        "x-permission": "social.view",
        "x-function": "social-api",
        "x-rate-limit-class": "read",
        "responses": {
          "200": {
            "description": "The accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/posts": {
      "get": {
        "operationId": "listPosts",
        "tags": [
          "Posts"
        ],
        "summary": "List posts",
        "description": "The workspace's posts, newest first, optionally filtered.",
        "x-scope": "social:read",
        "x-permission": "social.view",
        "x-function": "social-api",
        "x-rate-limit-class": "read",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/PostStatus"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "An ISO-8601 timestamp with an offset.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "An ISO-8601 timestamp with an offset.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "$ref": "#/components/parameters/Limit200"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of posts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createPost",
        "tags": [
          "Posts"
        ],
        "summary": "Create a post",
        "description": "One post to one or more connected accounts. Say when with exactly one of `scheduled_for`,\n`publish_now: true` or `draft: true`. A post needs a `body` or at least one media id. The\norganisation's monthly post allowance applies (`402 PLAN_LIMIT`). Posting to X is billed\nper call on top of the plan.\n",
        "x-scope": "social:write",
        "x-permission": "posts.create",
        "x-function": "social-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "post.publish",
        "x-boltclaw-approval-when": "draft is not true",
        "x-makes-public": true,
        "x-spends-money": true,
        "x-agent-consequence": "Publishes to the connected social accounts now or at scheduled_for, unless draft is true.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PostCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/Upstream"
          },
          "503": {
            "$ref": "#/components/responses/UpstreamUnavailable"
          }
        }
      }
    },
    "/posts/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getPost",
        "tags": [
          "Posts"
        ],
        "summary": "Read a post",
        "description": "The post, its media and where it stands on each account.",
        "x-scope": "social:read",
        "x-permission": "social.view",
        "x-function": "social-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The post.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "updatePost",
        "tags": [
          "Posts"
        ],
        "summary": "Update a post",
        "description": "Changes a draft or scheduled post. Any other is `409 CONFLICT`.",
        "x-scope": "social:write",
        "x-permission": "posts.create",
        "x-function": "social-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-makes-public": true,
        "x-spends-money": true,
        "x-agent-consequence": "Changes a draft or scheduled post, including when it will publish.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PostUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated post.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deletePost",
        "tags": [
          "Posts"
        ],
        "summary": "Delete a post",
        "description": "Deletes the post from BoltClaw.",
        "x-scope": "social:write",
        "x-permission": "posts.delete",
        "x-function": "social-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "post.delete",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Permanently deletes the post from BoltClaw.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/posts/{id}/cancel": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "cancelPost",
        "tags": [
          "Posts"
        ],
        "summary": "Cancel a scheduled post",
        "description": "Stops a post that has not published yet. Takes no body.",
        "x-scope": "social:write",
        "x-permission": "posts.cancel",
        "x-function": "social-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Stops a scheduled post from publishing.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The cancelled post.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/media": {
      "post": {
        "operationId": "createMediaUpload",
        "tags": [
          "Media"
        ],
        "summary": "Start a media upload",
        "description": "Reserves a media item and returns a signed URL to upload the file to, then call\n`POST /media/{id}/complete`. The organisation's media storage allowance applies\n(`402 PLAN_LIMIT`). A replay with the same `Idempotency-Key` answers `upload: null`: the\nupload credential is never kept, so ask for a new slot if you still need one.\n",
        "x-scope": "social:write",
        "x-permission": "posts.create",
        "x-function": "social-api",
        "x-rate-limit-class": "upload",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MediaCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Reserved.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaUpload"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/media/{id}/complete": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "completeMediaUpload",
        "tags": [
          "Media"
        ],
        "summary": "Finish a media upload",
        "description": "Checks the uploaded file and makes the media item ready for posts. Before the upload finishes it is `409 CONFLICT`.",
        "x-scope": "social:write",
        "x-permission": "posts.create",
        "x-function": "social-api",
        "x-rate-limit-class": "upload",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The media item.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Media"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/inbox": {
      "get": {
        "operationId": "listInbox",
        "tags": [
          "Inbox"
        ],
        "summary": "List the inbox",
        "description": "Comments, mentions and messages across connected accounts, newest first. Open items by default.",
        "x-scope": "social:read",
        "x-permission": "inbox.view",
        "x-function": "social-api",
        "x-rate-limit-class": "read",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "done",
                "hidden",
                "all"
              ]
            }
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/InboxKind"
            }
          },
          {
            "name": "account_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "$ref": "#/components/parameters/Limit200"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of items.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InboxList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/inbox/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getInboxThread",
        "tags": [
          "Inbox"
        ],
        "summary": "Read an inbox thread",
        "description": "The item and the rest of its thread, oldest first.",
        "x-scope": "social:read",
        "x-permission": "inbox.view",
        "x-function": "social-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The thread.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InboxThread"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "setInboxStatus",
        "tags": [
          "Inbox"
        ],
        "summary": "Set an item's status",
        "description": "Open or done, in your inbox only. Hiding is `POST /inbox/{id}/hide`.",
        "x-scope": "social:write",
        "x-permission": "inbox.reply",
        "x-function": "social-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InboxStatusUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InboxItem"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/inbox/{id}/reply": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "replyToInboxItem",
        "tags": [
          "Inbox"
        ],
        "summary": "Reply to an item",
        "description": "Replies on the network as the connected account: in public to a comment, privately to a\nmessage. The network's own refusal is `UPSTREAM_ERROR` (422, 502 or 503) with a sentence\nthat says what happened. Replying on X is billed per call on top of the plan.\n",
        "x-scope": "social:write",
        "x-permission": "inbox.reply",
        "x-function": "social-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "inbox.reply",
        "x-cross-tenant": 404,
        "x-makes-public": true,
        "x-spends-money": true,
        "x-agent-consequence": "Posts a reply on the network as the connected account, public for a comment and private for a message.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InboxReply"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The reply, as an outbound item.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InboxItem"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/Upstream"
          },
          "503": {
            "$ref": "#/components/responses/UpstreamUnavailable"
          }
        }
      }
    },
    "/inbox/{id}/hide": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "hideInboxItem",
        "tags": [
          "Inbox"
        ],
        "summary": "Hide or unhide a comment",
        "description": "Hides the comment on the network (an empty body means hide), or `{ \"hidden\": false }`\nunhides it. A network that cannot hide comments is `422`.\n",
        "x-scope": "social:write",
        "x-permission": "inbox.moderate",
        "x-function": "social-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "inbox.hide",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Hides or unhides the comment on the network.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InboxHide"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The item.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InboxItem"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/Upstream"
          },
          "503": {
            "$ref": "#/components/responses/UpstreamUnavailable"
          }
        }
      }
    },
    "/automations": {
      "get": {
        "operationId": "listAutomations",
        "tags": [
          "Automations"
        ],
        "summary": "List automations",
        "description": "Every automation, with its counters. Never paged (`next_cursor` is always null).",
        "x-scope": "social:read",
        "x-permission": "social.view",
        "x-function": "social-api",
        "x-rate-limit-class": "read",
        "responses": {
          "200": {
            "description": "The automations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AutomationList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createAutomation",
        "tags": [
          "Automations"
        ],
        "summary": "Create an automation",
        "description": "A rule that answers a keyword in a comment or a message with a DM, and optionally a public\nreply and a request for an email or phone number. What a network supports is checked\nhere (`400`). The plan sets how many automations an organisation may hold and how many\nDMs a month they may send (`402 PLAN_LIMIT`). DMs on X are billed per call on top of the plan.\n",
        "x-scope": "social:write",
        "x-permission": "automations.manage",
        "x-function": "social-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "automation.enable",
        "x-boltclaw-approval-when": "enabled is not false",
        "x-makes-public": true,
        "x-spends-money": true,
        "x-agent-consequence": "Starts replying to and messaging people automatically as the connected account, while it is enabled.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AutomationCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Automation"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/automations/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "patch": {
        "operationId": "updateAutomation",
        "tags": [
          "Automations"
        ],
        "summary": "Update an automation",
        "description": "Changes the fields you send. The account cannot change; delete it and create another.",
        "x-scope": "social:write",
        "x-permission": "automations.manage",
        "x-function": "social-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "automation.enable",
        "x-boltclaw-approval-when": "enabled is true",
        "x-cross-tenant": 404,
        "x-makes-public": true,
        "x-spends-money": true,
        "x-agent-consequence": "Changes what an automation sends; enabled true starts it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AutomationUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The automation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Automation"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteAutomation",
        "tags": [
          "Automations"
        ],
        "summary": "Delete an automation",
        "description": "Deletes the automation. Leads it captured are kept.",
        "x-scope": "social:write",
        "x-permission": "automations.manage",
        "x-function": "social-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "automation.delete",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Permanently deletes the automation and stops it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/leads": {
      "get": {
        "operationId": "listLeads",
        "tags": [
          "Leads"
        ],
        "summary": "List leads",
        "description": "Contact details people chose to give, newest first. Needs `leads:read`, which no preset includes.",
        "x-scope": "leads:read",
        "x-permission": "leads.view",
        "x-function": "social-api",
        "x-rate-limit-class": "read",
        "parameters": [
          {
            "name": "account_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "$ref": "#/components/parameters/Limit200"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of leads.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeadList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/leads/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "delete": {
        "operationId": "deleteLead",
        "tags": [
          "Leads"
        ],
        "summary": "Delete a lead",
        "description": "Deletes the lead's contact details. Needs `leads:read` and `social:write` together.",
        "x-scope": "leads:read",
        "x-also-scopes": [
          "social:write"
        ],
        "x-permission": "leads.delete",
        "x-function": "social-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "lead.delete",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Permanently deletes the lead's contact details.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/usage": {
      "get": {
        "operationId": "getUsage",
        "tags": [
          "Usage"
        ],
        "summary": "Read plan usage",
        "description": "The organisation's plan, its allowances, this month's use, and the platform charges billed\non top. The charges are the organisation's bill, so they are shown only when the person who\ncreated the key may manage billing; for everybody else `platform_charges` is `null`.\n",
        "x-scope": "social:read",
        "x-permission": "social.view",
        "x-function": "social-api",
        "x-rate-limit-class": "read",
        "responses": {
          "200": {
            "description": "Usage.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Usage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/conversions": {
      "post": {
        "operationId": "recordReferralConversion",
        "tags": [
          "Referrals"
        ],
        "summary": "Report a conversion",
        "description": "A lead, sale, refund or dispute. `event_id` is YOUR id for the event and the idempotency\nkey: to retry, send the SAME `event_id`. A repeat answers HTTP `200` with\n`duplicate: true` and changes nothing. A `409 CONFLICT` means another event for this\ncustomer was being recorded at that moment: send the same request again. Never make up a\nnew `event_id` to retry: that is a new conversion, and can pay a partner twice.\n\nA repeat answers with the conversion as it was first recorded. A lead or a sale needs\n`customer`; a sale needs `amount` and `currency`; a refund or dispute needs `refund_of`\n(the sale's `event_id`), and a refund its `amount`. Amounts are integers in minor units.\nNeeds `conversions:write`, which does nothing else, so it can sit in a checkout server.\n",
        "x-scope": "conversions:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-idempotent": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Records a lead, sale, refund or dispute; a sale can earn a partner a commission they will be paid.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConversionCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already reported; the first conversion, unchanged.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Conversion"
                }
              }
            }
          },
          "201": {
            "description": "Recorded.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Conversion"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs": {
      "get": {
        "operationId": "listReferralPrograms",
        "tags": [
          "Referrals"
        ],
        "summary": "List programs",
        "description": "The workspace's referral programs.",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit100"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of programs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProgramList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createReferralProgram",
        "tags": [
          "Referrals"
        ],
        "summary": "Create a program",
        "description": "A referral program with a public join page partners apply through. Values are checked\nhere: an https `product_url`, `cookie_days` from 1 to 365, a known `attribution_model`,\n`payout_schedule` and `join_mode` (`400`). The plan sets how many programs an\norganisation may run.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-makes-public": true,
        "x-agent-consequence": "Creates a program with a public join page partners can apply through.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProgramCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Program"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralProgram",
        "tags": [
          "Referrals"
        ],
        "summary": "Read a program",
        "description": "The program, its join link and how many partners are waiting for a decision.",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The program.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Program"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "updateReferralProgram",
        "tags": [
          "Referrals"
        ],
        "summary": "Update a program",
        "description": "Changes the fields you send; a field left out keeps its value. Values are checked as on create.\n\nSending `terms` saves new program terms and raises `terms_version`: every partner must accept\nthe new version before their next payout. An AI assistant's key is asked first when the body\ncarries `terms`, unless your organisation lets assistants save terms without asking. Draft\nterms from the program's own settings with `POST /v1/referrals/programs/{id}/terms/draft`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.terms.update",
        "x-boltclaw-approval-when": "terms is sent",
        "x-cross-tenant": 404,
        "x-makes-public": true,
        "x-agent-consequence": "Changes a program; its join page and terms change for partners at once, and new terms must be accepted by every partner before their next payout.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProgramUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The program.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Program"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/partners": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralPartners",
        "tags": [
          "Referrals"
        ],
        "summary": "List a program's partners",
        "description": "Partners by their name in this program, with all-time stats. An email appears only where the partner chose to share it. `q` keeps the partners whose display name contains it, ignoring case.",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/PartnerStatus"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "1 to 80 characters, without `*`. Keeps partners whose display name contains it, ignoring case.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            }
          },
          {
            "$ref": "#/components/parameters/Limit100"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of partners.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/partners/{id}/approve": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "approveReferralPartner",
        "tags": [
          "Referrals"
        ],
        "summary": "Approve a partner",
        "description": "Approves an application. The partner gets their links, starts earning, and is emailed at\nonce. Approving a partner who is already approved changes nothing and emails nobody, so a\nretry is safe. Takes no body.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.partner.approve",
        "x-boltclaw-approval-always": true,
        "x-idempotent": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Approves the partner, who is emailed at once and then earns commissions on their referrals.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The partner.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Partner"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/partners/{id}/reject": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "rejectReferralPartner",
        "tags": [
          "Referrals"
        ],
        "summary": "Reject a partner",
        "description": "Rejects an application. Takes no body.",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Rejects the partner's application.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The partner.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Partner"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/commissions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralCommissions",
        "tags": [
          "Referrals"
        ],
        "summary": "List a program's commissions",
        "description": "Commissions, newest first, each with its conversion and partner.",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/CommissionStatus"
            }
          },
          {
            "name": "partner_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          },
          {
            "name": "adjustments",
            "in": "query",
            "required": false,
            "description": "`true` for adjustments only, `false` for earned commissions only.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Limit100"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of commissions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommissionList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/commissions/{id}/adjust": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "adjustReferralCommission",
        "tags": [
          "Referrals"
        ],
        "summary": "Adjust a commission",
        "description": "Adds an adjustment on the same conversion, never an edit in place: a signed amount in\nminor units (not 0) and your reason, which is recorded in the activity log.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.commission.adjust",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Adds a commission adjustment the partner will be paid, or owe; it is audited.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CommissionAdjust"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The adjustment.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Commission"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/payouts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralPayouts",
        "tags": [
          "Referrals"
        ],
        "summary": "List a program's payouts",
        "description": "Payouts, newest first. Payout details and export files stay in the dashboard.",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/PayoutStatus"
            }
          },
          {
            "$ref": "#/components/parameters/Limit100"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of payouts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/payouts/{id}/paid": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "markReferralPayoutPaid",
        "tags": [
          "Referrals"
        ],
        "summary": "Mark a payout paid",
        "description": "You paid outside BoltClaw; this records it with your reference. Send `expected_minor` (the\namount you were shown): if refunds have moved the total since, nothing is marked and the\nanswer is `409 CONFLICT` naming the new figure. Recorded in the activity log.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.payout.paid",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Marks the payout paid with your reference and tells the partner; it is audited.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PayoutMarkPaid"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The payout.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payout"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/payout-providers": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralPayoutProviders",
        "tags": [
          "Referrals"
        ],
        "summary": "List a program's payout providers",
        "description": "The payout accounts this program can pay through, by name and state: your own PayPal,\nWise, Stripe, Tremendous, Trolley or Tango account, or your own payout system (`webhook`).\nNever a credential. Connecting or disconnecting one is done in the dashboard, on the\nPayouts tab.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The connections.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutProviderList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/payout-providers/{id}/test": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "testReferralPayoutProvider",
        "tags": [
          "Referrals"
        ],
        "summary": "Test a payout provider connection",
        "description": "Makes one harmless read at the provider with the stored credential and records whether it\nworks, and why not. Moves no money.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The connection, as the test left it.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutProvider"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/payout-settings": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralPayoutSettings",
        "tags": [
          "Referrals"
        ],
        "summary": "Get a program's payout settings",
        "description": "How the program pays: its provider, the providers a promoter may choose among, the\nschedule, sending on a schedule (available only after a legal review), and the largest\npayout sent without a second confirmation.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutSettings"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "updateReferralPayoutSettings",
        "tags": [
          "Referrals"
        ],
        "summary": "Change how a program pays",
        "description": "Choose the provider that pays promoters, the providers they may choose among (each must be\nconnected), the day of the month a monthly batch is made, and an optional largest payout.\n`auto_send` can be turned on only once BoltClaw allows it, after a legal review; until\nthen a person sends every batch. Recorded in the activity log.\n\nThese are a send's own rails, so a key that is not an AI assistant's needs\n`referrals:payouts` as well as `referrals:write`, as sending does; an assistant needs a\nperson's approval.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.payout.settings",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Changes which payout account pays this program's promoters, and how; nothing is sent.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PayoutSettingsUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutSettings"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/payout-preview": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralPayoutPreview",
        "tags": [
          "Referrals"
        ],
        "summary": "See who is ready to be paid",
        "description": "Ready to pay, before any batch: per currency and provider, the promoters a batch would pay\nand the provider's estimated fees (charged by that provider to your account; BoltClaw\ncharges nothing), and every promoter who would wait, with why: payout details changed in\nthe last 48 hours, payouts locked, an open fraud flag, below your minimum, or details the\nprovider needs that they have not given yet.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "Ready to pay.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutPreview"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/payout-batches": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "createReferralPayoutBatch",
        "tags": [
          "Referrals"
        ],
        "summary": "Make a payout batch",
        "description": "Turns every approved commission that can be paid into draft payouts, one per promoter per\ncurrency, each with the provider that pays it. Nothing is sent: the batch waits for\n`send`, a file export, or marking each payout paid. Below the minimum, negative, flagged\nor held waits for the next batch.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "Nothing could be paid now; no batch was made.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutBatchResult"
                }
              }
            }
          },
          "201": {
            "description": "A batch was made.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutBatchResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/payout-batches/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralPayoutBatch",
        "tags": [
          "Referrals"
        ],
        "summary": "Get a payout batch, ready to send",
        "description": "Every payout in the batch with its provider, whether it can be sent now (and why not),\nthe provider's estimated fee, and how it went once sent. `sendable` is exactly what `send`\nwould send: its payout ids, its totals per currency (send them back as\n`expected_totals`), and the payouts large enough to need confirming by name.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The batch.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutBatch"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/payout-batches/{id}/send": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "sendReferralPayoutBatch",
        "tags": [
          "Referrals"
        ],
        "summary": "Send a payout batch through your payout provider",
        "description": "Pays the batch's payouts from YOUR provider account (PayPal, Wise, Stripe, Tremendous,\nTrolley, Tango) or asks your own payout system for each (`referral.payout.requested`).\nBoltClaw never holds the money. `expected_totals` must equal what the batch would send\nnow (`GET /referrals/payout-batches/{id}`, `sendable.totals`), or nothing is sent; a\npayout in `sendable.large` must be named in `confirm_large`. Each payout is sent once,\nwith an idempotency key made from its id; results arrive as `referral.payout.*` events.\n\nNeeds `referrals:payouts`, the key your own payout server holds, or an AI assistant with a\nperson's approval: `referrals:write` alone never sends money. Recorded in the activity log.\n",
        "x-scope": "referrals:write",
        "x-any-scopes": [
          "referrals:write",
          "referrals:payouts"
        ],
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.payout.send",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Sends real money from your payout provider account to every promoter in this batch, for the totals shown.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PayoutSend"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What happened to each payout.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutSendResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/payout-batches/{id}/cancel-auto-send": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "cancelReferralPayoutAutoSend",
        "tags": [
          "Referrals"
        ],
        "summary": "Stop a scheduled send",
        "description": "Stops a batch that was going to be sent by itself after its 24-hour notice. A stop: it\nalways works, and the batch stays for a person to send. Recorded in the activity log.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "Whether a scheduled send was stopped.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AutoSendCancelled"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/payouts/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralPayout",
        "tags": [
          "Referrals"
        ],
        "summary": "Get a payout",
        "description": "One payout, with how it was sent and what its provider said (`delivery`).",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The payout.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payout"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/payouts/{id}/details": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralPayoutDetails",
        "tags": [
          "Referrals"
        ],
        "summary": "Read where a requested payout goes",
        "description": "For YOUR OWN payout system, after `referral.payout.requested`: the promoter's own payout\ndetails for this one payout, while it waits to be paid. Refused (409) while the promoter's\ndetails changed in the last 48 hours or they locked their payouts. Every read is recorded\nin the activity log, as a payout export is. Needs `referrals:payouts`, which an AI\nassistant's key never holds.\n",
        "x-scope": "referrals:payouts",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "Where to send it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/payouts/{id}/result": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "reportReferralPayoutResult",
        "tags": [
          "Referrals"
        ],
        "summary": "Report what your payout system did",
        "description": "For YOUR OWN payout system: the payout was `paid` (with your reference), `failed` or\n`returned` (with a reason). Failed and returned put its commissions back in Ready to pay.\nThe same answer again changes nothing; a payout already settled otherwise is 409. Needs\n`referrals:payouts`, which an AI assistant's key never holds.\n",
        "x-scope": "referrals:payouts",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PayoutResult"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The payout.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payout"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/tax": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralTaxSettings",
        "tags": [
          "Referrals"
        ],
        "summary": "Get a program's tax settings",
        "description": "Whether this program's payouts wait for a tax form (`tax_forms`) or for the promoter's own\ninvoice (`invoice_required`), and whether you issue self-billed invoices for your EU and UK\nbusiness promoters (`self_billing`). Also whether your own tax details are complete, never\nthe details themselves (your EIN, legal name and addresses are entered in the dashboard),\nand the table of which payout providers count toward your 1099-NEC, each with why and its\nsource. BoltClaw prepares these forms; it does not give tax advice or file anything for\nyou. See [Tax forms and 1099s](/docs/referral-tax).\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaxSettings"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "updateReferralTaxSettings",
        "tags": [
          "Referrals"
        ],
        "summary": "Change whether a program's payouts wait for tax forms or invoices",
        "description": "Send only what you change. `tax_forms`: `off`; `us_only` (a US person needs a W-9, and\nanyone else signs a short statement that they are not a US person); or `all` (everyone\nneeds a W-9, W-8BEN or W-8BEN-E). A promoter whose required form is missing, expired or\nreplaced waits in Ready to pay until they sign. `invoice_required`: each payout waits for\nthe promoter's own invoice (a promoter who self-bills never waits for one).\n`self_billing`: you issue the invoices for promoters who accept your self-billing\nagreement; turning it on needs your invoicing details first (`409` until they are\ncomplete). An archived program is `409`. Recorded in the activity log with the values\nbefore and after.\n\nTurning a requirement off lets money that waited for a form be paid, so a key needs\n`referrals:payouts` as well as `referrals:write` for this call, as changing how payouts\nare sent does; an AI assistant needs a person's approval.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.tax.settings",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Changes whether this program's payouts wait for tax forms or invoices; turning a requirement off lets money that waited for a form be paid.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaxSettingsUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaxSettings"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/tax-forms": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralTaxForms",
        "tags": [
          "Referrals"
        ],
        "summary": "List each promoter's tax form status",
        "description": "Every approved or pending promoter of the program with their latest tax form for your\nbusiness: its type (W-9, W-8BEN, W-8BEN-E, or the statement that they are not a US\nperson), revision, status, when it was signed and when it expires. STATUS ONLY: never a\ntax number, a legal name or anything the form says. Opening a form is done in the\ndashboard, with two-step verification. `requirement_met` is false while their payouts\nwait for a form; `waiting_since` and `forfeitable_minor` say since when, and how much.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit100"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of promoters, by membership id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaxFormStatusList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/partners/{id}/forfeit": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "forfeitReferralCommissions",
        "tags": [
          "Referrals"
        ],
        "summary": "Forfeit a promoter's money held for a missing tax form",
        "description": "Gives up, for good, every approved commission of this promoter that is waiting for a tax\nform in this program: they will not be paid it, and no payout will ever include it. Only\nmoney held for a missing, expired or replaced form can be forfeited (`409` otherwise, and\nwhen nothing is waiting); what they earn afterwards is not touched. Send a `reason` of 3 to\n500 characters: it is recorded in the activity log with what was given up. BoltClaw never\nforfeits anything by itself.\n\nNeeds `referrals:payouts` as well as `referrals:write`, as sending money does, or an AI\nassistant with a person's approval: `referrals:write` alone never gives a promoter's money\nup.\n",
        "x-scope": "referrals:write",
        "x-any-scopes": [
          "referrals:write",
          "referrals:payouts"
        ],
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.commission.forfeit",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Gives up, for good, this promoter's approved money that is waiting for a tax form; they will not be paid it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ForfeitRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was given up.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForfeitResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/tax-years/{year}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Year"
        }
      ],
      "get": {
        "operationId": "getReferralTaxYear",
        "tags": [
          "Referrals"
        ],
        "summary": "Get a year's 1099-NEC data",
        "description": "Everyone your referral programs paid in a calendar year, as one person per account, with\nhow each was paid (per provider and currency), their tax form's status, and whether they\nbelong on your 1099-NEC (`included`) and why, in a sentence. Warnings say what to fix\nbefore you file: no form, no tax number, backup withholding (24%) may apply, a replaced\nform, an expired W-8, money in another currency. Only US dollars paid through the\nproviders your 1099 table counts are reportable, against the year's threshold\n(`threshold_minor`). No tax number and no legal name: the files themselves (the IRS\nIRIS CSV, a general CSV and Copy B) are made in the dashboard, with two-step verification.\nBoltClaw never files, never withholds and never checks a tax number with the IRS.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The year.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaxYear"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/payouts/{id}/invoices": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralPayoutInvoices",
        "tags": [
          "Referrals"
        ],
        "summary": "List a payout's invoices",
        "description": "The invoices for one payout, newest first: the self-billed invoice you issued in the\npromoter's name, the promoter's own uploaded invoice, and any they replaced. Each says\nwhether it was accepted, disputed (with the promoter's reason) or replaced. Read a PDF with\n`POST /referrals/invoices/{id}/link`.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The payout's invoices.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/invoices/{id}/link": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "createReferralInvoiceLink",
        "tags": [
          "Referrals"
        ],
        "summary": "Get a download link for an invoice",
        "description": "A link to one invoice's PDF, good for ten minutes and one download. It names a promoter's\nbusiness, so it needs the payouts permission and every link is recorded in the activity\nlog. Treat the link as a secret; ask again for a new one.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The link.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileLink"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/payouts/{id}/statement-link": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "createReferralStatementLink",
        "tags": [
          "Referrals"
        ],
        "summary": "Get a download link for a payout statement",
        "description": "A link to the payout's statement as a PDF (who paid whom, how much, when and for which\nperiod), good for ten minutes and one download. The statement is what a promoter without\na self-billed or uploaded invoice keeps. Treat the link as a secret; ask again for a new\none.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The link.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileLink"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/payouts/{id}/invoice": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "issueReferralInvoice",
        "tags": [
          "Referrals"
        ],
        "summary": "Issue a paid payout's self-billed invoice",
        "description": "For a program that self-bills: issues the self-billed invoice of a paid payout that has\nnone (its promoter's or your own invoicing details were missing when it was paid). A\npayout that already has one answers that invoice, so a repeat changes nothing. `409` with\nwhat is missing when it cannot be issued: the payout is not paid, the promoter does not\nself-bill with this program, your invoicing details are incomplete, or the promoter is in\nyour own country and your domestic VAT rate is not set. Moves no money.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The invoice.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/invoices/{id}/reissue": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "reissueReferralInvoice",
        "tags": [
          "Referrals"
        ],
        "summary": "Issue a corrected invoice for a disputed one",
        "description": "A promoter disputed a self-billed invoice within its 14 days. Fix what they said was wrong\n(ask them to correct their own details, or correct yours in the dashboard), then call this:\nthe disputed invoice becomes `void` and a new one is issued, with the next number in the\npromoter's series, from the details as they are now. It starts its own 14 days. Only a\ndisputed self-billed invoice can be replaced (`409` otherwise). Recorded in the activity\nlog. Moves no money.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Voids the disputed invoice and issues a corrected one in the promoter's name, with a new number; no money moves.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "201": {
            "description": "The corrected invoice.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/brand": {
      "get": {
        "operationId": "getReferralBrand",
        "tags": [
          "Referrals"
        ],
        "summary": "Get the brand your partners see",
        "description": "Your brand as partners see it on your own partner portal, the join page, the in-app panel,\nthe friend's welcome and their emails: its name, logos, colours, corner radius, support\naddress, sender name and whether \"Powered by BoltClaw\" shows. Without `program_id`, the\nworkspace's brand, which every program uses; with it, that program's own override, where a\nfield left `null` follows the workspace's brand. `effective` is what partners of that\nprogram actually see, both merged. `support_email_verified` says whether the support\naddress was confirmed with a code: only then is it the reply-to of partners' emails. See\n[White-label portal](/docs/referral-portal).\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "parameters": [
          {
            "name": "program_id",
            "in": "query",
            "required": false,
            "description": "A program's own override. Leave it out for the workspace's brand.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The brand.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Brand"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "updateReferralBrand",
        "tags": [
          "Referrals"
        ],
        "summary": "Change the brand your partners see",
        "description": "Send only what you change; `null` clears a field (a program's override then follows the\nworkspace's brand again). `name` up to 80 characters; `accent` and `background` as\n`#rrggbb`; `radius` from 0 to 24; `sender_name` up to 60 characters, the name partners'\nemails come from (the address stays BoltClaw's); `support_email`, which becomes the\nreply-to of partners' emails only after you confirm it with a code (changing it unconfirms\nit); `show_powered_by`, off unless you turn it on. A logo or icon is uploaded first with\n`POST /referrals/brand/uploads`, then saved here by sending the `path` it answered as\n`logo_light_path`, `logo_dark_path` or `favicon_path` (or `null` to remove it). With\n`program_id`, the change is that program's override. Recorded in the activity log with the\nnames of the fields changed. An AI assistant asks a person first, unless your organisation\nlets assistants change it without asking.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.brand.update",
        "x-agent-consequence": "Changes the name, logo, colours or support address your referral partners see on your portal, join page, panel and emails.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BrandUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The brand.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Brand"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/brand/uploads": {
      "post": {
        "operationId": "createReferralBrandUpload",
        "tags": [
          "Referrals"
        ],
        "summary": "Get an upload URL for a logo or icon",
        "description": "A signed URL for one file of your brand: `logo_light`, `logo_dark` or `favicon`, as a PNG,\nJPEG or WebP image of at most 2 MiB (no SVG). `PUT` the file's bytes to `upload_url` with\nits content type, then save it with `PATCH /referrals/brand`, sending `path` as the slot's\nfield. Nothing changes until that PATCH: an upload that is never saved is not shown to\nanyone. The URL works for one file and expires (`expires_at`); treat it as a secret.\n`upload_url` is null in a replay of the same `Idempotency-Key`: ask for a new one.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-agent-consequence": "Creates a one-file upload address for your brand's logo or icon; nothing changes until the brand is saved with it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BrandUploadRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Where to upload.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrandUpload"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/brand/support-email/verification": {
      "post": {
        "operationId": "startReferralBrandEmailVerification",
        "tags": [
          "Referrals"
        ],
        "summary": "Send a code to confirm the support address",
        "description": "Emails a six-digit code to the support address saved on the brand (the workspace's, or the\nprogram's with `program_id`), good for 15 minutes. Confirm it with\n`POST /referrals/brand/support-email/verification/confirm`; until then partners' emails\nhave no reply-to. At most five codes a day for one brand and twenty for the workspace\n(`429` past that). `409` when the address is already confirmed, `400` when none is saved.\nThe answer repeats the address the code went to.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-agent-consequence": "Emails a six-digit confirmation code to the support address saved on your brand.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BrandScope"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The code was sent.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrandEmailCodeSent"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/brand/support-email/verification/confirm": {
      "post": {
        "operationId": "confirmReferralBrandEmailVerification",
        "tags": [
          "Referrals"
        ],
        "summary": "Confirm the support address with its code",
        "description": "The six-digit code from the email `POST /referrals/brand/support-email/verification` sent.\nEach code has five tries; a wrong, used or expired one is `400` (send a new one). Once\nconfirmed, the address is the reply-to of partners' emails about the program, until it is\nchanged. Recorded in the activity log.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-agent-consequence": "Confirms the support address on your brand, so partners' replies to your program's emails go to it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BrandEmailCodeConfirm"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The brand, with the address confirmed.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Brand"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/domains": {
      "get": {
        "operationId": "listReferralDomains",
        "tags": [
          "Referrals"
        ],
        "summary": "List your own link and portal domains",
        "description": "Every domain of your own connected to your referral programs: a links domain\n(`go.example.com`, where links read `https://go.example.com/<code>`) or a partner portal\n(`partners.example.com`), each with where it stands and the exact DNS records still to set.\nSee [Your own domain](/docs/referral-domains).\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "responses": {
          "200": {
            "description": "The domains.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DomainList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "addReferralDomain",
        "tags": [
          "Referrals"
        ],
        "summary": "Connect one of your own domains",
        "description": "Claims a subdomain of yours (`go.example.com` for links, `partners.example.com` for the\npartner portal; never a domain's root, a wildcard or an IP address) for every program\n(`all_programs`, the default) or the ones in `program_ids`. Nothing is served yet: the\nanswer's `records` are the exact DNS records to add at your DNS host, in order. First a\nTXT record, `_boltclaw.<host>` with the value `bc-verify=<token>`, which proves the domain\nis yours; once it is seen, the CNAME record our hosting provider recommends for it appears\nin `records` (it differs per domain: never use one from another domain), and sometimes a\nsecond TXT record our hosting provider asks for. Check progress with\n`POST /referrals/domains/{id}/check`. A domain counts against your plan's domains from the\nmoment it is added (`402` past it); `409` when it is already yours, or connected to\nanother account.\n\nAn AI assistant always asks a person first: it decides where your links and portal will\nanswer. In the dashboard, adding a domain also needs two-step verification; over the API\nthat approval stands in for it.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.domain.add",
        "x-boltclaw-approval-always": true,
        "x-agent-consequence": "Connects a domain of yours to your referral links or partner portal; once its DNS records are set, links and pages answer on it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DomainCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The domain, with the records to set.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Domain"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/domains/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralDomain",
        "tags": [
          "Referrals"
        ],
        "summary": "Get one of your domains",
        "description": "One domain: where it stands (`status`, and `problem`, a sentence saying what is in the\nway), the exact DNS records to set and whether each is in place, whether a CAA record on\nyour domain keeps the certificate from being issued, and, once active, its address.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The domain.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Domain"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "removeReferralDomain",
        "tags": [
          "Referrals"
        ],
        "summary": "Remove one of your domains",
        "description": "The domain stops answering at once: links on it stop working (each still works at its\n`https://boltclaw.ai/r/<code>` address) and its portal pages are gone. It is then taken off\nour hosting provider, so no record of ours is left pointing at it; remove your own DNS\nrecords for it too. Recorded in the activity log. An AI assistant always asks a person\nfirst.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.domain.remove",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Removes a domain of yours; every referral link and portal page on it stops working at once.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DomainRemoved"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/domains/{id}/check": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "checkReferralDomain",
        "tags": [
          "Referrals"
        ],
        "summary": "Check a domain's DNS records now",
        "description": "Looks at the domain's records now instead of waiting for the next check (pending domains\nare looked at every ten minutes, active ones every day), and moves it on when they are in\nplace: the TXT record proves the domain is yours, the CNAME record routes it, and then the\ncertificate is issued. At most once every 15 seconds per domain: a check asked sooner\nanswers the domain as it stands. Nothing is ever removed by a check.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Checks a domain's DNS records now and, when they are in place, moves its setup on.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The domain, as it stands after the check.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Domain"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/listing": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralListing",
        "tags": [
          "Referrals"
        ],
        "summary": "Get a program's directory listing",
        "description": "What the partner directory shows about the program, and whether it is listed. The\ndirectory is a page inside BoltClaw that only signed-in accounts can open; it lists only\nprograms whose business switched listing on, and never shows who promotes a program or\nhow many do. `missing` names every condition not met yet (an active program anybody may\napply to, terms, a reward for partners, an organisation at least a week old and not\nsuspended, and a real sale through your billing system or a finished setup checklist);\nlisting is refused until it is empty. `preview` is the card partners will see. `state` is\n`listed`, `unlisted`, `held` (reports from three different accounts hid it until\nBoltClaw reviews it) or `delisted`. See [Recruiting partners](/docs/referral-growth).\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The listing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListingSettings"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "updateReferralListing",
        "tags": [
          "Referrals"
        ],
        "summary": "Change a program's directory listing",
        "description": "Sets what the partner directory shows (`category`, `pitch` of at most 280 characters,\n`countries`, `languages`, the logo's `logo_path` from\n`POST /referrals/programs/{id}/listing/logo`) and whether the program is `listed`. The\nreward summary, cookie window and payout schedule come from the program itself, never\nfrom text you type. Turning `listed` on is `409` with every reason while a condition is\nnot met. An AI assistant asks a person first, because listing publishes the program to\nevery signed-in partner; your organisation may let assistants change it without asking.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.listing.update",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Changes what the partner directory shows about this program, or whether it is listed for every signed-in partner to find.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListingUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The listing, as it stands now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListingSettings"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/listing/logo": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "createReferralListingLogoUpload",
        "tags": [
          "Referrals"
        ],
        "summary": "Get a URL to upload the listing's logo",
        "description": "A signed URL for one PNG, JPEG or WebP file of at most 2 MiB. `PUT` the file's bytes\nthere with its content type, then save it with `PATCH /referrals/programs/{id}/listing`,\nsending the answer's `path` as `logo_path`. The file is accepted only if its bytes are an\nimage of that type. `upload_url` is null in a replay of the same `Idempotency-Key`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Creates a one-time address to upload the program's directory logo to; nothing changes until the listing saves it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageUploadCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Where to upload the file.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageUpload"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/broadcasts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralBroadcasts",
        "tags": [
          "Referrals"
        ],
        "summary": "List a program's broadcasts",
        "description": "The program's messages to its promoters, newest first (at most 50): drafts, ones being\nsent and sent ones, with who they went to and how many were emailed, muted or failed.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The broadcasts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BroadcastList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createReferralBroadcast",
        "tags": [
          "Referrals"
        ],
        "summary": "Write a broadcast to a program's promoters",
        "description": "A draft, to everyone approved in the program, one group or level, or a list of up to 500\npromoters you pick. Nothing is sent until `POST /referrals/broadcasts/{id}/send`. The\n`body` is a small Markdown subset: paragraphs, **bold**, *italic*, `[links](https://...)`\nand lists; no HTML, no images, https links only, and every link is shown with its domain.\n`{name}`, `{link}` and `{code}` are filled with each promoter's own display name, referral\nlink and discount code. Subject at most 120 characters, body at most 5,000.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Writes a draft message to this program's promoters; nothing is sent until it is sent.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BroadcastCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The draft.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Broadcast"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/broadcasts/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralBroadcast",
        "tags": [
          "Referrals"
        ],
        "summary": "Get one broadcast",
        "description": "One broadcast: its subject and Markdown, who it goes to, the hosts it links to, whether\nits current text was test-sent, and once sent, how many promoters it reached: emailed,\nmuted (they still see it on their program page), your own users in the in-app panel\n(`referrers`, never emailed by BoltClaw), failed and skipped.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The broadcast.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Broadcast"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "updateReferralBroadcast",
        "tags": [
          "Referrals"
        ],
        "summary": "Change a draft broadcast",
        "description": "Changes a draft's subject, body or audience. A sent broadcast is a record and is `409`:\nwrite a new one.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Changes a draft message to this program's promoters; nothing is sent.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BroadcastUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The draft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Broadcast"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/broadcasts/{id}/test": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "testReferralBroadcast",
        "tags": [
          "Referrals"
        ],
        "summary": "Send yourself a test of a draft",
        "description": "Emails the draft, as it reads now, to the address of the person who made this API key\nand nobody else, with sample values in `{name}`, `{link}` and `{code}`. At most ten per\ndraft, one every 15 seconds. The dashboard sends a broadcast only after a test of its\ncurrent text.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Emails a test of this draft to the key's own creator only; no promoter receives anything.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The draft, with where the test went.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BroadcastTested"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/broadcasts/{id}/send": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "sendReferralBroadcast",
        "tags": [
          "Referrals"
        ],
        "summary": "Send a broadcast to a program's promoters",
        "description": "Sends the draft. Every approved promoter it names sees it on their program page; those\nwith a BoltClaw account are emailed (unless they muted the program's messages: every email\nhas a one-click unsubscribe), a few a minute; your own users in the in-app panel are never\nemailed by BoltClaw, so `referral.broadcast.sent` tells your webhook, and\n`GET /referrals/broadcasts/{id}/referrers` names them for your own message. A program may\nsend 2 broadcasts in any 24 hours and 10 in any 30 days (`429` past either). An AI\nassistant always asks a person first: it messages people.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.broadcast.send",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Sends this message to the program's promoters; those with an account are emailed, and it cannot be taken back.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The broadcast, sending or sent.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Broadcast"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/broadcasts/{id}/referrers": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralBroadcastReferrers",
        "tags": [
          "Referrals"
        ],
        "summary": "List your own users a broadcast reached",
        "description": "The referrers (your own users, made by your server) a sent broadcast reached, 200 a page,\nby your own id for each (`external_id`), with their name, link and code, so your system can\nsend them the message itself. BoltClaw emails none of them.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of referrers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BroadcastReferrerList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/imports/preview": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "previewReferralImport",
        "tags": [
          "Referrals"
        ],
        "summary": "Check a partner list before importing it",
        "description": "Checks up to 500 rows (as `rows`, or a `csv` with a header row: `name`, `email`, and\noptionally `code`, `group`, `rate`, `balance`, `currency`) against the program and writes\nnothing: rows that would fail and why (a code taken, a person already invited or\npromoting, a group that does not exist, a balance in another currency). Addresses are\nhashed on arrival and never stored. Send a larger file in parts. See\n[Recruiting partners](/docs/referral-growth).\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Checks a list of partners against the program without importing anything.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What an import would do.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportPreview"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/imports": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralImports",
        "tags": [
          "Referrals"
        ],
        "summary": "List a program's imports",
        "description": "The partner lists brought into the program, newest first, with how many people each invited and how many claimed.",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The imports.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "commitReferralImport",
        "tags": [
          "Referrals"
        ],
        "summary": "Import a partner list",
        "description": "Makes an invite reserved for each row, all or nothing (`409` with the first row's reason\nwhen any fails: preview first). Each invite holds that person's vanity code (no one else\ncan take it meanwhile), group, custom rate and opening balance. BoltClaw sends nobody an\nemail: the answer has each person's personal link, and you send it from your own email.\nWhen they sign in through it they are approved (if your plan has room) with their code,\ngroup and rate, and an opening balance becomes a bonus paid by hand (\"Balance brought\nover\") only then. `import_id` continues an import begun by an earlier part of the same\nfile. A file with opening balances also needs `referrals:payouts` on the key. An AI\nassistant always asks a person first. Recorded in the activity log.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.import.commit",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Imports a list of partners as personal invites, with any opening balances they are owed, paid when each person claims their invite.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportCommit"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The import, with each person's link.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportCommitted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/imports/{id}/invites": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralImportInvites",
        "tags": [
          "Referrals"
        ],
        "summary": "List an import's personal links",
        "description": "Everyone in one import with their personal link and whether they claimed it, 1,000 a page,\nfor the file you send from your own email. The links are credentials (each joins one\nperson), so this needs `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of invites.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportInviteList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/library": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralLibrary",
        "tags": [
          "Referrals"
        ],
        "summary": "List a program's file library",
        "description": "The images, text snippets and links partners see on their program page (an item with a\n`group_id` only by that group's), in order. An image item is shown once its file is in\n(`ready`).\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The library.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LibraryItemList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createReferralLibraryItem",
        "tags": [
          "Referrals"
        ],
        "summary": "Add to a program's file library",
        "description": "An `image` (its file follows through `POST /referrals/library/{id}/upload`), a `snippet`\nof at most 2,000 characters whose `{link}` and `{code}` partners see filled with their\nown (with the program's disclosure line after it), or a `link` to a brand guide or press\nkit (https only; its domain is shown). `group_id` shows it to one group only; `in_panel`\nshows a snippet to your own users in the in-app panel too. At most 200 items a program.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Adds an image, a text snippet or a link to the program's file library, for partners to use.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LibraryItemCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The item.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LibraryItem"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/library/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "patch": {
        "operationId": "updateReferralLibraryItem",
        "tags": [
          "Referrals"
        ],
        "summary": "Change a library item",
        "description": "Changes an item's title, text, link, group, place in the order, or whether a snippet is in\nthe panel. For an image, `image_path` saves the file uploaded through\n`POST /referrals/library/{id}/upload`; it is accepted only if its bytes are a PNG, JPEG or\nWebP image.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Changes an item partners see in the program's file library.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LibraryItemUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LibraryItem"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteReferralLibraryItem",
        "tags": [
          "Referrals"
        ],
        "summary": "Delete a library item",
        "description": "The item and its file are gone; partners no longer see it. An AI assistant asks a person\nfirst, unless your organisation lets assistants do it without asking.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.library.delete",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Deletes an item from the program's file library; partners no longer see it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LibraryItemDeleted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/library/{id}/upload": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "createReferralLibraryUpload",
        "tags": [
          "Referrals"
        ],
        "summary": "Get a URL to upload a library image",
        "description": "A signed URL for an image item's file: one PNG, JPEG or WebP of at most 2 MiB (no SVG,\nHTML or PDF). `PUT` the bytes there with their content type, then save it with\n`PATCH /referrals/library/{id}` and the answer's `path` as `image_path`. `upload_url` is\nnull in a replay of the same `Idempotency-Key`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Creates a one-time address to upload a library image to; nothing changes until the item saves it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImageUploadCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Where to upload the file.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImageUpload"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/link-hosts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralLinkHosts",
        "tags": [
          "Referrals"
        ],
        "summary": "List the sites a program's links may go to",
        "description": "A partner's link can open any page of your site: `https://boltclaw.ai/r/<code>?to=/pricing`\ngoes to your product URL's host plus `/pricing`, and `&host=docs.example.com` to another\nof your hosts. Those other hosts are the program's `allowed_hosts`, each used only once\nyou prove it is yours with the TXT record shown here (`_boltclaw.<host>` with the value\n`bc-verify=<token>`), then `POST /referrals/programs/{id}/link-hosts/check`. Anything\nelse falls back to the product URL: a link is never an open redirect.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The hosts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkHostList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/link-hosts/check": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "checkReferralLinkHost",
        "tags": [
          "Referrals"
        ],
        "summary": "Check a site's TXT record now",
        "description": "Looks up `_boltclaw.<host>` now for one of the program's allowed hosts; when it holds the\nvalue shown, links may go to that host. A check that finds the record gone takes that\naway again. At most once every 10 seconds per host.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Checks one of your sites' DNS records now, so the program's links may go to it once it is proven yours.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "host"
                ],
                "additionalProperties": false,
                "properties": {
                  "host": {
                    "type": "string",
                    "maxLength": 300,
                    "description": "One of the program's allowed hosts."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The hosts, after the check.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkHostList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/leaderboard": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralLeaderboard",
        "tags": [
          "Referrals"
        ],
        "summary": "Read a program's leaderboard",
        "description": "Every promoter with something counted, ranked by the program's leaderboard metric over its\nperiod (or the `metric` and `period` you ask for), with each one's membership id, name and,\nfor one of your own users, your `external_id`. This is your view: promoters see only the\nfirst places, each named as its promoter chose (hidden, initials or their name), and money\nonly when the board shows amounts. What counts is counted activity: a sale no person or\nrule holds, not refunded in full, not disputed; on a waitlist, confirmed friends. Counted\nagain at most every 10 minutes. See [Campaigns](/docs/referral-campaigns).\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "name": "metric",
            "in": "query",
            "required": false,
            "description": "What to rank by. Left out, the program's own setting.",
            "schema": {
              "$ref": "#/components/schemas/BoardMetric"
            }
          },
          {
            "name": "period",
            "in": "query",
            "required": false,
            "description": "Over which window. Left out, the program's own setting.",
            "schema": {
              "$ref": "#/components/schemas/BoardPeriod"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The leaderboard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Leaderboard"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/contests": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralContests",
        "tags": [
          "Referrals"
        ],
        "summary": "List a program's contests",
        "description": "A program's contests, newest first, each with its prizes and what it is now (`scheduled`,\n`running`, `ended`, `awarded` or `cancelled`; the clock decides, not a job).\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The contests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContestList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createReferralContest",
        "tags": [
          "Referrals"
        ],
        "summary": "Create a contest",
        "description": "A window (up to a year), a metric as a leaderboard's, who may enter (every promoter, or one\ngroup or level), a minimum to qualify, and prizes by place: cash (paid as a bonus by hand)\nor a reward that is not cash (credit, a gift card, a free period or a perk). Standings count\nonly what happens inside the window. Nothing is paid until a person awards the prizes after\nit ends. At most 10 contests a program may be scheduled or running at once.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Creates a contest with prizes the program's promoters compete for; nothing is paid until a person awards them.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContestCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The contest.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contest"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/contests/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralContest",
        "tags": [
          "Referrals"
        ],
        "summary": "Read a contest and its standings",
        "description": "The contest, its standings (each promoter's place among those who reached the minimum,\nwhether they are under review, and the prize the place wins as things stand) and whether\nthe prizes may be awarded now (`award.possible`), and if not, why and from when.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The contest.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContestDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "updateReferralContest",
        "tags": [
          "Referrals"
        ],
        "summary": "Change a contest",
        "description": "Before it starts, anything. Once it has started, only its name and description (`409` for\nanything else): its window, what it counts, who may enter, its minimum and its prizes stay\nas promoters saw them. Once it is awarded or cancelled, nothing (`409`).\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Changes a contest's name, dates or prizes, within what may still change once it has started.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContestUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The contest.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contest"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/contests/{id}/cancel": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "cancelReferralContest",
        "tags": [
          "Referrals"
        ],
        "summary": "Cancel a contest",
        "description": "Ends a contest that was not awarded, with a reason promoters in it are shown. No prize is\npaid. An AI assistant always asks a person first. Recorded in the activity log.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.contest.cancel",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Cancels this contest; no prize is paid, and the promoters competing in it see the reason.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReasonBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The contest, cancelled.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contest"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/contests/{id}/award": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "awardReferralContest",
        "tags": [
          "Referrals"
        ],
        "summary": "Award a contest's prizes",
        "description": "Gives each winning place its prize, once: a cash prize as a bonus paid by hand (\"Contest:\n<name>, 1st place\"), approved at once and paid with the next payout; any other as a reward\nready to give. Refused (`409`, with the reason) before the contest ends, while any winner is\nunder review, and until the last hold on what the winners counted has passed\n(`award.awardable_at` on the contest). BoltClaw never awards by itself. Needs the\n`referrals.payouts` permission. A key that is not an AI assistant's must also hold the\n`referrals:payouts` scope (`403 INSUFFICIENT_SCOPE` otherwise), as an import with opening\nbalances must; an AI assistant always asks a person first, and the person who approves may\nnot be one of the winners. Recorded in the activity log.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.contest.award",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Pays the contest's prizes to its winning places, cash prizes as bonuses with the next payout; it cannot be done twice.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The contest, awarded, and what each place was given.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContestAward"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/entries": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralEntries",
        "tags": [
          "Referrals"
        ],
        "summary": "List a waitlist's entries",
        "description": "A waitlist program's line, in place order: each entry's place (null until they confirm),\nconfirmed friends, whether they are disqualified and why, and who referred them. Places move\nas friends confirm, so a page is the line as it was when read. No address is ever in an\nentry: people who joined on the hosted page are exported to CSV in the dashboard only.\n`filter` is `all` (default), `confirmed`, `unconfirmed`, `disqualified` or `invited`.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "name": "filter",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "confirmed",
                "unconfirmed",
                "disqualified",
                "invited"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Limit200"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of entries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WaitlistEntryList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "upsertReferralEntry",
        "tags": [
          "Referrals"
        ],
        "summary": "Add a person to a waitlist, or update them",
        "description": "Your own sign-up form's call: your id for the person (`external_id`), the referral code\nthey arrived with if any, and later `verified: true` once they confirmed their address\n(that cannot be taken back). `201` for a new entry, `200` for an existing one. An entry\ncounts toward the organisation's referrers, never its partners. An `email`, when you send\none, is kept only as a keyed fingerprint for the self-referral check: never stored or\nreturned. A key may create at most a few hundred new entries an hour (`429` past it).\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Adds one of your own users to the waitlist, or updates their entry; it sends nobody anything.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WaitlistEntryUpsert"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The entry, updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WaitlistEntry"
                }
              }
            }
          },
          "201": {
            "description": "The entry, new.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WaitlistEntry"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/entries/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralEntry",
        "tags": [
          "Referrals"
        ],
        "summary": "Read a waitlist entry",
        "description": "One entry, with the person's own view of their place (`state`), as the in-app panel shows it to them.",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The entry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WaitlistEntryDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "updateReferralEntry",
        "tags": [
          "Referrals"
        ],
        "summary": "Confirm an entry, or set its status",
        "description": "`verified: true` once the person confirmed their address (it moves the friend who referred\nthem up; it cannot be taken back); `status` is `waiting`, `invited` or `joined`, for your own\nrecord. Recorded in the activity log.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Confirms a waitlist entry or changes its status; confirming moves the friend who referred them up the line.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WaitlistEntryUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The entry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WaitlistEntry"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/entries/{id}/disqualify": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "disqualifyReferralEntry",
        "tags": [
          "Referrals"
        ],
        "summary": "Disqualify a waitlist entry",
        "description": "Takes the entry out of the line (it keeps its row) and stops it moving anybody up, with a\nreason the person never sees. An AI assistant always asks a person first. Recorded in the\nactivity log.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.waitlist.disqualify",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Takes this person out of the waitlist's line, and their friends stop counting for them.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReasonBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The entry.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WaitlistEntry"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/entries/{id}/requalify": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "requalifyReferralEntry",
        "tags": [
          "Referrals"
        ],
        "summary": "Put a disqualified entry back in line",
        "description": "Undoes a disqualification, with a reason. The entry takes its place by its join order again. Recorded in the activity log.",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Puts a disqualified person back in the waitlist's line.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReasonBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The entry.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WaitlistEntry"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/entries/invite": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "inviteReferralEntries",
        "tags": [
          "Referrals"
        ],
        "summary": "Invite waitlist entries",
        "description": "Marks up to 500 entries `invited` and emails the ones who joined on the hosted page your\nmessage (a subject of one line, at most 140 characters, and a body in the broadcasts'\nMarkdown subset). Their addresses never leave BoltClaw. An entry from your own form is\nmarked only, for your own system to email. An AI assistant always asks a person first.\nRecorded in the activity log.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.waitlist.invite",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Marks these waitlist entries invited and emails your message to the ones who joined on the hosted page.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WaitlistInvite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How many were invited and emailed.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WaitlistInvited"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/second-tier": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralSecondTier",
        "tags": [
          "Referrals"
        ],
        "summary": "Read a program's second tier",
        "description": "Whether a partner earns a share of what the partners they recruit earn, and how much: at\nmost 20% of the recruit's own cash commission on sales, for 1 to 24 months after the\nrecruit joined, one level only, never on a partner's own purchases. With the clause the\nterms carry while it is on, and why it is fenced. See [Two-tier](/docs/referral-campaigns#two-tier).\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The second tier.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SecondTierSettings"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "updateReferralSecondTier",
        "tags": [
          "Referrals"
        ],
        "summary": "Switch a program's second tier on or off",
        "description": "`enabled: true` with `percent_bps` (1 to 2000) and `months` (1 to 24) pays a recruiter that\nshare of each recruit's cash commission on sales, from the next sale on; the clause is added\nto the program's terms and every approved partner is asked to accept the new version before\ntheir next payout. `enabled: false` stops it (what was earned stays). Off by default.\nSwitching it on is always asked first for an AI assistant. Recorded in the activity log.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.tier.update",
        "x-boltclaw-approval-always": true,
        "x-boltclaw-approval-when": "enabled is true",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Turns the second tier on or off; on, recruiters earn a share of their recruits' commissions and every partner must accept the new terms.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SecondTierUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The second tier.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SecondTierSettings"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/waitlist/end": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "endReferralWaitlist",
        "tags": [
          "Referrals"
        ],
        "summary": "End a waitlist",
        "description": "Ends the waitlist for good: nobody new can join, through the hosted page or through\n`POST /referrals/programs/{id}/entries`, and everybody already in keeps their place and\nmay still be updated or invited. The addresses the hosted page holds are deleted 90 days\nafter the end (`POST /referrals/programs/{id}/waitlist/forget` deletes them now); once an\naddress is gone, its entrant's own link stops working. Ending again keeps the first end.\nA program that is not a waitlist is `409`. To stop new entries for a while instead, set\n`waitlist.closed` on the program. An AI assistant always asks a person first. Recorded in\nthe activity log.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.waitlist.end",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Ends the waitlist for good, so nobody new can join; the addresses its hosted page holds are deleted 90 days later.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "When the waitlist ended (`forgotten` is 0).",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WaitlistEnded"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/waitlist/forget": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "forgetReferralWaitlist",
        "tags": [
          "Referrals"
        ],
        "summary": "Delete a waitlist's addresses now",
        "description": "Deletes every address the waitlist's hosted page holds, and every entrant's own link, now,\nand ends the waitlist if it had not ended (as `POST /referrals/programs/{id}/waitlist/end`\ndoes). Entries stay in the line by your own `external_id`; nothing can be emailed to them\nany more. It cannot be undone. A program that is not a waitlist is `409`. An AI assistant\nalways asks a person first. Recorded in the activity log.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.waitlist.forget",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Deletes every address the waitlist's hosted page holds, now and for good, and ends the waitlist.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "When the waitlist ended, and how many entries' addresses were deleted by this call.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WaitlistEnded"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/waitlist/rotate-page": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "rotateReferralWaitlistPage",
        "tags": [
          "Referrals"
        ],
        "summary": "Give the hosted waitlist page a new address",
        "description": "Makes a new random address for the waitlist's hosted page and stops the old one at once,\nfor a page whose address was posted somewhere it should not be or is being flooded.\nReferral links (`/r/<code>`) keep working: they always send people to the current address.\nUpdate the address wherever you published it. An AI assistant always asks a person first: a\nnew address stops the one already published.\nRecorded in the activity log.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.waitlist.rotate",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Replaces the hosted waitlist page's address; the old address stops answering at once.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The page's new address.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WaitlistPageAddress"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/codes": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralCodes",
        "tags": [
          "Referrals"
        ],
        "summary": "List a program's discount codes",
        "description": "Every partner discount code of the program, newest first (up to 500, in one page), and the\ndiscount a friend gets with one (`friend_discount`, from the program's friend's discount\nrule, or null when it gives none). A shop's own system reads it to keep its coupons in step:\nBolt Claw's WooCommerce plugin reads it every hour. A code that is not `active` was switched\noff: switch it off in your shop too. `stripe_promotion_code` and `shopify_discount` are the\ncode in your own Stripe or in the program's linked Shopify shop, once it is one there.\n\nNeeds `referrals:read`.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The codes, and the friend's discount.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiscountCodeList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/invites": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralInvites",
        "tags": [
          "Referrals"
        ],
        "summary": "List a program's invites",
        "description": "Every partner invite of the program and whether it is still usable, newest first, a page\nat a time. An invite's link is never shown again here: whoever holds it may join, so it is\nshown once, when the invite is made.\n\nNeeds `referrals:read`.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit100"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "The invites, without their links.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InviteSummaryList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createReferralInvite",
        "tags": [
          "Referrals"
        ],
        "summary": "Create an invite to a program",
        "description": "A link that lets one person (or up to `max_uses` people) join the program as a partner,\noptionally until `expires_at` (within a year). In an invite-only program it is the only\nway in. Bolt Claw emails nobody: send the link from your own system, a Zap or a message.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Creates a link that lets whoever holds it join your program as a partner; it is not available to AI assistants, and this call is refused for an assistant's key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InviteCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The invite, with its link.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invite"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/shopify-codes": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "syncReferralShopifyCodes",
        "tags": [
          "Referrals"
        ],
        "summary": "Create the program's codes in its Shopify shop",
        "description": "Keeps every partner code of the program in step with the Shopify shop linked to it through\nBolt Claw's Shopify app: an active code becomes a discount in the shop, at the friend's\ndiscount (percent or amount off, once per customer, or for that many months of a\nsubscription), and a code switched off in Bolt Claw is switched off in the shop. A code the\nshop already has with the same text is matched, not made again; running this twice makes\nnothing twice. At most 50 codes a call: `remaining` says how many wait for the next one.\nWith no linked shop, or no friend's discount rule, nothing is made and `reason` says why.\nLinking a shop is done from the shop's own Shopify admin, never through this API. Recorded\nin the activity log. An AI assistant's key is always asked first.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.shopify.codes",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Creates or switches off discount codes in your own Shopify shop, which changes what your customers pay at checkout.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "What was made, matched and switched off.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShopifyCodesResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/privacy-requests": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralPrivacyRequests",
        "tags": [
          "Referrals"
        ],
        "summary": "List a program's privacy requests",
        "description": "What Shopify's privacy webhooks asked of this program, newest first (up to 200, in one\npage): a customer asking for their data (`data_request`, waiting for you to answer it within\n30 days of `received_at`, so `due_by`), and the erasures Bolt Claw did when Shopify asked\n(`customer_redact`, `shop_redact`), with counts only. Read a data request to see what Bolt\nClaw holds, give it to your customer, then mark it answered.\n\nNeeds `referrals:read`.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PrivacyRequestList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/privacy-requests/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralPrivacyRequest",
        "tags": [
          "Referrals"
        ],
        "summary": "Read a privacy request",
        "description": "One request, and for a data request still waiting, everything Bolt Claw holds about the\ncustomer it named in this program (`data`): your customer id, who referred them, their\nsales, refunds and rewards, and the referral link they were given. Bolt Claw holds no name,\nemail address, postal address or phone number, and `data.note` says so in words you can pass\non. Each read of `data` is recorded in the activity log.\n\nNeeds `referrals:read`.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The request, and what Bolt Claw holds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PrivacyRequestDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/privacy-requests/{id}/answer": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "answerReferralPrivacyRequest",
        "tags": [
          "Referrals"
        ],
        "summary": "Mark a privacy request answered",
        "description": "Says you gave your customer what Bolt Claw holds about them. The Shopify customer and order\nids the request named are forgotten in the same step, and the request reads `answered`.\nOnly a waiting data request is answered (`400` otherwise). Recorded in the activity log.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Marks a customer's data request answered and forgets the Shopify ids it named.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The request, answered.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PrivacyRequest"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/data-subjects/{external_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/SubjectExternalId"
        }
      ],
      "get": {
        "operationId": "getReferralDataSubject",
        "tags": [
          "Referrals"
        ],
        "summary": "Look up what is held about one of your people",
        "description": "Answers a privacy request from one of your own customers or in-app referrers: everything\nBolt Claw holds about them in this workspace, by the id your system uses for them (the\n`external_id` you sent with their conversions or when you made them a referrer, or the\npayment processor's customer id). For a customer: each program they were referred in, their\nconversions and the rewards they were given. For a referrer: their display name, status,\nlinks, commissions and rewards. An email fingerprint is shown only as held or not, never its\nvalue; Bolt Claw holds no name, email address, postal address or phone number of a customer.\nEach lookup is recorded in the activity log, with counts only. Nobody with that id is `404`.\n\nNeeds `referrals:read`.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "Everything held about the person in this workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataSubject"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "eraseReferralDataSubject",
        "tags": [
          "Referrals"
        ],
        "summary": "Erase one of your people's referral data",
        "description": "Erases a customer's or an in-app referrer's referral data at their request, in every program\nof this workspace. Their ids and fingerprints are removed and cannot come back; amounts,\ncommissions and payouts stay as anonymous rows, because your books need them. A referrer's\nname, payout details and panel sessions go and their links stop counting. Copies of webhook\nbodies that carried the id are scrubbed. Recorded in the activity log, with counts only. An AI\nassistant's key is always asked first.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.subject.erase",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Permanently erases a customer's or referrer's referral data in every program; the amounts stay on record without them.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "responses": {
          "200": {
            "description": "Erased, with what was erased counted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataSubjectErased"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/terms/draft": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "draftReferralProgramTerms",
        "tags": [
          "Referrals"
        ],
        "summary": "Draft program terms",
        "description": "Writes the program's terms from Bolt Claw's template: you answer a few questions (your\nbusiness's legal name and contact address, who may join, the minimum age, countries left\nout, how many months money held for a missing tax form waits before it may be forfeited, the\ngoverning law and courts) and every other fact is read from the program's own settings: the\ncookie window, attribution, the hold, the minimum payout, the schedule, tax forms, invoices,\nself-billing, the second tier, levels and the disclosure line. Nothing is saved. To use the\ntext, send it as `terms` to `PATCH /v1/referrals/programs/{id}`. The text ends with a notice\nthat it is based on a template and is not legal advice. See [Program terms](/docs/referral-terms).\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TermsDraftRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The drafted terms, and the settings they were read from.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TermsDraft"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/export": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "exportReferralReport",
        "tags": [
          "Referrals"
        ],
        "summary": "Export a report as a CSV download link",
        "description": "A single-use link to one of the program's reports as a CSV file, valid for ten minutes:\n`commissions` (each with the sentence saying why it is that amount), `conversions`,\n`partners` (partners and your own in-app referrers; an email only when the partner chose to\nshare it with you), `payouts`, `grants` (rewards that are not cash) or `contests` (each\ncontest's standings and prizes). Filter by `from` and `to` (a date like `2026-10-01`, or an\ninstant) and by `status` (for conversions, the event). The file is never in the response:\nopen `url` within ten minutes, once. It is built when it is opened, from what is true then,\nand only if the key that asked is still live and its creator may still read the report.\n\nAt most 25,000 rows a file (narrow the dates for more; a file cut there says so in its\nname), and 60 links an hour for each key (a platform setting; the dashboard counts each\nperson apart, so an assistant never uses up yours). Recorded in the activity log with the\nfilters, never a row.\n\nNeeds `referrals:read`. The `payouts` report also needs `referrals:payouts` (which no AI\nassistant holds) and the permission to pay partners.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Makes a ten-minute, single-use download link to this program's report; nothing changes.",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "commissions",
                "conversions",
                "partners",
                "payouts",
                "grants",
                "contests"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Rows at or after this date (`2026-10-01`) or instant.",
            "schema": {
              "type": "string",
              "maxLength": 40
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Rows before this date or instant.",
            "schema": {
              "type": "string",
              "maxLength": 40
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "One status of that report's rows: a commission's, a partner's, a payout's, a grant's or a contest's; for `conversions`, the event (`lead`, `sale`, `refund`, `dispute`, `bonus`).",
            "schema": {
              "type": "string",
              "maxLength": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The link, once. Only its fingerprint is kept.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReportLink"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/pause": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "pauseReferralProgram",
        "tags": [
          "Referrals"
        ],
        "summary": "Pause a program",
        "description": "Stops a program taking new clicks, signups and sales: its links still open your site, but\nnothing new is credited until it is resumed. What partners already earned is kept, and\npayouts of it go on. A stop, so it is never asked first and keeps working during\nmaintenance.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Pauses this program; its links stop crediting new clicks and sales until it is resumed.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The program, paused.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Program"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/resume": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "resumeReferralProgram",
        "tags": [
          "Referrals"
        ],
        "summary": "Resume a program",
        "description": "Starts a paused or archived program again: its links credit clicks, signups and sales from\nnow on. Nothing that happened while it was paused is credited afterwards. An AI assistant\nresumes a paused program at once; bringing back an archived one asks a person first\n(archiving is a person's decision, and so is undoing it).\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.program.restore",
        "x-boltclaw-approval-always": true,
        "x-boltclaw-approval-when": "the program is archived",
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Resumes this program; its links credit new clicks and sales again.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The program, active.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Program"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/archive": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "archiveReferralProgram",
        "tags": [
          "Referrals"
        ],
        "summary": "Archive a program",
        "description": "Ends a program: it is frozen, its links stop crediting anything, partners can no longer\njoin, and it no longer counts against your plan's programs. What partners earned is kept\nand can still be paid. Resuming it starts it again. An AI assistant always asks a person\nfirst.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.program.archive",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Archives this program; every partner's link stops crediting, and nobody can join until it is resumed.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The program, archived.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Program"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/signup-link/rotate": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "rotateReferralSignupLink",
        "tags": [
          "Referrals"
        ],
        "summary": "Give a program a new signup link",
        "description": "Makes a new signup link for the program and stops the old one at once, for a link that was\nposted somewhere it should not be. Partners already in the program, and their referral\nlinks, are not affected; invites stay valid. An AI assistant always asks a person first: a\nnew link stops the one already shared.\nRecorded in the activity log.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.signup.rotate",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Replaces this program's signup link; the old link stops letting anyone apply.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The program, with its new signup link.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Program"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/partners/{id}/ban": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "banReferralPartner",
        "tags": [
          "Referrals"
        ],
        "summary": "Ban a partner",
        "description": "Shuts a partner out of the program: their links and codes stop crediting, and what they are\nowed and not yet paid is not paid. Only a person decides this; a fraud rule never bans\nanybody. An AI assistant always asks a person first. Recorded in the activity log.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.partner.ban",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Bans this partner; their links stop crediting and their unpaid commissions are not paid.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The partner, banned.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Partner"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/partners/{id}/unban": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "unbanReferralPartner",
        "tags": [
          "Referrals"
        ],
        "summary": "Lift a partner's ban",
        "description": "Lets a banned partner back into the program, approved. Their links and codes credit again\nfrom now on. An AI assistant always asks a person first, as for approving a partner; the\npartner themselves can never be the person who decides. Recorded in the activity log.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.partner.unban",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Lets this banned partner back into the program; their links credit again from now on.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The partner, approved again.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Partner"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/partners/{id}/codes": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "createReferralCode",
        "tags": [
          "Referrals"
        ],
        "summary": "Give a partner a discount code",
        "description": "A discount code that credits this partner when a customer types it at checkout. Leave\n`code` out for the program's suggestion (its code prefix and the partner's name). For\napproved partners only; a code is unique in the program, ignoring case. When the program\nhas a friend's discount and your Stripe is connected, the code is also made as a\npromotion code in your Stripe, and in a Shopify shop linked to the program; otherwise it\nonly tells BoltClaw which partner a code belongs to. An AI assistant always asks a person\nfirst (never the partner it is for): a code changes what customers pay and who is paid.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.code.create",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Gives this partner a discount code that credits them when a customer types it, made in your own Stripe or Shopify shop when they are connected.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CodeCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The code.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiscountCode"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/codes/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "patch": {
        "operationId": "updateReferralCode",
        "tags": [
          "Referrals"
        ],
        "summary": "Link a code to a Stripe promotion code",
        "description": "Tells BoltClaw which promotion code in your own Stripe this code is (`promo_…`), so a sale\nwhere a customer typed it at Stripe Checkout is credited even before Stripe's own events\ntaught BoltClaw. `null` forgets it. Usually unnecessary: BoltClaw learns it from Stripe's\npromotion-code events. An AI assistant always asks a person first, and the approval page\nnames the promotion code: every sale that uses it is credited to this partner, your own\ncampaign codes included.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.code.link",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Records which Stripe promotion code this discount code is; every sale that uses that promotion code is credited to this code's partner.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CodeUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The code.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiscountCode"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/codes/{id}/deactivate": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "deactivateReferralCode",
        "tags": [
          "Referrals"
        ],
        "summary": "Switch a discount code off",
        "description": "Stops a code crediting its partner: a sale where a customer types it is no longer\nattributed. What it earned before is kept. A code switched off stays off; give the partner a\nnew one instead. A stop, so it is never asked first and keeps working during maintenance.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Switches this discount code off; sales with it are no longer credited to its partner.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The code, switched off.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiscountCode"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/links": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralLinks",
        "tags": [
          "Referrals"
        ],
        "summary": "List a program's referral links",
        "description": "Every referral link of the program (`/r/<code>`), or one partner's with `partner_id`: the\npartner's main link and the links BoltClaw placed for them (a bio block, a post, an\nautomation, shown by type only), archived ones included. Newest first, a page at a time.\n\nNeeds `referrals:read`.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "name": "partner_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "$ref": "#/components/parameters/Limit100"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "The links.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReferralLinkList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/links/{id}/archive": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "archiveReferralLink",
        "tags": [
          "Referrals"
        ],
        "summary": "Archive a referral link",
        "description": "Stops one of a partner's links crediting new clicks (it still opens your site). What it\nearned is kept. An AI assistant asks a person first unless your organisation lets\nassistants archive links without asking.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.link.archive",
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Archives this partner's referral link; it stops crediting new clicks.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The link, archived.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReferralLink"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/invites/{id}/revoke": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "revokeReferralInvite",
        "tags": [
          "Referrals"
        ],
        "summary": "Revoke an invite",
        "description": "Stops an invite letting anyone join. People who already joined with it stay. A stop, so it\nis never asked first and keeps working during maintenance.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Revokes this invite; nobody else can join with it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The invite, revoked, without its link.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InviteSummary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/commissions/{id}/approve": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "approveReferralCommission",
        "tags": [
          "Referrals"
        ],
        "summary": "Approve a commission before its hold ends",
        "description": "Makes a pending commission payable now, before the program's refund window ends, with your\nreason. A refund after this still takes it back from the next payout. Not for a commission\na fraud flag is holding: decide the flag instead. The permission to pay partners is\nneeded; a key that is not an AI assistant's also needs `referrals:payouts`, and an AI\nassistant always asks a person first (never the partner it pays). Recorded in the activity\nlog with the reason.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.commission.approve",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Makes this commission payable now, before its refund window ends.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CommissionApproveRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The commission, approved.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Commission"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/payouts/{id}/cancel": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "cancelReferralPayout",
        "tags": [
          "Referrals"
        ],
        "summary": "Cancel a payout",
        "description": "Takes an unpaid payout out of its batch, as `cancelled` (or `failed`, when you tried to pay\nit and the money did not arrive). Its commissions go back to Ready to pay, so nothing a\npartner earned is lost. A payout a provider is still handling is `409` until the provider\nanswers. The permission to pay partners is needed. Cancelling a draft is a stop. Marking\none `failed`, or cancelling one that was exported or sent, lets its commissions be paid\nagain (it may already have been paid by hand): an AI assistant asks a person first, who\ndecides at a two-step session and is never the partner it pays, and a key needs\n`referrals:payouts` for it. Recorded in the activity log.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.payout.reopen",
        "x-boltclaw-approval-always": true,
        "x-boltclaw-approval-when": "the payout is not a draft (it was exported or sent)",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Cancels this unpaid payout, or marks it failed; its commissions go back to Ready to pay and may be paid again.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PayoutCancel"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The payout.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payout"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/levels/recalculate": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "recalculateReferralLevels",
        "tags": [
          "Referrals"
        ],
        "summary": "Recalculate partners' levels now",
        "description": "Moves the program's partners to the highest level they qualify for, now, instead of at the\ndaily run: never down past a level that keeps what was earned, and never a partner you\npinned. Answers how many moved. Each move is recorded in the activity log.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Moves this program's partners to the levels they qualify for now, as the daily run would.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "How many partners moved.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LevelRecalculation"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/rewards": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralRewards",
        "tags": [
          "Referrals"
        ],
        "summary": "List a program's reward rules",
        "description": "Every reward rule of the program: for everyone, for a group or level, and for single\npartners, with the conditions each applies under and a one-sentence `summary`. See\n[Reward rules](/docs/rewards) for how one rule is picked for each sale.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The program's rules.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RewardList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createReferralReward",
        "tags": [
          "Referrals"
        ],
        "summary": "Add a reward rule",
        "description": "A rule for everyone (`scope` `program`), a group or level, or one partner, with conditions\non a sale's products, prices, plans, intervals, first payment or renewal, currency and\namount. Each audience may have one rule with no conditions per event (`409` for a second).\nA `bonus` is a flat amount paid once per window when a partner crosses its `milestone`.\nA program holds at most 200 rules. The new rule applies to sales from now on.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Adds a reward rule; matching sales from now on pay partners by it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RewardCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The rule.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Reward"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/rewards/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "patch": {
        "operationId": "updateReferralReward",
        "tags": [
          "Referrals"
        ],
        "summary": "Update a reward rule",
        "description": "Changes the fields you send, in place. A rule's event and audience are fixed: add a new\nrule for a different one. What was already earned is a stored amount and never changes;\nthe new terms apply to sales from now on.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Changes what a reward rule pays on sales from now on.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RewardUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Reward"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteReferralReward",
        "tags": [
          "Referrals"
        ],
        "summary": "Delete a reward rule",
        "description": "Removes the rule. What it already earned stays earned; it stops paying from now on, and\nthe next most specific rule pays instead. An AI assistant's key is always asked first.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.reward.delete",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Deletes a reward rule; partners stop earning by it from now on.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReferralDeleted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/groups": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralGroups",
        "tags": [
          "Referrals"
        ],
        "summary": "List a program's groups and levels",
        "description": "Plain groups you place partners in, and levels: groups with a rank and a minimum that the\ndaily level job moves partners into. Each with how many partners are in it now.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The program's groups and levels.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GroupList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createReferralGroup",
        "tags": [
          "Referrals"
        ],
        "summary": "Add a group or level",
        "description": "A plain group (no `rank`), or a level (`rank` 1 to 100, and above the first a `minimum`).\nGive it its own rates with `POST /referrals/programs/{id}/rewards` and `scope` `group`.\nNames and ranks are unique in a program; a program holds at most 50 groups and levels.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Adds a partner group or level to the program.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GroupCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The group.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Group"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/groups/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "patch": {
        "operationId": "updateReferralGroup",
        "tags": [
          "Referrals"
        ],
        "summary": "Update a group or level",
        "description": "Changes the fields you send. A minimum is only for a level; a group without a rank has none.",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Changes a partner group or level, which can change who the level job moves.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GroupUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The group.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Group"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteReferralGroup",
        "tags": [
          "Referrals"
        ],
        "summary": "Delete a group or level",
        "description": "Removes the group and its own rules. Its partners stay in the program, in no group, and\nwhat they earned stays earned. An AI assistant's key is always asked first.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.group.delete",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Deletes a partner group or level and its rates; its partners fall back to the program's rates.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReferralDeleted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/partners/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "patch": {
        "operationId": "updateReferralPartner",
        "tags": [
          "Referrals"
        ],
        "summary": "Move a partner to a group",
        "description": "Puts the partner in a group or level of their program (`group_id`), or in none (`null`).\n`pinned: true` keeps the daily level job from moving them again.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Moves a partner to another group or level, which changes the rates they earn from now on.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The partner.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Partner"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/partners/{id}/bonuses": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "createReferralBonus",
        "tags": [
          "Referrals"
        ],
        "summary": "Pay a partner a bonus",
        "description": "A flat fee or one-off bonus by hand (\"200.00 for the launch video\"), in the program's\ncurrency, with a reason the partner reads. It holds for `hold_days` (0 by default, so it\nis ready to pay at once), then is paid like any commission. Only an approved partner can\nbe paid one. Recorded in the activity log, and announced as `referral.bonus.created`.\nAn AI assistant's key is always asked first.\n\n`Idempotency-Key` is required: a retried network timeout must never pay a partner twice.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.commission.bonus",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Adds money the partner will be paid; it is audited and the partner is told.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKeyRequired"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BonusCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The bonus, as a commission.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Commission"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/embed-tokens": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "createReferralEmbedToken",
        "tags": [
          "Referrals"
        ],
        "summary": "Create a referral panel session for one of your users",
        "description": "Call this from YOUR server when a signed-in user opens the page that shows their referrals.\nIt makes the user a referrer in the program if they are not one yet (by the `external_id`\nyou send, which is your own id for that user), and returns a short-lived token your page\nhands to the Bolt Claw panel: `BoltClawReferrals.mount(element, { token, embedKey })`. See\n[Referral panel](/docs/referral-panel).\n\nThe token is shown once, in this response, and is the only thing that ever goes to the\nbrowser. It works for that one referrer in that one program, lives `ttl_seconds` (60 to\n86400, default 3600), and ends early if you call `POST /referrals/programs/{id}/embed/revoke-sessions`\nor the referrer is erased. A replay of the same `Idempotency-Key` returns `token: null`:\nmint a new one instead.\n\nSend `mode: \"preview\"` and no `referrer` for a session that shows sample data, to try your\nlook and copy before launch.\n\nNeeds `referrals:embed` (the \"Referral panel\" key preset), or `referrals:write`. Never put\nthis key in a browser or a mobile app, and an AI assistant's key can never mint a session.\nThe panel must be switched on for the program (`embed_enabled`), or the answer is `409`.\n",
        "x-scope": "referrals:embed",
        "x-any-scopes": [
          "referrals:embed",
          "referrals:write"
        ],
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Creates a short-lived credential that a person's browser will hold; it is not available to AI assistants, and this call is refused for an assistant's key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmbedTokenCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The session. `token` is shown here and nowhere else.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmbedTokenCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/referrers": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralReferrers",
        "tags": [
          "Referrals"
        ],
        "summary": "List a program's referrers",
        "description": "The people you gave a referral link through the panel or the API, newest first, with what\neach link has done. A referrer is one of YOUR users; they have no Bolt Claw account and\nare not counted as partners. `search` matches the start of your `external_id`.\n\nNeeds `referrals:read`, or `referrals:embed`.\n",
        "x-scope": "referrals:read",
        "x-any-scopes": [
          "referrals:read",
          "referrals:embed"
        ],
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ReferrerStatus"
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "The start of the `external_id`, at most 200 characters.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "$ref": "#/components/parameters/Limit100"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of referrers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReferrerList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "upsertReferralReferrer",
        "tags": [
          "Referrals"
        ],
        "summary": "Create or update a referrer",
        "description": "Makes one of your users a referrer, or updates the one your `external_id` already names:\n`201` when it was created, `200` when it already existed. Only the fields you send change;\nsend `null` to clear one. The `email`, if you send one, is only used to recognise the\nsame person again and is never stored, returned or shown to anyone. A new referrer is\nannounced as `referral.referrer.created`.\n\n`code` is the vanity part of their link (`/r/jane`), 3 to 40 lowercase letters and digits\nwith single hyphens, chosen only when the referrer is created: after that it is already\nin links they have shared, and sending one is `409`. `409` also when the code is taken.\n\nYou do not need this before minting a panel session, which creates the referrer itself;\nuse it to create referrers ahead of time or to keep their name current.\n\nNeeds `referrals:embed`, or `referrals:write`. A referrer past the plan's allowance is\n`402`.\n",
        "x-scope": "referrals:embed",
        "x-any-scopes": [
          "referrals:embed",
          "referrals:write"
        ],
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Creates or updates a referrer, a person who then holds a referral link in the program.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReferrerUpsert"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The referrer already existed; the fields you sent are updated.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReferrerView"
                }
              }
            }
          },
          "201": {
            "description": "The referrer was created.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReferrerView"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/referrers/{external_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        },
        {
          "$ref": "#/components/parameters/ExternalId"
        }
      ],
      "get": {
        "operationId": "getReferralReferrer",
        "tags": [
          "Referrals"
        ],
        "summary": "Read one referrer's panel",
        "description": "Exactly what the panel shows this referrer, as data: their link, the offer, their numbers,\na history row for each friend (up to 50, newest first) with a stable `reason_code`, how\nthey are paid, and the share text. Use it to build your own screen. `{external_id}` is\nyour own id for the user and must be percent-encoded.\n\nThe View has no names, emails or ids of your customers: a friend is \"Friend 3\". An erased\nreferrer, or one that does not exist, is `404`.\n\nNeeds `referrals:read`, or `referrals:embed`.\n",
        "x-scope": "referrals:read",
        "x-any-scopes": [
          "referrals:read",
          "referrals:embed"
        ],
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The referrer's panel View.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReferrerPanelView"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/referrers/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "patch": {
        "operationId": "updateReferralReferrer",
        "tags": [
          "Referrals"
        ],
        "summary": "Approve or ban a referrer",
        "description": "Sets a referrer's `status` to `approved` or `banned`. `{id}` is the referrer's `id` from\n`ReferrerView`, not your `external_id`. A banned referrer earns nothing from then on and\nkeeps what was already earned; approving one again can be `402` if the plan's referrer\nallowance is full. An erased referrer cannot be changed (`409`). An AI assistant bans one at\nonce; approving one asks a person first, as letting a banned partner back in does.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.referrer.approve",
        "x-boltclaw-approval-always": true,
        "x-boltclaw-approval-when": "status is approved",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Approves or bans a referrer, which decides whether their link earns rewards from now on.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReferrerStatusUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The referrer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReferrerView"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "eraseReferralReferrer",
        "tags": [
          "Referrals"
        ],
        "summary": "Erase a referrer",
        "description": "Erases a person's referral data on their request. The name, the payout details, the\nrecognition of their address and their panel sessions are removed and cannot come back;\nwhat they earned stays in the commission and payout records as an anonymous row, because\nyour books need it. Their link stops counting. `{id}` is the referrer's `id` from\n`ReferrerView`. An AI assistant's key is always asked first.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.referrer.erase",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Permanently erases a referrer's name and payout details and ends their link; what they earned stays on record without them.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "responses": {
          "200": {
            "description": "Erased.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReferralDeleted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/embed/revoke-sessions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "revokeReferralEmbedSessions",
        "tags": [
          "Referrals"
        ],
        "summary": "End every live panel session",
        "description": "Every panel token minted for this program stops working at once, wherever it is held; each\nopen panel shows \"This link to your referrals has expired\" and asks your page for a new\none. Use it if a token may have leaked. Sessions you mint afterwards work as usual.\n`embed.generation` in the program goes up by one.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Ends every open referral panel session for the program; people see the panel as expired until your site mints a new one.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The program, with its new `embed.generation`.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Program"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/embed/rotate-key": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "rotateReferralEmbedKey",
        "tags": [
          "Referrals"
        ],
        "summary": "Rotate the panel's public key",
        "description": "Gives the program a new public `embed.key`. The key is not a secret (it is in your page\nand in the panel's address) but it names the panel's settings, so rotating it makes every\ninstall that still carries the old one stops showing the panel until you update `embedKey`\nthere. Sessions already minted are not ended by a rotation; to end those as well, call\n`POST /referrals/programs/{id}/embed/revoke-sessions`. An AI assistant always asks a person\nfirst.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.embed.rotate",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Replaces the panel's public key; every page that still embeds the old key stops showing the panel until it is updated.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The program, with its new `embed.key` and `embed.panel_url`.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Program"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/grants": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralGrants",
        "tags": [
          "Referrals"
        ],
        "summary": "List a program's rewards that are not cash",
        "description": "The free months, credit, perks, gift cards and friend's discounts the program has given or\nowes, newest first. Each says what it is (`summary`), whose it is (`recipient`: one of your\nown users with a link by your `external_id`, an affiliate by their name in the program, or\nthe friend by your id for them), and where it stands.\n\nA grant waits out the program's hold as `pending`, then is `ready`: give it in your own\nsystem and say so with `POST /referrals/grants/{id}/fulfil`, or let Bolt Claw give it\nthrough your Stripe if you set a restricted key. A refund or dispute of the sale cancels\none not yet given and marks one already given `reversed`. A friend's discount is recorded\nas `fulfilled` with the sale, when the sale shows it was used.\n\nNeeds `referrals:read`.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/GrantStatus"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/GrantType"
            }
          },
          {
            "name": "recipient",
            "in": "query",
            "required": false,
            "description": "`promoter` for the people who shared, `referee` for the friends.",
            "schema": {
              "type": "string",
              "enum": [
                "promoter",
                "referee"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Limit100"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of grants.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GrantList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/grants/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralGrant",
        "tags": [
          "Referrals"
        ],
        "summary": "Read one reward that is not cash",
        "description": "One grant, as the list shows it. Read it from a `referral.grant.*` webhook event's\n`grant_id` before you give it.\n\nNeeds `referrals:read`.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The grant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Grant"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/grants/{id}/fulfil": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "fulfilReferralGrant",
        "tags": [
          "Referrals"
        ],
        "summary": "Say a reward was given",
        "description": "You gave the reward in your own system (a month added, credit applied, a perk sent); this\nrecords it with your `reference`, tells the person on their page and raises\n`referral.grant.fulfilled`. Only a `ready` grant can be given: one still in its hold, or\ncancelled, is `409`. Sending the same reference again answers the grant as it is and\nchanges nothing, so a retry is safe; a different reference for a grant already given is\n`409`. Recorded in the activity log. An AI assistant's key is always asked first.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.grant.fulfil",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Records that a reward was given with your reference and tells the person; it is audited.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GrantFulfil"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The grant, now `fulfilled`.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Grant"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/grants/{id}/fail": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "failReferralGrant",
        "tags": [
          "Referrals"
        ],
        "summary": "Say a reward could not be given",
        "description": "The reward cannot be given as it stands (the account was closed, the perk is out of\nstock): this records your `reason`, shown to you and not to the person, and leaves the\ngrant `failed`, so you can still give it later with `POST /referrals/grants/{id}/fulfil`.\nA grant already given or cancelled is `409`. Recorded in the activity log. An AI\nassistant's key is always asked first.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.grant.fail",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Records that a reward could not be given, with your reason; it is audited.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GrantFail"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The grant, now `failed`.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Grant"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/grants/{id}/send": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "sendReferralGrant",
        "tags": [
          "Referrals"
        ],
        "summary": "Give a gift card through your Tremendous",
        "description": "Gives a ready gift card to the promoter who earned it, from YOUR Tremendous account, at\nthe email in their payout details, and records it fulfilled with Tremendous's order id.\nThe order's id is made from the grant's, so pressing again never buys a second card. A\nfriend's gift card cannot be sent this way (BoltClaw holds no address for a friend).\n`202` means Tremendous did not answer in time: try again in ten minutes.\n\nNeeds `referrals:payouts`, or an AI assistant with a person's approval, given by someone\nsigned in with two-step verification who is not the promoter it pays. A grant whose\npromoter or sale has an open fraud flag waits. Recorded in the activity log.\n",
        "x-scope": "referrals:write",
        "x-any-scopes": [
          "referrals:write",
          "referrals:payouts"
        ],
        "x-permission": "referrals.payouts",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.grant.fulfil",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Buys the gift card from your Tremendous balance and sends it to the promoter.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The grant, given.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Grant"
                }
              }
            }
          },
          "202": {
            "description": "Tremendous did not answer in time; the grant waits. Try again in ten minutes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Grant"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/offers/{code}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LinkCode"
        }
      ],
      "get": {
        "operationId": "getReferralOffer",
        "tags": [
          "Referrals"
        ],
        "summary": "Read the friend's offer for a referral link",
        "description": "What a person who arrived through this link is offered, in the words the welcome shows\n(`offer`), and whether THIS customer may have it at your checkout (`eligible`). Call it\nfrom your server before you create a checkout: with `customer_id` (your own id for the\ncustomer, when they have one) and `new_customer`, a customer who is the referrer\nthemselves, or was already referred by someone else, is not eligible, with a stable\n`reason_code` and a sentence to show instead of an error.\n\n`coupon` is the coupon Bolt Claw made for the friend's discount in your own Stripe (with a\nrestricted key), ready for `discounts: [{ coupon }]`; `offer.stripe.promotion_code` is the\nreferrer's own promotion code, when they have one.\n\nA link that does not exist, is archived, or is another workspace's answers `200` with\n`offer: null` and `reason_code: unknown_code`, exactly as one that never existed. `coupon`\nis null whenever `eligible` is false.\n\nNeeds `referrals:read`, or `referrals:embed`.\n",
        "x-scope": "referrals:read",
        "x-any-scopes": [
          "referrals:read",
          "referrals:embed"
        ],
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "parameters": [
          {
            "name": "customer_id",
            "in": "query",
            "required": false,
            "description": "Your own id for the customer, at most 255 characters.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          },
          {
            "name": "new_customer",
            "in": "query",
            "required": false,
            "description": "`false` when the customer has paid you before: the offer is for new customers.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The offer and whether this customer may have it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferAnswer"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/customers/{external_id}/referral": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        },
        {
          "$ref": "#/components/parameters/CustomerExternalId"
        }
      ],
      "get": {
        "operationId": "getReferralCustomerReferral",
        "tags": [
          "Referrals"
        ],
        "summary": "Read who invited a customer",
        "description": "For your own screens (\"Invited by Jane\"): whether this customer of yours arrived through\nthe program, when, and how (`link` or `discount_code`). `invited_by` is the inviter's\nname only when they agreed to it being shown; otherwise null. A customer nobody referred\nanswers with every field null; one the program has never seen is `404`.\n\nNeeds `referrals:read`, or `referrals:embed`.\n",
        "x-scope": "referrals:read",
        "x-any-scopes": [
          "referrals:read",
          "referrals:embed"
        ],
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The customer's referral.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerReferral"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/stripe-codes": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "syncReferralStripeCodes",
        "tags": [
          "Referrals"
        ],
        "summary": "Create the program's codes in Stripe",
        "description": "Makes one coupon in your own Stripe from the friend's discount rule (\"Referral: 20% off for\n3 months\"), then a promotion code for every active code of the program that Stripe does not\nhave yet, so a friend can type their referrer's code at Stripe Checkout. A code that\nalready exists in Stripe with the same text is linked, not made again; running this twice\nmakes nothing twice. Needs a restricted Stripe key, set in the dashboard (Integration tab);\nwithout one, or without a friend's discount rule, the answer is `409`. Recorded in the\nactivity log. An AI assistant's key is always asked first.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.stripe.codes",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Creates a coupon and promotion codes in your own Stripe account, which changes what your customers pay at checkout.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "What was made and linked.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StripeCodesResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/integration-status": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralIntegrationStatus",
        "tags": [
          "Referrals"
        ],
        "summary": "Read a program's live setup status",
        "description": "Whether each part of the setup works, from what Bolt Claw has actually seen for this\nprogram: the website tag, billing (any connected billing system, or sales through this\nAPI), signups, sales, discount codes, the in-app panel and the friend's welcome. Each part\nis `connected`, `warnings` (connected, with something to fix) or `not_connected`, with one\nsentence saying what was seen and one saying what to do. A part the program's own setup\ndoes not use is `skipped` and does not count toward `overall`. Test data never makes a part\ngreen: only live events do.\n\n`integrations` lists every connected billing system as its card shows it: which one, how it\nproves its deliveries, when the last verified event arrived and how many were refused since.\nNo credential is ever returned, not even in part.\n\nNeeds `referrals:read`.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The program's setup, part by part.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IntegrationStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/integration-status/tag-check": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "checkReferralTag",
        "tags": [
          "Referrals"
        ],
        "summary": "Check the website tag on the program's product page",
        "description": "Fetches the program's product URL once, from Bolt Claw's server, and looks for the website\ntag (`/referrals/v2.js`, or the older `v1.js`) in a script tag, then updates the setup\nstatus. Only `https`, only the program's own hosts, never a private address (checked after\nthe name is resolved, and again on every redirect), five seconds and 512 KB at most. A tag\nyour site adds with JavaScript is not visible to this check: open your site with the verify\nlink from the dashboard instead. A few checks per program an hour.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Fetches your program's product page once from Bolt Claw's server to see whether the website tag is on it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "What the check found.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagCheck"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/test-sale": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "sendReferralTestSale",
        "tags": [
          "Referrals"
        ],
        "summary": "Send a test sale through the program",
        "description": "Runs the real path with TEST data, step by step: the program's test partner (\"Test\npartner\", made once) and its link, a click on it, a signup, a sale of 100.00 in the\nprogram's currency with two items, then a partial refund of 25.00. Each step answers with\nwhat it caused: the conversion with its commissions and grants, the friend's offer, and how\nmany webhook deliveries it queued for endpoints that receive test events.\n\nTest data is never payable, never counts toward a limit or a total, emails nobody, and\nreaches only webhook endpoints that switched test events on. `Clear test data` deletes it\nall. The program must be active.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-agent-consequence": "Records a test click, signup, sale and refund through your program, credited to its test partner; nothing is payable and only endpoints that receive test events are told.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "201": {
            "description": "Every step, in order.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestSale"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/test-data": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "delete": {
        "operationId": "clearReferralTestData",
        "tags": [
          "Referrals"
        ],
        "summary": "Delete a program's test data",
        "description": "Deletes every TEST row of the program: the conversions, commissions, rewards, customers and\nclicks that test sales, `test: true` conversions, a test-mode Stripe or a sandbox recorded,\nand the program's test partner with its link. Live data is never touched. Recorded in the\nactivity log. An AI assistant's key is always asked first.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.testdata.clear",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Deletes every test conversion, commission, reward, customer and click of this program, and its test partner. Live data is not touched.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "responses": {
          "200": {
            "description": "How many of each were deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestDataCleared"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/clicks": {
      "post": {
        "operationId": "recordReferralClick",
        "tags": [
          "Referrals"
        ],
        "summary": "Record a click your own server saw",
        "description": "For a partner's link that opens straight on your own site (`?via=<code>`), reported by your\nserver, for example by the SDK's `referralsMiddleware()`. Send the link's code and what your\nserver saw: the visitor's IP address and user agent are hashed and classed exactly as a\nclick on `/r/<code>` is, and never stored. It counts toward the click allowance like any\nclick, and earns nothing by itself: a click is never a conversion. A robot is answered\n`recorded: false` and stored nowhere; a code that is not one of your links is `404`.\n\nNeeds `conversions:write`, like reporting a conversion.\n",
        "x-scope": "conversions:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-agent-consequence": "Records one click on one of your partners' links, as your own server saw it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReferralClickCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Not stored (a robot, or the month's click allowance is used).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReferralClickRecorded"
                }
              }
            }
          },
          "201": {
            "description": "Recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReferralClickRecorded"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/flags": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "listReferralFlags",
        "tags": [
          "Referrals"
        ],
        "summary": "List a program's review queue",
        "description": "The flags waiting for a decision (or, with `status`, the ones already decided), newest\nfirst, up to 200. A flag is raised by a rule, never decided by one: the risk engine only\never HOLDS (a longer hold on what a conversion earned, or `risk_review` for a person to\nlook at), and nothing here bans, rejects or confirms anybody by itself. Each flag names\nthe partner by their name in this program, and the conversion, commission or link it is\nabout; `score` is the risk score (0 to 100) behind a `risk_review` flag.\n\nFilter with `rule`, `partner_id`, `min_score` and `older_than_days`.\n\nNeeds `referrals:read`.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "`open` (the default), `dismissed` or `confirmed`.",
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "dismissed",
                "confirmed"
              ]
            }
          },
          {
            "name": "rule",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/FlagRule"
            }
          },
          {
            "name": "partner_id",
            "in": "query",
            "required": false,
            "description": "One partner's membership id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "min_score",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100
            }
          },
          {
            "name": "older_than_days",
            "in": "query",
            "required": false,
            "description": "Only flags at least this many days old.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 3650
            }
          },
          {
            "name": "reason",
            "in": "query",
            "required": false,
            "description": "Only `risk_review` flags whose score had this reason, for example `fast_conversion`.",
            "schema": {
              "type": "string",
              "enum": [
                "fast_conversion",
                "paid_ads",
                "datacenter_click",
                "disposable_customer",
                "email_lookalike",
                "no_click_pattern",
                "code_leak_spike",
                "refund_farming",
                "conversion_rate_outlier",
                "new_partner"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The flags.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlagList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/flags/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralFlag",
        "tags": [
          "Referrals"
        ],
        "summary": "Read one flag in full",
        "description": "Everything a person needs to decide a flag: the score and every reason as a sentence (what\nwas seen, in counts and times: \"Sale 4 seconds after the click\", never an address, a card\nor an email), the timeline of the customer's click, signup, sale and refunds with the\nseconds between them, and the partner's history in this program (age, clicks and customers\nin 30 days, refunds in 60, and their other flags). The partner never sees any of it: their\nside says only \"Under review\".\n\nNeeds `referrals:read`.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The flag, its reasons, timeline and the partner's history.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlagDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/flags/{id}/dismiss": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "dismissReferralFlag",
        "tags": [
          "Referrals"
        ],
        "summary": "Dismiss a flag (not fraud)",
        "description": "Not fraud: the flag is closed and what it held goes on. A held commission matures at its\nhold (or on the next sweep if that has passed) and joins a payout like any other. A\nflag already decided is `409`. Recorded in the activity log with your note. An AI\nassistant's key is always asked first.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.flag.dismiss",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Closes the flag as not fraud, so the commission it held can be paid; it is audited.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FlagDecision"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The flag, now `dismissed`.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlagResolution"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/flags/{id}/confirm": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "confirmReferralFlag",
        "tags": [
          "Referrals"
        ],
        "summary": "Confirm a flag as fraud",
        "description": "Fraud: every commission the flag holds that is still `pending` or `approved` becomes\n`fraud`, which never reaches a payout, and a reward not yet given is cancelled. With\n`ban_partner: true` the partner is banned from the program too; nothing bans anybody\nunless you say so. A commission already in a payout is `409`. Recorded in the activity log\nwith your note. An AI assistant's key is always asked first.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.flag.confirm",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Marks what the flag held as fraud so it is never paid, and bans the partner if asked; it is audited.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FlagConfirm"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The flag, now `confirmed`, and what it did.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlagResolution"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/flags/dismiss": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "dismissReferralFlags",
        "tags": [
          "Referrals"
        ],
        "summary": "Dismiss many flags at once",
        "description": "Dismisses up to 200 open flags of one program with one reason, each decided and recorded in\nthe activity log on its own. A flag that is not this program's, not open, or holds a\ncommission already in a payout is left as it was and listed in `skipped` with why. An AI\nassistant's key is always asked first.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.flag.dismiss",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Closes each listed flag as not fraud, so what they held can be paid; each is audited.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FlagsBulk"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How many were decided, and the ones left as they were.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlagsResolution"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/flags/confirm": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "confirmReferralFlags",
        "tags": [
          "Referrals"
        ],
        "summary": "Confirm many flags as fraud at once",
        "description": "Confirms up to 200 open flags of one program as fraud with one reason, each decided and\nrecorded in the activity log on its own: what each held becomes `fraud`, and with\n`ban_partners: true` their partners are banned. Flags left as they were are listed in\n`skipped` with why. An AI assistant's key is always asked first.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.flag.confirm",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Marks what each listed flag held as fraud so it is never paid, and bans their partners if asked; each is audited.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FlagsBulkConfirm"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How many were decided, and the ones left as they were.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FlagsResolution"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/partners/{id}/hold-payouts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "holdReferralPartnerPayouts",
        "tags": [
          "Referrals"
        ],
        "summary": "Hold a partner's payouts",
        "description": "\"Hold this promoter's payouts\": opens a `manual_hold` flag on the partner, and nothing they\nearned is paid while it is open. A stop, never a decision: what they earned stays theirs\nuntil you dismiss or confirm the flag. Holding a partner already held answers the open\nflag with `already: true`. Recorded in the activity log.\n\nOne API key may place at most 30 holds an hour (a platform setting), answered `429\nRATE_LIMITED` past it: only a person releases a hold. The dashboard is not counted.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-cross-tenant": 404,
        "x-idempotent": true,
        "x-agent-consequence": "Holds every payout to this partner until a person decides; nothing is taken away.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FlagDecision"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already held; the open flag.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutHold"
                }
              }
            }
          },
          "201": {
            "description": "Held; the new flag.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutHold"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/risk-policy": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralRiskPolicy",
        "tags": [
          "Referrals"
        ],
        "summary": "Read a program's fraud settings",
        "description": "What holds this program's money: the score at which a conversion's earnings wait longer\n(`hold_at`, and `extra_hold_days`), the score at which a person is asked (`review_at`), the\nfewest seconds from click to signup, sale or shop order that look human, whether paid\nsearch on the brand is allowed (with campaign ids that are), and whether new partners wait\nlonger. `policy` is what is in force (your values, else the platform's); `custom` is only\nwhat you set. `alert_email` emails the program's managers when a promoter needs a look, at\nmost once per promoter a day.\n\nNeeds `referrals:read`.\n",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RiskPolicyView"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "operationId": "updateReferralRiskPolicy",
        "tags": [
          "Referrals"
        ],
        "summary": "Change a program's fraud settings",
        "description": "Replaces what you set with `policy` (send `null` to go back to the platform's values) and\nswitches the alert email. Every value is checked: `review_at` is never below `hold_at`, a\ncampaign id is letters, digits and `_.:-`. Applies from the next conversion; what was\nalready assessed keeps its hold. Recorded in the activity log with the values before and\nafter. An AI assistant's key is always asked first.\n\nNeeds `referrals:write`.\n",
        "x-scope": "referrals:write",
        "x-permission": "referrals.manage",
        "x-function": "referrals-api",
        "x-rate-limit-class": "write",
        "x-boltclaw-approval": "referral.risk.update",
        "x-boltclaw-approval-always": true,
        "x-cross-tenant": 404,
        "x-agent-consequence": "Changes the thresholds and rules that decide when this program's payouts are held; it is audited.",
        "parameters": [
          {
            "$ref": "#/components/parameters/BoltClawApproval"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RiskPolicyUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The settings now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RiskPolicyView"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "428": {
            "$ref": "#/components/responses/ApprovalRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/referrals/programs/{id}/stats": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getReferralProgramStats",
        "tags": [
          "Referrals"
        ],
        "summary": "Read a program's stats",
        "description": "Clicks, leads, sales, revenue and commission over a window, in total, per partner and, with `bucket`, over time.",
        "x-scope": "referrals:read",
        "x-permission": "referrals.view",
        "x-function": "referrals-api",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          },
          {
            "name": "bucket",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "day",
                "week",
                "month"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The stats.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProgramStats"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/auth/device": {
      "post": {
        "operationId": "startDeviceAuthorization",
        "tags": [
          "Agents"
        ],
        "summary": "Start a device sign-in",
        "description": "How an AI assistant or a command-line tool gets a key, without anybody pasting one.\n\n**Your program generates the key.** Make 32 random bytes, render them as 43 base62\ncharacters, prefix them with `bc_live_`, and send only the SHA-256 of the whole string\nplus the first 8 characters of the random body. The key itself must never leave the\nmachine it was made on, and there is no field here that would carry it. BoltClaw stores\nthe hash, exactly as it stores every other key's, so no secret of ours exists at any\nmoment, not even between the approval and your next poll.\n\nYou get back a short code and a URL. Show both to the person, poll\n`POST /v1/auth/device/poll` every `interval` seconds, and once they approve, the key you\nalready hold starts working.\n\nAsk for the permissions you need and no more: the person sees them, can untick any of\nthem, and the key is narrowed again to what they themselves may grant. Permissions an\nassistant may never hold, such as managing API keys or webhooks, are refused here rather\nthan silently dropped later.\n\nThis call needs no API key, and it is limited per address rather than per key.\n",
        "x-scope": "none",
        "x-function": "api-agents",
        "x-rate-limit-class": "auth",
        "x-keyless": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceAuthorizationStart"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The request is waiting for a person.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceAuthorization"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/auth/device/poll": {
      "post": {
        "operationId": "pollDeviceAuthorization",
        "tags": [
          "Agents"
        ],
        "summary": "Poll a device sign-in",
        "description": "Has a person decided yet? Send the `device_code` from `POST /v1/auth/device`, no more\noften than `interval` seconds apart.\n\n`status` is `pending` while you wait, `slow_down` if you are asking too fast (wait\n`interval` seconds and carry on), `approved` once a person has said yes, `denied` if they\nsaid no, and `expired` if nobody decided in time. A code that was never issued, or has been\ncleared away since, answers `expired` too, exactly like one that lapsed: the answer never\ntells you whether a code existed. Either way, start a new sign-in.\n\nAn approval answers with the key's id, its prefix, the permissions it ended up with and\nwhen it expires. It never answers with a key: you made it, so you already have it.\n\nIt also names the workspace the person chose, by id and name. Apart from `GET /workspace`\nand one route reserved for the BoltClaw MCP server, this is the only answer in the API that\ncarries a workspace id, and it goes only to the holder of the device code: the program\nthat started the sign-in, which is about to hold a key for that workspace and could read\nthe same id with it.\n\nThis call needs no API key.\n",
        "x-scope": "none",
        "x-function": "api-agents",
        "x-rate-limit-class": "auth",
        "x-keyless": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceAuthorizationPoll"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Where the sign-in has got to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceAuthorizationStatus"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/approvals/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getApprovalStatus",
        "tags": [
          "Agents"
        ],
        "summary": "Read an approval you asked for",
        "description": "Has the person decided yet? This is the one thing a key may read about an approval, and\nonly about its own: an approval another key asked for is `404`, exactly like one that\nnever existed.\n\nYou get the status and the deadline, not the summary: the summary was written for the\nperson who decides, and you already know what you asked for. A `pending` or `approved`\nticket past its deadline reads as `expired`.\n\nCheap and safe to poll while you wait, and left out of the agent call log so that waiting\ndoes not bury the calls that did something. Needs no scope.\n",
        "x-scope": "none",
        "x-function": "api-agents",
        "x-rate-limit-class": "read",
        "x-cross-tenant": 404,
        "responses": {
          "200": {
            "description": "The approval, as it stands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApprovalStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/auth/key/extend": {
      "post": {
        "operationId": "extendOwnApiKey",
        "tags": [
          "Agents"
        ],
        "summary": "Extend the key you are using",
        "description": "A key approved for an AI assistant lasts 90 days. Within 30 days of its expiry it can push\nthat out by another 90, up to a year from the day it was issued, and then a person signs\nin again.\n\nNothing else can be extended this way: a key a person created in the dashboard has an\nexpiry they chose, and this call will not second-guess it. The extension also stops if the\nperson who approved the connection can no longer approve it, or if the organisation has\nturned AI assistant access off, which is what makes it safe to let a credential renew\nitself.\n\nActs only on the key that made the call, so it needs no scope. Safe to call on a schedule:\noutside the 30-day window it answers `extended: false` and changes nothing.\n",
        "x-scope": "none",
        "x-function": "api-agents",
        "x-rate-limit-class": "auth",
        "x-agent-consequence": "Extends the expiry of the key making the call. Nothing else changes.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "Whether it moved, and when it now expires.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyExtendResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/auth/key/revoke": {
      "post": {
        "operationId": "revokeOwnApiKey",
        "tags": [
          "Agents"
        ],
        "summary": "Revoke the key you are using",
        "description": "Sign out, for real. Deleting a key from a configuration file leaves it working; this ends\nit. Use it when a person disconnects your program, and when you are uninstalling.\n\nActs only on the key that made the call, so it needs no scope, and it works even when the\norganisation has turned AI assistant access off: handing a credential back must never be\nthe thing you cannot do. It cannot be undone, and it is safe to repeat.\n\nIf the key is the root of a connection that spans several workspaces, every key in that\nconnection ends with it.\n",
        "x-scope": "none",
        "x-function": "api-agents",
        "x-rate-limit-class": "auth",
        "x-agent-consequence": "Permanently ends the key making the call, and every key in its connection.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The key is revoked.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyRevokeResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/auth/key/revoke-oauth": {
      "post": {
        "operationId": "revokeOauthConnection",
        "tags": [
          "Agents"
        ],
        "summary": "End a connection",
        "description": "Ends a whole AI connection, from its root, so no workspace is left reachable. Reserved\nfor the BoltClaw MCP server; anything else is `404`.\n\nIt takes a key ID rather than a key, because the MCP server reaches it exactly when it no\nlonger holds one: a person signing the assistant out of their client revokes the grant\nwhose encrypted store was the only copy of the key. The answer is the same for a key id\nthat was a connection's root and for one that was never anything, so it tells a caller\nnothing about which ids exist.\n\nThis call needs no API key.\n",
        "x-scope": "none",
        "x-function": "api-agents",
        "x-rate-limit-class": "auth",
        "x-mcp-worker-only": true,
        "x-keyless": true,
        "x-agent-consequence": "Permanently ends every key in the connection.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConnectionRevoke"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The connection is revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyRevokeResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/auth/key/workspaces": {
      "get": {
        "operationId": "listConnectionWorkspaces",
        "tags": [
          "Agents"
        ],
        "summary": "List a connection's workspaces",
        "description": "Which workspaces this connection reaches today. Re-resolved on every call against the live\npermissions of the person who approved it, so a workspace they can no longer reach drops\nout with nothing to revoke. Reserved for the BoltClaw MCP server; anything else is `404`.\n",
        "x-scope": "none",
        "x-function": "api-agents",
        "x-rate-limit-class": "read",
        "x-mcp-worker-only": true,
        "responses": {
          "200": {
            "description": "The workspaces, and which already have a key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectionWorkspaceList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "registerConnectionWorkspace",
        "tags": [
          "Agents"
        ],
        "summary": "Register a connection's workspace",
        "description": "Registers the key for one workspace of a connection, from a hash the caller derived and a\nprefix to display. Creates nothing if that workspace already has a live key, and answers\nthe one it has. Reserved for the BoltClaw MCP server; anything else is `404`.\n",
        "x-scope": "none",
        "x-function": "api-agents",
        "x-rate-limit-class": "write",
        "x-mcp-worker-only": true,
        "x-agent-consequence": "Registers one more key for this connection, in a workspace it already reaches.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConnectionWorkspaceRegister"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "That workspace already had a key.",
            "headers": {
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectionWorkspaceKey"
                }
              }
            }
          },
          "201": {
            "description": "Registered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectionWorkspaceKey"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/logs/agent-calls": {
      "get": {
        "operationId": "listAgentCalls",
        "tags": [
          "Logs"
        ],
        "summary": "List agent calls",
        "description": "Every request an AI assistant made in this workspace, newest first: which connection, which\ntool it said it was running, which operation, what it answered, how long it took, and the\napproval it carried if it carried one. Kept for 90 days.\n\nPage with `before`, an ISO-8601 timestamp from the last row of the previous page.\n",
        "x-scope": "logs:read",
        "x-permission": "logs.view",
        "x-function": "api-agents",
        "x-rate-limit-class": "read",
        "parameters": [
          {
            "name": "before",
            "in": "query",
            "required": false,
            "description": "An ISO-8601 timestamp with an offset. Returns calls older than it.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "$ref": "#/components/parameters/Limit100"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of calls.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentCallList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/logs/audit": {
      "get": {
        "operationId": "listAuditLog",
        "tags": [
          "Logs"
        ],
        "summary": "List the activity log",
        "description": "What everybody did in this workspace, newest first: the same list the dashboard shows under\nSettings, Activity. Each entry names the action, what it acted on, when, and who by, as a\nlabel rather than an account: a member's display name, the name of an API key, or BoltClaw\nsupport.\n\nValues that look like a credential are replaced with `[redacted]` before the entry leaves\nthe database, so a log is never a place to find one.\n",
        "x-scope": "logs:read",
        "x-permission": "logs.view",
        "x-function": "api-agents",
        "x-rate-limit-class": "read",
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit100"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of entries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuditEntryList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer bc_live_...`: `bc_live_` and 43 letters and digits. A key belongs to\none workspace and carries scopes. Only a hash is stored, so a lost key can only be replaced.\nA malformed, unknown or revoked key is `401 UNAUTHORIZED`, and the answer never says which.\n"
      }
    },
    "parameters": {
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "The `next_cursor` from the previous page. Opaque; an invalid one is `400`.",
        "schema": {
          "type": "string",
          "maxLength": 512
        }
      },
      "Limit200": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Page size, 1 to 200. Above 200 is clamped; anything else is the default.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      },
      "Limit100": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Page size, 1 to 100. Above 100 is clamped; anything else is the default.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 50
        }
      },
      "Slug": {
        "name": "slug",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/Slug"
        }
      },
      "Id": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "An id this API gave you. Anything that is not one of yours is `404`.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "SubjectExternalId": {
        "name": "external_id",
        "in": "path",
        "required": true,
        "description": "The id your system uses for the person: the `external_id` you sent with their conversions or\nwhen you made them a referrer, or the payment processor's customer id. Percent-encode it in\nthe path. An id nobody in this workspace has is `404`. The decoded id is at most 255 characters;\nthe encoded segment may be longer.\n",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 2300,
          "pattern": "^(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+$"
        }
      },
      "ExternalId": {
        "name": "external_id",
        "in": "path",
        "required": true,
        "description": "Your own id for the user, as you sent it in `external_id`. It can hold any printable\ncharacters, so percent-encode it in the path (`user%2F1`). An id that is not one of your\nreferrers is `404`. The decoded id is at most 200 characters; the encoded segment may be longer.\n",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 1800,
          "pattern": "^(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+$"
        }
      },
      "CustomerExternalId": {
        "name": "external_id",
        "in": "path",
        "required": true,
        "description": "Your own id for the customer, as you sent it in a conversion's `customer.id`. Percent-encode\nit in the path. The decoded id is at most 255 characters; the encoded segment may be longer.\n",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 2300,
          "pattern": "^(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+$"
        }
      },
      "LinkCode": {
        "name": "code",
        "in": "path",
        "required": true,
        "description": "The code at the end of a referral link (`/r/jane`), lower case.",
        "schema": {
          "type": "string",
          "minLength": 3,
          "maxLength": 40,
          "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
        }
      },
      "Year": {
        "name": "year",
        "in": "path",
        "required": true,
        "description": "A calendar year, four digits (`2026`). A year before 2026, or after this one, is `400`;\nanything that is not four digits is `404`.\n",
        "schema": {
          "type": "string",
          "minLength": 4,
          "maxLength": 4,
          "pattern": "^[0-9]{4}$"
        }
      },
      "BoltClawApproval": {
        "name": "BoltClaw-Approval",
        "in": "header",
        "required": false,
        "description": "The id of an approval a person has decided, from a `428` you were given earlier. Only an\nAI assistant's request is ever gated; send the identical request again with this header\nonce the person has approved it. An id that is spent, lapsed, for another request, or for\nanother key is `409 APPROVAL_INVALID`.\n",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Optional. One key per logical operation, 1 to 255 printable ASCII characters (a UUID\nworks). The same key and request within 24 hours replays the first success with\n`Idempotent-Replay: true`. The same key with another request is\n`409 IDEMPOTENCY_KEY_CONFLICT`; while the first is running, `409 IDEMPOTENCY_KEY_IN_FLIGHT`.\nA malformed key is `400`.\n",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255
        }
      },
      "IdempotencyKeyRequired": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "Required on this operation, which mints a credential exactly once. One key per logical\noperation, 1 to 255 printable ASCII characters (a UUID works). The same key and request\nwithin 24 hours replays the first success with `Idempotent-Replay: true` and `secret:\nnull`. The same key with another request is `409 IDEMPOTENCY_KEY_CONFLICT`; while the\nfirst is running, `409 IDEMPOTENCY_KEY_IN_FLIGHT`. A missing or malformed key is `400`.\n",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255
        }
      }
    },
    "headers": {
      "Location": {
        "description": "The path of the object this operation created.",
        "schema": {
          "type": "string"
        }
      },
      "XRequestId": {
        "description": "This request's id; the same value as `request_id` in an error body.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "IdempotentReplay": {
        "description": "`true` when this is the stored answer to an earlier request with the same Idempotency-Key.",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      },
      "RateLimitLimit": {
        "description": "Requests a minute this key may make, from its organisation's plan.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitRemaining": {
        "description": "Requests left in the current minute.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "Unix seconds at which the current minute ends.",
        "schema": {
          "type": "integer"
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "schemas": {
      "DeviceAuthorizationStart": {
        "type": "object",
        "required": [
          "client_name",
          "scopes",
          "key_hash",
          "key_prefix"
        ],
        "additionalProperties": false,
        "description": "The key is made on your machine and never sent. `key_hash` is the SHA-256 of the whole\n`bc_live_...` string, as 64 lowercase hex characters; `key_prefix` is the first 8\ncharacters of the random body after the prefix, and is what a person sees in the list of\nconnections.\n",
        "properties": {
          "client_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "What you are called, shown to the person deciding. Never trusted for anything."
          },
          "client_host": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "The machine you are running on, shown beside the name so a person can tell two terminals apart."
          },
          "scopes": {
            "type": "array",
            "minItems": 1,
            "maxItems": 25,
            "description": "The permissions you are asking for. Ask for what you need and no more.",
            "items": {
              "$ref": "#/components/schemas/Scope"
            }
          },
          "key_hash": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$"
          },
          "key_prefix": {
            "type": "string",
            "pattern": "^[0-9A-Za-z]{8}$"
          },
          "flow": {
            "type": "string",
            "enum": [
              "device",
              "oauth"
            ],
            "default": "device",
            "description": "`device` for a program somebody is sitting at. `oauth` is the hosted BoltClaw MCP\nserver acting for a person, and only it may ask for one.\n"
          },
          "redirect_host": {
            "type": "string",
            "minLength": 1,
            "maxLength": 253,
            "description": "Only with `flow: oauth`. The host the person will be sent back to, shown to them before they approve."
          }
        }
      },
      "DeviceAuthorization": {
        "type": "object",
        "required": [
          "device_code",
          "user_code",
          "verification_uri",
          "verification_uri_complete",
          "expires_in",
          "expires_at",
          "interval"
        ],
        "properties": {
          "device_code": {
            "type": "string",
            "description": "Your half of the exchange. Keep it, poll with it, and never show it to anybody."
          },
          "user_code": {
            "type": "string",
            "description": "What the person types, as `XXXX-XXXX`. Show it to them."
          },
          "verification_uri": {
            "type": "string",
            "format": "uri"
          },
          "verification_uri_complete": {
            "type": "string",
            "format": "uri",
            "description": "The same page with the code filled in. Use it when you can show a link or a QR code."
          },
          "expires_in": {
            "type": "integer",
            "description": "Seconds until the request lapses."
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "interval": {
            "type": "integer",
            "description": "Seconds to wait between polls. Asking faster is answered `slow_down`."
          }
        }
      },
      "DeviceAuthorizationPoll": {
        "type": "object",
        "required": [
          "device_code"
        ],
        "additionalProperties": false,
        "properties": {
          "device_code": {
            "type": "string",
            "minLength": 16,
            "maxLength": 200
          }
        }
      },
      "DeviceAuthorizationStatus": {
        "type": "object",
        "required": [
          "status"
        ],
        "description": "`pending` while you wait, `slow_down` if you are asking too fast, `approved` once a person\nhas said yes, `denied` if they said no, `expired` if nobody decided in time. A code that\nwas never issued answers `expired` as well, so the answer never says whether one existed.\n",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "slow_down",
              "approved",
              "denied",
              "expired"
            ]
          },
          "interval": {
            "type": "integer",
            "description": "Only with `slow_down`. Wait this many seconds."
          },
          "key": {
            "type": "object",
            "description": "Only with `approved`. Never the key itself, which you already hold.",
            "required": [
              "id",
              "key_prefix",
              "scopes",
              "expires_at"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "key_prefix": {
                "type": "string"
              },
              "scopes": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Scope"
                }
              },
              "expires_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "connection_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid"
              }
            }
          },
          "workspace": {
            "type": "object",
            "description": "Only with `approved`. The workspace the person chose. One of the few answers that\ncarries a workspace id, because it goes only to the holder of the device code: the\nprogram that started the sign-in, whose new key belongs to this workspace.\n",
            "required": [
              "id",
              "name"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "callback_nonce_hash": {
            "type": "string",
            "description": "Only for the `oauth` flow, and only to the client that started it."
          }
        }
      },
      "ApprovalStatus": {
        "type": "object",
        "required": [
          "id",
          "action",
          "status",
          "expires_at",
          "decided_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "action": {
            "$ref": "#/components/schemas/ApprovalAction"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "denied",
              "used",
              "expired"
            ]
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "decided_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ApprovalAction": {
        "type": "string",
        "description": "What an assistant asked to do. The actions in the first group are the ones an organisation\nmay decide to stop being asked about, or waive for one connection for a few hours; the\nrest are always asked, because they move somebody else's money or change somebody else's\naccess.\n",
        "enum": [
          "post.publish",
          "post.delete",
          "inbox.reply",
          "inbox.hide",
          "conversation.reply",
          "automation.enable",
          "automation.delete",
          "page.delete",
          "page.unpublish",
          "link.delete",
          "lead.delete",
          "referral.payout.paid",
          "referral.commission.adjust",
          "referral.partner.approve",
          "social.account.disconnect",
          "referral.commission.bonus",
          "referral.reward.delete",
          "referral.group.delete",
          "referral.referrer.erase",
          "referral.grant.fulfil",
          "referral.grant.fail",
          "referral.stripe.codes",
          "referral.testdata.clear",
          "referral.flag.dismiss",
          "referral.flag.confirm",
          "referral.risk.update",
          "referral.payout.send",
          "referral.payout.settings",
          "referral.commission.forfeit",
          "referral.tax.settings",
          "referral.brand.update",
          "referral.domain.add",
          "referral.domain.remove",
          "referral.listing.update",
          "referral.library.delete",
          "referral.broadcast.send",
          "referral.import.commit",
          "referral.contest.award",
          "referral.contest.cancel",
          "referral.waitlist.disqualify",
          "referral.waitlist.invite",
          "referral.tier.update",
          "referral.waitlist.end",
          "referral.waitlist.forget",
          "referral.shopify.codes",
          "referral.terms.update",
          "referral.subject.erase",
          "referral.program.archive",
          "referral.partner.ban",
          "referral.partner.unban",
          "referral.commission.approve",
          "referral.code.create",
          "referral.link.archive",
          "referral.program.restore",
          "referral.signup.rotate",
          "referral.waitlist.rotate",
          "referral.embed.rotate",
          "referral.referrer.approve",
          "referral.code.link",
          "referral.payout.reopen"
        ]
      },
      "KeyExtendResult": {
        "type": "object",
        "required": [
          "extended",
          "expires_at"
        ],
        "properties": {
          "extended": {
            "type": "boolean",
            "description": "False when it was not yet near enough to its expiry, or is already at its limit."
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "reason": {
            "type": "string",
            "description": "Why it did not move, when it did not."
          }
        }
      },
      "KeyRevokeResult": {
        "type": "object",
        "required": [
          "revoked"
        ],
        "properties": {
          "revoked": {
            "type": "boolean",
            "const": true
          }
        }
      },
      "ConnectionWorkspaceList": {
        "type": "object",
        "required": [
          "connection_id",
          "all_workspaces",
          "data",
          "next_cursor"
        ],
        "description": "Answered only to the BoltClaw MCP server, which keeps one key per workspace of a connection\nand needs each workspace's id to do it. Anything else asking is answered `404`.\n",
        "properties": {
          "connection_id": {
            "type": "string",
            "format": "uuid"
          },
          "all_workspaces": {
            "type": "boolean",
            "description": "The connection follows the organisation, including workspaces made after it was approved."
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "is_default",
                "key_id",
                "generation"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "is_default": {
                  "type": "boolean"
                },
                "key_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "generation": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "How many of this connection's keys for the workspace were revoked. The MCP server\nderives the workspace's key with it, so a workspace taken out and put back gets a new\nkey; a revoked key is never revived.\n"
                }
              }
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "ConnectionRevoke": {
        "type": "object",
        "required": [
          "key_id"
        ],
        "additionalProperties": false,
        "properties": {
          "key_id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of any key in the connection. Its root, and every sibling, end with it."
          }
        }
      },
      "ConnectionWorkspaceRegister": {
        "type": "object",
        "required": [
          "workspace_id",
          "key_hash",
          "key_prefix"
        ],
        "additionalProperties": false,
        "properties": {
          "workspace_id": {
            "type": "string",
            "format": "uuid"
          },
          "key_hash": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$"
          },
          "key_prefix": {
            "type": "string",
            "pattern": "^[0-9A-Za-z]{8}$"
          }
        }
      },
      "ConnectionWorkspaceKey": {
        "type": "object",
        "required": [
          "key_id",
          "created",
          "scopes"
        ],
        "properties": {
          "key_id": {
            "type": "string",
            "format": "uuid"
          },
          "created": {
            "type": "boolean",
            "description": "False when that workspace already had a live key."
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            }
          }
        }
      },
      "AgentCall": {
        "type": "object",
        "required": [
          "id",
          "key_id",
          "client_name",
          "tool",
          "operation_id",
          "method",
          "status",
          "duration_ms",
          "approval_id",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "key_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "client_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the assistant called itself when it signed in."
          },
          "tool": {
            "type": [
              "string",
              "null"
            ],
            "description": "The tool it said it was running. A label it chose, never acted on."
          },
          "operation_id": {
            "type": "string"
          },
          "method": {
            "type": "string",
            "enum": [
              "GET",
              "POST",
              "PATCH",
              "DELETE"
            ]
          },
          "status": {
            "type": "integer"
          },
          "duration_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "approval_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AgentCallList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AgentCall"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "AuditEntry": {
        "type": "object",
        "required": [
          "id",
          "action",
          "resource",
          "resource_id",
          "created_at",
          "actor",
          "details"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "action": {
            "type": "string"
          },
          "resource": {
            "type": [
              "string",
              "null"
            ]
          },
          "resource_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The id of what it acted on. `null` when that is the workspace itself or its\norganisation (the `workspace.*` and `impersonation.*` entries): a log entry never names\neither of those by id.\n"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "actor": {
            "type": "object",
            "required": [
              "kind",
              "label"
            ],
            "description": "Who did it, as a label. Never an account id and never an email address.",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "member",
                  "former",
                  "api_key",
                  "support",
                  "system"
                ]
              },
              "label": {
                "type": "string"
              }
            }
          },
          "details": {
            "type": "object",
            "additionalProperties": true,
            "description": "What changed. Anything that looks like a credential is `[redacted]`."
          }
        }
      },
      "AuditEntryList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AuditEntry"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "error",
          "code",
          "request_id"
        ],
        "description": "Every failed response has this shape.",
        "properties": {
          "error": {
            "type": "string",
            "description": "A sentence for a person. It never contains SQL, a stack trace, or anything belonging\nto another customer. Branch on `code`, not on this: wording may change within v1.\n"
          },
          "code": {
            "$ref": "#/components/schemas/ErrorCode"
          },
          "request_id": {
            "type": "string",
            "format": "uuid",
            "description": "This request's id, also sent as `X-Request-Id`. Quote it to support."
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "On a validation error, each field the request got wrong and the rule it broke."
          },
          "approval": {
            "$ref": "#/components/schemas/ApprovalRef"
          }
        }
      },
      "ApprovalRef": {
        "type": "object",
        "required": [
          "id",
          "url",
          "expires_at",
          "status"
        ],
        "description": "Present when an AI assistant's request waits for a person to approve it. Show `url` to the\nperson; once they approve, repeat the identical request with the approval id.\n",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending"
            ]
          }
        }
      },
      "ErrorCode": {
        "type": "string",
        "description": "Stable, machine-readable codes. New ones may be added within v1: treat an unknown code as\na generic failure of its HTTP status class.\n\n| Code | HTTP | What it means |\n|---|---|---|\n| `UNAUTHORIZED` | 401 | No key, a malformed one, or one that does not exist or was revoked. The answer never says which. |\n| `FORBIDDEN` | 403 | Refused for a reason no scope would change. The message says what to do. |\n| `NOT_FOUND` | 404 | No such object, or another workspace's. Deliberately indistinguishable. Also an unknown path. |\n| `VALIDATION_ERROR` | 400, 422 | The request or a value in it is not acceptable. `fields` names each field where it can. |\n| `CONFLICT` | 409 | The request clashes with the object's current state: a taken slug, a closed conversation, a full page. |\n| `RATE_LIMITED` | 429 | Too many requests. Wait for `Retry-After`. |\n| `METHOD_NOT_ALLOWED` | 405 | The path exists; the method does not. `Allow` lists the ones that do. |\n| `PAYLOAD_TOO_LARGE` | 413 | The body is over 64 KB. |\n| `INTERNAL_ERROR` | 500, 503 | Ours. Safe to retry, with the same `Idempotency-Key`. |\n| `ALREADY_EXISTS` | 409 | Reserved; not answered today. |\n| `PLAN_LIMIT` | 402 | The organisation's plan does not allow more of this. The message names the allowance. |\n| `INSUFFICIENT_SCOPE` | 403 | The key is valid; it was not issued for this operation. |\n| `ORGANISATION_SUSPENDED` | 403 | The key's organisation is suspended. Its published pages stay live. |\n| `UPSTREAM_ERROR` | 422, 502, 503 | A social network refused or failed the call. The message says what happened. |\n| `UNAVAILABLE` | 503 | Dashboard only; `/v1` answers `500 INTERNAL_ERROR` instead. |\n| `THREAD_ALREADY_OPEN` | 409 | Dashboard only: the person already has an open conversation on that page. |\n| `MFA_REQUIRED` | 401 | Dashboard only: the session owes its second sign-in step. |\n| `REAUTH_REQUIRED` | 401 | Dashboard only: changing where money goes needs a sign-in in the last 15 minutes. |\n| `TWO_STEP_REQUIRED` | 403 | Dashboard only: sending payouts or connecting a payout account needs two-step verification. |\n| `IDEMPOTENCY_KEY_CONFLICT` | 409 | The `Idempotency-Key` was already used with a different request. |\n| `IDEMPOTENCY_KEY_IN_FLIGHT` | 409 | The first request with this `Idempotency-Key` is still running. |\n| `AGENT_ACCESS_DISABLED` | 403 | An AI assistant's key, in an organisation that has turned assistants off. |\n| `APPROVAL_REQUIRED` | 428 | A person must approve this AI assistant's request first. Nothing has happened. |\n| `APPROVAL_PENDING` | 428 | The approval has not been decided yet. Nothing has happened. |\n| `APPROVAL_DENIED` | 403 | A person declined the request. Nothing has happened. |\n| `APPROVAL_INVALID` | 409 | The approval is spent, has lapsed, or is for another request or another key. |\n",
        "x-dashboard-only": [
          "THREAD_ALREADY_OPEN",
          "MFA_REQUIRED",
          "UNAVAILABLE",
          "REAUTH_REQUIRED",
          "TWO_STEP_REQUIRED"
        ],
        "x-reserved": [
          "ALREADY_EXISTS"
        ],
        "enum": [
          "UNAUTHORIZED",
          "FORBIDDEN",
          "NOT_FOUND",
          "VALIDATION_ERROR",
          "CONFLICT",
          "RATE_LIMITED",
          "METHOD_NOT_ALLOWED",
          "PAYLOAD_TOO_LARGE",
          "INTERNAL_ERROR",
          "ALREADY_EXISTS",
          "PLAN_LIMIT",
          "INSUFFICIENT_SCOPE",
          "ORGANISATION_SUSPENDED",
          "UPSTREAM_ERROR",
          "UNAVAILABLE",
          "THREAD_ALREADY_OPEN",
          "MFA_REQUIRED",
          "REAUTH_REQUIRED",
          "TWO_STEP_REQUIRED",
          "IDEMPOTENCY_KEY_CONFLICT",
          "IDEMPOTENCY_KEY_IN_FLIGHT",
          "AGENT_ACCESS_DISABLED",
          "APPROVAL_REQUIRED",
          "APPROVAL_PENDING",
          "APPROVAL_DENIED",
          "APPROVAL_INVALID"
        ]
      },
      "Ping": {
        "type": "object",
        "required": [
          "pong",
          "key_kind",
          "scopes",
          "lost_scopes"
        ],
        "properties": {
          "pong": {
            "type": "boolean",
            "const": true
          },
          "key_kind": {
            "type": "string",
            "enum": [
              "secret"
            ],
            "description": "BoltClaw keys are secret keys, for servers. There are no browser keys."
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            },
            "description": "What the key may do right now."
          },
          "lost_scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            },
            "description": "Scopes the key was issued with and no longer has, because the person who created it\nlost the permission behind them or left the workspace. Normally empty. Anything here\nexplains a `403 INSUFFICIENT_SCOPE` on an operation the key was created for.\n"
          }
        }
      },
      "Workspace": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "Scope": {
        "type": "string",
        "description": "What a key may be issued to do. A key carries one or more, and every operation names the\none it needs. A key never carries more than the person who created it: if they lose the\npermission a scope stands on, the key loses the scope on its next request, and `GET /ping`\nreports it in `lost_scopes`.\n\n`logs:read` reads the workspace's activity log and the log of what AI assistants did\n(`GET /logs/audit` and `GET /logs/agent-calls`). `workspace:read` gates nothing:\n`GET /ping` and `GET /workspace` need no scope at all.\n\n`keys:read`, `keys:write`, `webhooks:read` and `webhooks:write` cannot be carried by a key\nan AI assistant holds, whatever is asked for: issuing credentials and pointing your data at\na new address are things a person does, or a system a person deployed.\n\n`referrals:embed` is for the server that shows your own users their referrals: it mints panel\nsessions (`POST /referrals/programs/{id}/embed-tokens`), creates, updates and reads referrers,\nand nothing else. It cannot touch a program, a partner, a commission or a payout, so a leaked\nkey can show somebody a referral panel and do no more. It cannot be carried by a key an AI\nassistant holds. `referrals:write` is also accepted where this scope is, but gives far more.\n\n`referrals:payouts` is for your own payout system: it reads where a requested payout goes,\nreports what happened to it, and sends a payout batch without an AI assistant's approval\nstep. `referrals:write` alone never sends money. It cannot be carried by a key an AI\nassistant holds.\n",
        "enum": [
          "pages:read",
          "pages:write",
          "pages:delete",
          "inquiries:read",
          "inquiries:write",
          "social:read",
          "social:write",
          "leads:read",
          "conversions:write",
          "referrals:read",
          "referrals:write",
          "referrals:embed",
          "referrals:payouts",
          "keys:read",
          "keys:write",
          "webhooks:read",
          "webhooks:write",
          "logs:read",
          "workspace:read"
        ]
      },
      "ApiKeyOrigin": {
        "type": "string",
        "enum": [
          "dashboard",
          "api",
          "device",
          "oauth"
        ],
        "description": "How the key came to exist: `dashboard` in the BoltClaw dashboard, `api` minted by another\nkey through this API, `device` or `oauth` approved for an AI assistant. Never an input to\nwhat a key may do.\n"
      },
      "ApiKey": {
        "type": "object",
        "required": [
          "id",
          "name",
          "prefix",
          "scopes",
          "effective_scopes",
          "lost_scopes",
          "origin",
          "client_name",
          "last_used_at",
          "last_used_ip",
          "expires_at",
          "rotated_from",
          "revoked_at",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 60
          },
          "prefix": {
            "type": [
              "string",
              "null"
            ],
            "description": "The first 8 characters of the key's random body, so a key is recognisable in a list.\nNever the key.\n"
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            },
            "description": "What the key was issued to do."
          },
          "effective_scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            },
            "description": "What the key may do right now: `scopes`, minus anything whose permission the person\nwho created it no longer holds. This is what a request is judged against.\n"
          },
          "lost_scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            },
            "description": "`scopes` minus `effective_scopes`. Normally empty. Anything here means the person who\ncreated the key lost the permission behind it, or left the workspace: restore their\npermission, or issue the key again from somebody who holds it.\n"
          },
          "origin": {
            "$ref": "#/components/schemas/ApiKeyOrigin"
          },
          "client_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "The client a person approved for an assistant's key. Null for every other key."
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_used_ip": {
            "type": [
              "string",
              "null"
            ],
            "description": "The address the key was last seen from, recorded at most once a minute. For your own\neyes: nothing reads it back, and no decision depends on it.\n"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the key stops working, or null for a key that does not expire."
          },
          "rotated_from": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The key this one replaced. Rotation makes a new key, with a new id."
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the key stops working. A value in the FUTURE is a rotation grace window that is\nstill open: the key works until then.\n"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ApiKeyList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiKey"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NullCursor"
          }
        }
      },
      "ApiKeyCreate": {
        "type": "object",
        "required": [
          "name",
          "scopes"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 60,
            "description": "What this key is for, so it can be told apart from the next one in a list."
          },
          "scopes": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/Scope"
            },
            "description": "Asked for, not granted. A scope the calling key does not itself carry, or whose\npermission its creator does not hold, is left off and listed in `dropped`.\n"
          },
          "expires_in_days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 365,
            "description": "Omit for a key that does not expire. Clamped to the calling key's own expiry, so a\nchild never outlives its parent.\n"
          }
        }
      },
      "ApiKeyUpdate": {
        "type": "object",
        "minProperties": 1,
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 60
          },
          "scopes": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/Scope"
            },
            "description": "The new set, in full. The key itself does not change, so whatever holds it keeps\nworking.\n"
          }
        }
      },
      "ApiKeyRotate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "grace_seconds": {
            "type": "integer",
            "minimum": 0,
            "maximum": 86400,
            "description": "How long the old key keeps working, so the new one can reach every deployment first.\nZero, the default, stops it at once, which is what you want when the old value leaked.\n"
          }
        }
      },
      "ApiKeyCreated": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ApiKey"
          },
          {
            "type": "object",
            "required": [
              "dropped",
              "secret"
            ],
            "properties": {
              "dropped": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Scope"
                },
                "description": "Scopes that were asked for and not granted. Compare it with `scopes`."
              },
              "secret": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The key itself: `bc_live_` and 43 letters and digits. SHOWN ONCE. Only a hash is\nstored and no endpoint can produce it again. Null on an idempotent replay, which\ntells you the key was already created; rotate it if you lost the value.\n"
              }
            }
          }
        ]
      },
      "ApiKeyUpdated": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ApiKey"
          },
          {
            "type": "object",
            "required": [
              "dropped"
            ],
            "properties": {
              "dropped": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Scope"
                },
                "description": "Scopes that were asked for and not granted."
              }
            }
          }
        ]
      },
      "ApiKeyRotated": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ApiKeyCreated"
          },
          {
            "type": "object",
            "required": [
              "previous_valid_until"
            ],
            "properties": {
              "previous_valid_until": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "When the key this replaced stops working. Null when it stopped at once.\n"
              }
            }
          }
        ]
      },
      "Deleted": {
        "type": "object",
        "required": [
          "deleted",
          "id"
        ],
        "properties": {
          "deleted": {
            "type": "boolean",
            "const": true
          },
          "id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "NullCursor": {
        "type": "null",
        "description": "Always null; this list is never paged."
      },
      "NextCursor": {
        "type": [
          "string",
          "null"
        ],
        "description": "Pass back as `cursor` for the next page; null on the last page."
      },
      "Slug": {
        "type": "string",
        "minLength": 1,
        "maxLength": 63,
        "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
        "description": "Lowercase letters, digits and single hyphens. Reserved names are refused."
      },
      "Theme": {
        "type": "object",
        "additionalProperties": false,
        "description": "The page's colours and style. Each value is a string of 1 to 64 characters.",
        "properties": {
          "background": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          },
          "surface": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          },
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          },
          "muted": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          },
          "accent": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          },
          "accentText": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          },
          "border": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          },
          "font": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          },
          "buttonStyle": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          },
          "radius": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          }
        }
      },
      "Social": {
        "type": "object",
        "required": [
          "platform",
          "url"
        ],
        "additionalProperties": false,
        "properties": {
          "platform": {
            "type": "string",
            "pattern": "^[a-z0-9-]{1,32}$"
          },
          "url": {
            "type": "string",
            "maxLength": 2048,
            "pattern": "^(https?://|mailto:|tel:)"
          }
        }
      },
      "Page": {
        "type": "object",
        "required": [
          "id",
          "slug",
          "title",
          "bio",
          "avatar_url",
          "theme",
          "socials",
          "contact_enabled",
          "contact_note",
          "official_channel",
          "published",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "$ref": "#/components/schemas/Slug"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "bio": {
            "type": [
              "string",
              "null"
            ]
          },
          "avatar_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "theme": {
            "$ref": "#/components/schemas/Theme"
          },
          "socials": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Social"
            }
          },
          "contact_enabled": {
            "type": "boolean"
          },
          "contact_note": {
            "type": [
              "string",
              "null"
            ],
            "description": "The creator's own words above the message box."
          },
          "official_channel": {
            "type": "boolean",
            "description": "The creator's promise that this is the only channel they answer through."
          },
          "published": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PageList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Page"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "PageCreate": {
        "type": "object",
        "required": [
          "slug"
        ],
        "additionalProperties": false,
        "properties": {
          "slug": {
            "$ref": "#/components/schemas/Slug"
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "120 characters by default; your organisation's number is `page_title_chars` in `GET /usage`."
          },
          "bio": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 600,
            "description": "600 characters by default; your organisation's number is `page_bio_chars` in `GET /usage`."
          },
          "theme": {
            "$ref": "#/components/schemas/Theme"
          },
          "socials": {
            "type": "array",
            "maxItems": 12,
            "description": "12 icons by default; your organisation's number is `social_icons_per_page` in\n`GET /usage`. A page already holding more may save the same number or fewer.\n",
            "items": {
              "$ref": "#/components/schemas/Social"
            }
          },
          "contact_enabled": {
            "type": "boolean"
          },
          "contact_note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "300 characters by default; your organisation's number is `contact_note_chars` in `GET /usage`."
          },
          "official_channel": {
            "type": "boolean"
          },
          "published": {
            "type": "boolean"
          }
        }
      },
      "PageUpdate": {
        "type": "object",
        "minProperties": 1,
        "additionalProperties": false,
        "description": "At least one field. The slug cannot change.",
        "properties": {
          "title": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "120 characters by default; your organisation's number is `page_title_chars` in `GET /usage`."
          },
          "bio": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 600,
            "description": "600 characters by default; your organisation's number is `page_bio_chars` in `GET /usage`."
          },
          "theme": {
            "$ref": "#/components/schemas/Theme"
          },
          "socials": {
            "type": "array",
            "maxItems": 12,
            "description": "12 icons by default; your organisation's number is `social_icons_per_page` in\n`GET /usage`. A page already holding more may save the same number or fewer.\n",
            "items": {
              "$ref": "#/components/schemas/Social"
            }
          },
          "contact_enabled": {
            "type": "boolean"
          },
          "contact_note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "300 characters by default; your organisation's number is `contact_note_chars` in `GET /usage`."
          },
          "official_channel": {
            "type": "boolean"
          },
          "published": {
            "type": "boolean"
          }
        }
      },
      "PageDeleted": {
        "type": "object",
        "required": [
          "deleted",
          "slug",
          "message"
        ],
        "properties": {
          "deleted": {
            "type": "boolean",
            "const": true
          },
          "slug": {
            "$ref": "#/components/schemas/Slug"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "PageStats": {
        "type": "object",
        "required": [
          "slug",
          "from",
          "to",
          "views",
          "clicks",
          "links"
        ],
        "properties": {
          "slug": {
            "$ref": "#/components/schemas/Slug"
          },
          "from": {
            "type": "string",
            "format": "date-time"
          },
          "to": {
            "type": "string",
            "format": "date-time"
          },
          "views": {
            "type": "integer"
          },
          "clicks": {
            "type": "integer"
          },
          "links": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "label",
                "clicks"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "label": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "clicks": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "Link": {
        "type": "object",
        "required": [
          "id",
          "label",
          "url",
          "icon",
          "position",
          "visible",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "label": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          },
          "icon": {
            "type": [
              "string",
              "null"
            ]
          },
          "position": {
            "type": "integer"
          },
          "visible": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "LinkList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "maxItems": 5000,
            "description": "At most 1,000 for a page by default: a safety ceiling the same on every plan. A page that\nhad more when its ceiling was lowered keeps them, and every one is listed, up to 5,000,\nthe most any page can ever hold.\n",
            "items": {
              "$ref": "#/components/schemas/Link"
            }
          }
        }
      },
      "LinkCreate": {
        "type": "object",
        "required": [
          "url"
        ],
        "additionalProperties": false,
        "properties": {
          "label": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "120 characters by default; your organisation's number is `link_label_chars` in `GET /usage`."
          },
          "url": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "pattern": "^(https?://|mailto:|tel:)"
          },
          "icon": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 32
          },
          "position": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000
          },
          "visible": {
            "type": "boolean"
          }
        }
      },
      "LinkUpdate": {
        "type": "object",
        "minProperties": 1,
        "additionalProperties": false,
        "properties": {
          "label": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "120 characters by default; your organisation's number is `link_label_chars` in `GET /usage`."
          },
          "url": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "pattern": "^(https?://|mailto:|tel:)"
          },
          "icon": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 32
          },
          "position": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000
          },
          "visible": {
            "type": "boolean"
          }
        }
      },
      "InquiryStatus": {
        "type": "string",
        "enum": [
          "new",
          "read",
          "replied",
          "archived",
          "spam"
        ]
      },
      "Inquiry": {
        "type": "object",
        "required": [
          "id",
          "page_slug",
          "channel",
          "participant",
          "subject",
          "status",
          "closed_at",
          "last_message_at",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "page_slug": {
            "type": [
              "string",
              "null"
            ],
            "description": "The page it was started on."
          },
          "channel": {
            "type": "string",
            "enum": [
              "page"
            ]
          },
          "participant": {
            "type": [
              "object",
              "null"
            ],
            "description": "The person who wrote in, as they are known on this page only: `id` is stable on this\npage and unrelated to the same person on any other page. There is no email.\n",
            "required": [
              "id",
              "display_name"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "display_name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "subject": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/InquiryStatus"
          },
          "closed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_message_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "InquiryList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Inquiry"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "InquiryUpdate": {
        "type": "object",
        "required": [
          "status"
        ],
        "additionalProperties": false,
        "properties": {
          "status": {
            "$ref": "#/components/schemas/InquiryStatus"
          }
        }
      },
      "InquiryClose": {
        "type": "object",
        "required": [
          "closed"
        ],
        "additionalProperties": false,
        "properties": {
          "closed": {
            "type": "boolean"
          }
        }
      },
      "Message": {
        "type": "object",
        "required": [
          "id",
          "from",
          "body",
          "read_at",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "from": {
            "type": "string",
            "enum": [
              "you",
              "them"
            ],
            "description": "`you` is the creator's side, whoever on the team wrote it; `them` is the person who wrote in."
          },
          "body": {
            "type": "string"
          },
          "read_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MessageList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            }
          }
        }
      },
      "MessageCreate": {
        "type": "object",
        "required": [
          "body"
        ],
        "additionalProperties": false,
        "properties": {
          "body": {
            "type": "string",
            "minLength": 1,
            "maxLength": 5000
          }
        }
      },
      "MessageSent": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "MarkedRead": {
        "type": "object",
        "required": [
          "marked_read"
        ],
        "properties": {
          "marked_read": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "WebhookEvent": {
        "type": "string",
        "description": "An event an endpoint can subscribe to. `GET /webhooks/events` returns the live list with a\ndescription of each; read that rather than hard-coding this enum, because an event added\nlater appears there first.\n",
        "enum": [
          "conversation.message.created",
          "social.inbox.item.created",
          "social.post.published",
          "social.post.failed",
          "lead.created",
          "referral.partner.applied",
          "referral.partner.approved",
          "referral.conversion.created",
          "referral.commission.created",
          "referral.bonus.created",
          "referral.referrer.created",
          "referral.referrer.conversion",
          "referral.referrer.reward_earned",
          "referral.referrer.payout_details_changed",
          "referral.commission.reversed",
          "referral.payout.paid",
          "referral.grant.ready",
          "referral.grant.fulfilled",
          "referral.grant.cancelled",
          "referral.grant.reversed",
          "referral.referee.invite_ready",
          "referral.risk.alert",
          "referral.payout.sent",
          "referral.payout.failed",
          "referral.payout.returned",
          "referral.payout.requested",
          "referral.tax_form.signed",
          "referral.tax_form.expiring",
          "referral.invoice.issued",
          "referral.invoice.disputed",
          "referral.broadcast.sent",
          "referral.partner.imported_claimed",
          "referral.contest.ended",
          "referral.milestone.reached",
          "referral.waitlist.entry_created",
          "referral.waitlist.position_changed",
          "webhook.test"
        ]
      },
      "WebhookEventType": {
        "type": "object",
        "required": [
          "event",
          "resource",
          "label",
          "description"
        ],
        "properties": {
          "event": {
            "$ref": "#/components/schemas/WebhookEvent"
          },
          "resource": {
            "type": "string",
            "description": "What the event is about, and what you fetch to get the content."
          },
          "label": {
            "type": "string"
          },
          "description": {
            "type": "string"
          }
        }
      },
      "WebhookEventTypeList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          }
        }
      },
      "Webhook": {
        "type": "object",
        "required": [
          "id",
          "url",
          "events",
          "description",
          "page_slugs",
          "enabled",
          "disabled_at",
          "consecutive_failures",
          "last_success_at",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            },
            "description": "Null for every event, now and in future."
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "page_slugs": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/Slug"
            },
            "description": "Null for every page, now and in future."
          },
          "enabled": {
            "type": "boolean",
            "description": "False while the endpoint is paused. Events are not queued and are not replayed later."
          },
          "disabled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "consecutive_failures": {
            "type": "integer"
          },
          "last_success_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "test_events": {
            "type": "boolean",
            "description": "Whether this endpoint also receives TEST events (referral test sales, test-mode and\nsandbox events, `test: true` conversions). A test delivery's body carries\n`\"test\": true`; a live one never does.\n"
          }
        }
      },
      "WebhookList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Webhook"
            }
          }
        }
      },
      "WebhookCreate": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "minLength": 12,
            "maxLength": 2048,
            "description": "An `https` URL at a public host. Loopback, private ranges and bare IP addresses are refused."
          },
          "events": {
            "type": [
              "array",
              "null"
            ],
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            },
            "description": "Which events this endpoint hears. Leave it out, or send null, for every event now and\nin future. An unknown name is refused rather than dropped.\n"
          },
          "page_slugs": {
            "type": [
              "array",
              "null"
            ],
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/Slug"
            },
            "description": "Which pages this endpoint hears about. Leave it out, or send null, for every page now\nand in future. Events that are not about a page only ever reach an endpoint that\ncovers all of them.\n"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100
          },
          "test_events": {
            "type": "boolean",
            "description": "Also receive TEST events, each with `\"test\": true` in its body (default false)."
          }
        }
      },
      "WebhookUpdate": {
        "type": "object",
        "additionalProperties": false,
        "description": "Send only the fields you want to change. An empty object is `400`.",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "minLength": 12,
            "maxLength": 2048
          },
          "events": {
            "type": [
              "array",
              "null"
            ],
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            }
          },
          "page_slugs": {
            "type": [
              "array",
              "null"
            ],
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/Slug"
            }
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100
          },
          "enabled": {
            "type": "boolean",
            "description": "False pauses the endpoint, keeping it and its signing secret. True resumes it and\nclears the failure count.\n"
          },
          "test_events": {
            "type": "boolean",
            "description": "Also receive TEST events, each with `\"test\": true` in its body."
          }
        }
      },
      "WebhookCreated": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Webhook"
          },
          {
            "type": "object",
            "required": [
              "secret",
              "warning"
            ],
            "properties": {
              "secret": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The signing secret, shown once. `null` on an idempotent replay: the endpoint\nexists, and the value is not stored anywhere it could be read again. Rotate it if\nyou did not record it.\n"
              },
              "warning": {
                "type": "string"
              }
            }
          }
        ]
      },
      "WebhookSecretRotated": {
        "type": "object",
        "required": [
          "id",
          "secret",
          "warning"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "secret": {
            "type": [
              "string",
              "null"
            ],
            "description": "The new signing secret, shown once. `null` on an idempotent replay."
          },
          "warning": {
            "type": "string"
          }
        }
      },
      "WebhookDeleted": {
        "type": "object",
        "required": [
          "deleted",
          "id"
        ],
        "properties": {
          "deleted": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "required": [
          "id",
          "delivery_id",
          "event",
          "attempt",
          "payload",
          "status",
          "response_status",
          "response_excerpt",
          "error",
          "duration_ms",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "delivery_id": {
            "type": "string",
            "format": "uuid",
            "description": "Stable across every attempt of one occurrence, and the value the `X-BoltClaw-Delivery`\nheader carries. Key your idempotency off it.\n"
          },
          "event": {
            "$ref": "#/components/schemas/WebhookEvent"
          },
          "attempt": {
            "type": "integer",
            "minimum": 1
          },
          "payload": {
            "type": "object",
            "additionalProperties": true,
            "description": "The exact body that was signed and sent. Identifiers only, by design."
          },
          "status": {
            "type": "string",
            "enum": [
              "delivered",
              "failed"
            ]
          },
          "response_status": {
            "type": [
              "integer",
              "null"
            ]
          },
          "response_excerpt": {
            "type": [
              "string",
              "null"
            ],
            "description": "At most 512 bytes of what your endpoint answered. Null when there was nothing to keep,\nor when the address finally reached was not one we would deliver to.\n"
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why it failed, as one of a fixed set of sentences. Null when it succeeded. Never your\nown server's words.\n"
          },
          "duration_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookDeliveryList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookDelivery"
            }
          }
        }
      },
      "WebhookQueued": {
        "type": "object",
        "required": [
          "id",
          "status",
          "delivery_id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending"
            ]
          },
          "delivery_id": {
            "type": "string",
            "format": "uuid",
            "description": "The new occurrence id. The body is the one that was frozen when the event happened."
          }
        }
      },
      "WebhookTestQueued": {
        "type": "object",
        "required": [
          "id",
          "event",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "event": {
            "type": "string",
            "enum": [
              "webhook.test"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending"
            ]
          }
        }
      },
      "EmptyBody": {
        "type": "object",
        "additionalProperties": false,
        "description": "This operation takes no fields. Send `{}`, or no body at all."
      },
      "Network": {
        "type": "string",
        "enum": [
          "instagram",
          "facebook",
          "threads",
          "bluesky",
          "x",
          "tiktok",
          "linkedin",
          "youtube",
          "pinterest"
        ]
      },
      "Capabilities": {
        "type": "object",
        "required": [
          "publish",
          "comments",
          "hide_comment",
          "dms",
          "private_reply",
          "webhooks",
          "max_body_length",
          "max_media",
          "text_only",
          "video"
        ],
        "properties": {
          "publish": {
            "type": "boolean"
          },
          "comments": {
            "type": "boolean"
          },
          "hide_comment": {
            "type": "boolean"
          },
          "dms": {
            "type": "boolean"
          },
          "private_reply": {
            "type": "boolean"
          },
          "webhooks": {
            "type": "boolean"
          },
          "max_body_length": {
            "type": "integer"
          },
          "max_media": {
            "type": "integer"
          },
          "text_only": {
            "type": "boolean"
          },
          "video": {
            "type": "boolean"
          }
        }
      },
      "Account": {
        "type": "object",
        "required": [
          "id",
          "network",
          "handle",
          "display_name",
          "avatar_url",
          "status",
          "last_error",
          "connected_at",
          "capabilities"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "network": {
            "$ref": "#/components/schemas/Network"
          },
          "handle": {
            "type": [
              "string",
              "null"
            ]
          },
          "display_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "avatar_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "needs_reauth",
              "disconnected"
            ]
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "connected_at": {
            "type": "string",
            "format": "date-time"
          },
          "capabilities": {
            "$ref": "#/components/schemas/Capabilities"
          }
        }
      },
      "AccountList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Account"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NullCursor"
          }
        }
      },
      "Media": {
        "type": "object",
        "required": [
          "id",
          "mime_type",
          "bytes",
          "url",
          "status",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "mime_type": {
            "type": "string"
          },
          "bytes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "ready"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MediaCreate": {
        "type": "object",
        "required": [
          "mime_type",
          "filename",
          "bytes"
        ],
        "additionalProperties": false,
        "properties": {
          "mime_type": {
            "type": "string",
            "enum": [
              "image/jpeg",
              "image/png",
              "image/webp",
              "image/gif",
              "video/mp4",
              "video/quicktime"
            ]
          },
          "filename": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "bytes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 536870912
          }
        }
      },
      "MediaUpload": {
        "type": "object",
        "required": [
          "media",
          "upload"
        ],
        "properties": {
          "media": {
            "$ref": "#/components/schemas/Media"
          },
          "upload": {
            "type": [
              "object",
              "null"
            ],
            "description": "Where to upload the file, once. Null on an idempotent replay.",
            "required": [
              "signed_url",
              "token",
              "path"
            ],
            "properties": {
              "signed_url": {
                "type": "string"
              },
              "token": {
                "type": "string"
              },
              "path": {
                "type": "string"
              }
            }
          }
        }
      },
      "PostStatus": {
        "type": "string",
        "enum": [
          "draft",
          "scheduled",
          "publishing",
          "published",
          "partial",
          "failed",
          "cancelled"
        ]
      },
      "Target": {
        "type": "object",
        "required": [
          "id",
          "account_id",
          "network",
          "handle",
          "status",
          "external_url",
          "last_error",
          "published_at",
          "metrics"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "account_id": {
            "type": "string",
            "format": "uuid"
          },
          "network": {
            "$ref": "#/components/schemas/Network"
          },
          "handle": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "publishing",
              "processing",
              "published",
              "failed",
              "cancelled"
            ]
          },
          "external_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "published_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "metrics": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "number"
            },
            "description": "The network's own counters, by the network's own names."
          }
        }
      },
      "PostOptions": {
        "type": "object",
        "description": "Per-network options, keyed by network, in each network's own vocabulary.",
        "propertyNames": {
          "$ref": "#/components/schemas/Network"
        },
        "additionalProperties": {
          "type": "object"
        }
      },
      "Post": {
        "type": "object",
        "required": [
          "id",
          "body",
          "media",
          "options",
          "status",
          "scheduled_for",
          "published_at",
          "source",
          "created_at",
          "updated_at",
          "targets"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "body": {
            "type": "string"
          },
          "media": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Media"
            }
          },
          "options": {
            "$ref": "#/components/schemas/PostOptions"
          },
          "status": {
            "$ref": "#/components/schemas/PostStatus"
          },
          "scheduled_for": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "published_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "source": {
            "type": "string",
            "enum": [
              "dashboard",
              "api",
              "mcp"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "targets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Target"
            }
          }
        }
      },
      "PostList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Post"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "PostCreate": {
        "type": "object",
        "required": [
          "account_ids"
        ],
        "additionalProperties": false,
        "description": "Exactly one of `scheduled_for`, `publish_now: true` or `draft: true`, and a `body` or at\nleast one media id.\n",
        "properties": {
          "body": {
            "type": "string",
            "maxLength": 5000
          },
          "media_ids": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "account_ids": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "scheduled_for": {
            "type": "string",
            "format": "date-time"
          },
          "publish_now": {
            "type": "boolean"
          },
          "draft": {
            "type": "boolean"
          },
          "options": {
            "$ref": "#/components/schemas/PostOptions"
          }
        }
      },
      "PostUpdate": {
        "type": "object",
        "minProperties": 1,
        "additionalProperties": false,
        "properties": {
          "body": {
            "type": "string",
            "maxLength": 5000
          },
          "media_ids": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "account_ids": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "scheduled_for": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "options": {
            "$ref": "#/components/schemas/PostOptions"
          }
        }
      },
      "InboxKind": {
        "type": "string",
        "enum": [
          "comment",
          "dm",
          "mention"
        ]
      },
      "InboxItem": {
        "type": "object",
        "required": [
          "id",
          "account_id",
          "network",
          "kind",
          "direction",
          "thread_key",
          "post_external_id",
          "author_handle",
          "author_name",
          "body",
          "permalink",
          "status",
          "read_at",
          "replied_at",
          "occurred_at",
          "automation_id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "account_id": {
            "type": "string",
            "format": "uuid"
          },
          "network": {
            "$ref": "#/components/schemas/Network"
          },
          "kind": {
            "$ref": "#/components/schemas/InboxKind"
          },
          "direction": {
            "type": "string",
            "enum": [
              "inbound",
              "outbound"
            ]
          },
          "thread_key": {
            "type": "string"
          },
          "post_external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "author_handle": {
            "type": [
              "string",
              "null"
            ]
          },
          "author_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "body": {
            "type": "string"
          },
          "permalink": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "done",
              "hidden"
            ]
          },
          "read_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "replied_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "automation_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          }
        }
      },
      "InboxList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InboxItem"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "InboxThread": {
        "type": "object",
        "required": [
          "item",
          "thread"
        ],
        "properties": {
          "item": {
            "$ref": "#/components/schemas/InboxItem"
          },
          "thread": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InboxItem"
            }
          }
        }
      },
      "InboxStatusUpdate": {
        "type": "object",
        "required": [
          "status"
        ],
        "additionalProperties": false,
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "open",
              "done"
            ]
          }
        }
      },
      "InboxReply": {
        "type": "object",
        "required": [
          "text"
        ],
        "additionalProperties": false,
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 5000,
            "description": "Trimmed; not blank."
          }
        }
      },
      "InboxHide": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "hidden": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "Automation": {
        "type": "object",
        "required": [
          "id",
          "account_id",
          "network",
          "name",
          "trigger",
          "keywords",
          "match_mode",
          "post_external_ids",
          "public_replies",
          "dm_message",
          "link_url",
          "link_label",
          "collect",
          "collect_prompt",
          "use_ai",
          "enabled",
          "triggered_count",
          "dms_sent",
          "leads_captured",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "account_id": {
            "type": "string",
            "format": "uuid"
          },
          "network": {
            "$ref": "#/components/schemas/Network"
          },
          "name": {
            "type": "string"
          },
          "trigger": {
            "type": "string",
            "enum": [
              "comment_keyword",
              "dm_keyword"
            ]
          },
          "keywords": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "match_mode": {
            "type": "string",
            "enum": [
              "contains",
              "exact"
            ]
          },
          "post_external_ids": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            }
          },
          "public_replies": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "dm_message": {
            "type": "string"
          },
          "link_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "link_label": {
            "type": [
              "string",
              "null"
            ]
          },
          "collect": {
            "type": "string",
            "enum": [
              "none",
              "email",
              "phone"
            ]
          },
          "collect_prompt": {
            "type": [
              "string",
              "null"
            ]
          },
          "use_ai": {
            "type": "boolean"
          },
          "enabled": {
            "type": "boolean"
          },
          "triggered_count": {
            "type": "integer"
          },
          "dms_sent": {
            "type": "integer"
          },
          "leads_captured": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AutomationList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Automation"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NullCursor"
          }
        }
      },
      "AutomationFields": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "dm_message": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1000
          },
          "trigger": {
            "type": "string",
            "enum": [
              "comment_keyword",
              "dm_keyword"
            ]
          },
          "keywords": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            }
          },
          "match_mode": {
            "type": "string",
            "enum": [
              "contains",
              "exact"
            ]
          },
          "post_external_ids": {
            "type": [
              "array",
              "null"
            ],
            "maxItems": 50,
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          },
          "public_replies": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 1000
            }
          },
          "link_url": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2048,
            "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://"
          },
          "link_label": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80
          },
          "collect": {
            "type": "string",
            "enum": [
              "none",
              "email",
              "phone"
            ]
          },
          "collect_prompt": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1000
          },
          "use_ai": {
            "type": "boolean"
          },
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "AutomationCreate": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AutomationFields"
          },
          {
            "type": "object",
            "required": [
              "account_id",
              "name",
              "dm_message"
            ],
            "properties": {
              "account_id": {
                "type": "string",
                "format": "uuid"
              }
            }
          }
        ],
        "unevaluatedProperties": false
      },
      "AutomationUpdate": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AutomationFields"
          }
        ],
        "minProperties": 1,
        "unevaluatedProperties": false,
        "description": "At least one field. The account cannot change."
      },
      "Lead": {
        "type": "object",
        "required": [
          "id",
          "account_id",
          "automation_id",
          "network",
          "handle",
          "name",
          "email",
          "phone",
          "source",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "account_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "automation_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "network": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Network"
              },
              {
                "type": "null"
              }
            ]
          },
          "handle": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "comment_to_dm",
              "dm",
              "api"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "LeadList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Lead"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "Usage": {
        "type": "object",
        "required": [
          "plan",
          "period_start",
          "limits",
          "usage",
          "social_accounts",
          "networks",
          "platform_charges"
        ],
        "properties": {
          "plan": {
            "type": "string"
          },
          "period_start": {
            "type": "string",
            "format": "date"
          },
          "limits": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Each limit by name, such as `posts_per_month`: the number that applies to your\norganisation right now, including any we have raised for it. Besides the allowances it\ncarries the field sizes and ceilings a write is held to (`page_title_chars`,\n`page_bio_chars`, `link_label_chars`, `contact_note_chars`, `social_icons_per_page`,\n`links_per_page`, `webhook_endpoints`) and the hourly budgets\n(`api_pages_created_per_hour`, `api_messages_per_hour`, `thread_messages_per_hour`).\nAI is two allowances: `ai_credits_per_month`, one credit per AI draft (paid plans; 0\non Free), spent first, then `ai_credits_per_day`, where 1 credit is 10 tokens of what\nthe AI reads and writes (3,000 on Free, about 70 for a reply draft); and\n`ai_requests_per_minute` drafts in any 60 seconds.\n"
          },
          "usage": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "This month's use, by allowance name (`ai_credits` counts this month's AI drafts), and\n`ai_credits_today`: today's AI credits, by UTC date, against `ai_credits_per_day`.\nThey reset at midnight UTC and what is left is not carried over.\n"
          },
          "social_accounts": {
            "type": "integer"
          },
          "networks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Network"
            }
          },
          "platform_charges": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "count",
              "billed_mills",
              "by_operation"
            ],
            "description": "This month's per-call charges billed on top of the plan, in thousandths of a US dollar.\n`null` unless the person who created the key may manage the organisation's billing:\nthe charges are the organisation's bill, not the workspace's.\n",
            "properties": {
              "count": {
                "type": "integer"
              },
              "billed_mills": {
                "type": "integer"
              },
              "by_operation": {
                "type": "object",
                "description": "Keyed by the network operation's own name.",
                "additionalProperties": {
                  "type": "object",
                  "required": [
                    "count",
                    "billed_mills"
                  ],
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "billed_mills": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "CommissionStatus": {
        "type": "string",
        "enum": [
          "pending",
          "approved",
          "payable",
          "paid",
          "reversed",
          "fraud"
        ]
      },
      "ConversionEvent": {
        "type": "string",
        "enum": [
          "lead",
          "sale",
          "refund",
          "dispute"
        ]
      },
      "Conversion": {
        "type": "object",
        "required": [
          "id",
          "event",
          "amount_minor",
          "currency",
          "occurred_at",
          "attributed",
          "partner",
          "commission",
          "commissions",
          "duplicate"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "event": {
            "$ref": "#/components/schemas/ConversionEvent"
          },
          "amount_minor": {
            "type": "integer",
            "description": "Minor units. 0 for a lead; for a refund, this refund alone."
          },
          "currency": {
            "type": [
              "string",
              "null"
            ]
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "attributed": {
            "type": "boolean",
            "description": "Whether a partner was credited."
          },
          "partner": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "display_name"
            ],
            "properties": {
              "display_name": {
                "type": "string"
              }
            }
          },
          "commission": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "amount_minor",
              "currency",
              "status"
            ],
            "properties": {
              "amount_minor": {
                "type": "integer"
              },
              "currency": {
                "type": "string"
              },
              "status": {
                "$ref": "#/components/schemas/CommissionStatus"
              }
            }
          },
          "commissions": {
            "type": "array",
            "description": "Every commission the conversion created, net of reversals, each with why it is that\namount. A sale whose items matched two reward rules earns two; `commission` is the\nfirst of them.\n",
            "items": {
              "type": "object",
              "required": [
                "amount_minor",
                "currency",
                "status",
                "basis_text"
              ],
              "properties": {
                "amount_minor": {
                  "type": "integer"
                },
                "currency": {
                  "type": "string"
                },
                "status": {
                  "$ref": "#/components/schemas/CommissionStatus"
                },
                "basis_text": {
                  "type": "string",
                  "description": "Why this amount, in one sentence."
                }
              }
            }
          },
          "duplicate": {
            "type": "boolean",
            "description": "True when this event_id had been reported before and nothing changed."
          },
          "grants": {
            "type": "array",
            "description": "The rewards that are not cash this conversion gave, to the promoter or to the friend,\neach in words. A friend's discount is here only when the sale shows it was used.\n",
            "items": {
              "type": "object",
              "required": [
                "beneficiary",
                "reward_type",
                "status",
                "summary"
              ],
              "properties": {
                "beneficiary": {
                  "type": "string",
                  "enum": [
                    "promoter",
                    "referee"
                  ]
                },
                "reward_type": {
                  "$ref": "#/components/schemas/GrantType"
                },
                "status": {
                  "$ref": "#/components/schemas/GrantStatus"
                },
                "summary": {
                  "type": "string",
                  "description": "What it is, in a few words: \"1 month free\"."
                }
              }
            }
          },
          "referee_referrer": {
            "type": [
              "object",
              "null"
            ],
            "description": "Invite after purchase: the friend who just made their first payment now has a referral\nlink of their own, so your thank-you page can ask them to invite their friends. Null\nunless the program asks (`invite_after_purchase`), you named the customer by your own\nid, and the link exists.\n",
            "required": [
              "link",
              "code"
            ],
            "properties": {
              "link": {
                "type": "string"
              },
              "code": {
                "type": "string"
              }
            }
          },
          "referral_refused": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "late_code",
              null
            ],
            "description": "A referral code that was sent and not applied, and why: `late_code` when it came more\nthan the program's `late_code_days` after the customer signed up. Null otherwise.\n"
          },
          "test": {
            "type": "boolean",
            "description": "TEST data: a test sale, `test: true`, or a test-mode or sandbox event from your billing\nsystem. Credited to the program's test partner only; never payable, never counted.\n"
          }
        }
      },
      "ConversionCreate": {
        "type": "object",
        "required": [
          "program_id",
          "event",
          "event_id"
        ],
        "additionalProperties": false,
        "properties": {
          "program_id": {
            "type": "string",
            "format": "uuid"
          },
          "event": {
            "$ref": "#/components/schemas/ConversionEvent"
          },
          "event_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "YOUR id for this event, and the idempotency key. Reporting it again changes nothing, so\na retry sends the same one; a new one is a new conversion.\n"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "customer": {
            "type": "object",
            "required": [
              "id"
            ],
            "additionalProperties": false,
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 255
              },
              "email": {
                "type": "string",
                "minLength": 3,
                "maxLength": 320,
                "description": "Hashed on arrival and never stored."
              }
            }
          },
          "referral": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "code": {
                "type": "string",
                "maxLength": 64
              },
              "discount_code": {
                "type": "string",
                "maxLength": 64
              },
              "discount_applied": {
                "type": "boolean",
                "description": "True when the friend's referral discount was applied to this sale in your own\ncheckout. A friend's discount is recorded only on evidence it was used: this, a\ndiscount code, or (from Stripe) a coupon or promotion code on the payment.\n"
              },
              "sub": {
                "type": "string",
                "maxLength": 80,
                "description": "The partner's campaign tag the website tag kept (`BoltClawReferral.sub()`, from\n`/r/<code>?sub=yt`): lower-case letters, digits, `_` and `-`. Shown in the\npartner's results by tag; ignored when it is not one. From Stripe, metadata\n`boltclaw_sub`.\n"
              },
              "click_id": {
                "type": "string",
                "maxLength": 32,
                "description": "The click id the website tag kept (`BoltClawReferral.clickId()`, 16 letters and digits), so\nthe time from the click to this conversion counts in the risk score. Ignored when it\nis not one. From Stripe, `client_reference_id` as `<code>--<click id>` or\n`metadata.boltclaw_click` does the same.\n"
              }
            }
          },
          "amount": {
            "type": "integer",
            "minimum": 0,
            "description": "Minor units."
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Za-z]{3}$"
          },
          "refund_of": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "The sale's event_id."
          },
          "items": {
            "type": "array",
            "maxItems": 100,
            "description": "A sale's lines, so a reward rule can pay per product, price, plan or interval. Their\n`amount`s must add up to the sale's `amount` (`400` otherwise). Leave them out and the\nsale is matched as a whole.\n",
            "items": {
              "$ref": "#/components/schemas/SaleItem"
            }
          },
          "test": {
            "type": "boolean",
            "description": "Record this as TEST data: credited to the program's test partner whoever's code it\ncarries, never payable, never counted toward a limit or a total, emailing nobody, and\nsent only to webhook endpoints that receive test events. A test `event_id` and a live\none never collide, and neither do test and live customers. Deleted with `DELETE\n/v1/referrals/programs/{id}/test-data`.\n"
          }
        }
      },
      "SaleItem": {
        "type": "object",
        "required": [
          "amount"
        ],
        "additionalProperties": false,
        "properties": {
          "product": {
            "type": "string",
            "maxLength": 200,
            "description": "Your product id, or Stripe's `prod_…`."
          },
          "price": {
            "type": "string",
            "maxLength": 200,
            "description": "Your price or plan id, or Stripe's `price_…`."
          },
          "plan": {
            "type": "string",
            "maxLength": 200,
            "description": "A plan name in your own words, matched without regard to case."
          },
          "interval": {
            "type": "string",
            "enum": [
              "month",
              "year",
              "one_time"
            ]
          },
          "quantity": {
            "type": "integer",
            "minimum": 1
          },
          "amount": {
            "type": "integer",
            "minimum": 0,
            "description": "This line's share of the sale, minor units."
          }
        }
      },
      "IntegrationStatus": {
        "type": "object",
        "required": [
          "program_id",
          "checked_at",
          "overall",
          "parts",
          "integrations",
          "test_conversions"
        ],
        "properties": {
          "program_id": {
            "type": "string",
            "format": "uuid"
          },
          "checked_at": {
            "type": "string",
            "format": "date-time"
          },
          "overall": {
            "$ref": "#/components/schemas/SetupState"
          },
          "parts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SetupPart"
            }
          },
          "integrations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Integration"
            }
          },
          "test_conversions": {
            "type": "integer",
            "description": "Test conversions the program holds now. `DELETE /v1/referrals/programs/{id}/test-data` deletes them."
          }
        }
      },
      "SetupState": {
        "type": "string",
        "enum": [
          "connected",
          "warnings",
          "not_connected"
        ]
      },
      "SetupPart": {
        "type": "object",
        "required": [
          "part",
          "label",
          "state",
          "skipped",
          "summary",
          "fix",
          "warnings"
        ],
        "properties": {
          "part": {
            "type": "string",
            "enum": [
              "tag",
              "billing",
              "signups",
              "sales",
              "codes",
              "panel",
              "welcome"
            ]
          },
          "label": {
            "type": "string"
          },
          "state": {
            "$ref": "#/components/schemas/SetupState"
          },
          "skipped": {
            "type": "boolean",
            "description": "The program's own setup does not use this part; it does not count toward `overall`."
          },
          "summary": {
            "type": "string",
            "description": "One sentence about what was seen",
            "or not.": null
          },
          "fix": {
            "type": [
              "string",
              "null"
            ],
            "description": "What to do next; null when nothing is needed."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "code",
                "message",
                "fix"
              ],
              "properties": {
                "code": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                },
                "fix": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "TagCheck": {
        "type": "object",
        "required": [
          "result",
          "version",
          "consent",
          "payment_links",
          "sentence",
          "checked_at"
        ],
        "properties": {
          "result": {
            "type": "string",
            "enum": [
              "found_v2",
              "found_v1",
              "not_found",
              "refused",
              "unreachable"
            ]
          },
          "version": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "v1",
              "v2",
              null
            ]
          },
          "consent": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the tag waits for consent (`data-consent=\"required\"`)."
          },
          "payment_links": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "none",
              "ready",
              "missing",
              null
            ],
            "description": "Stripe Payment Links or a Buy Button on the page: `none`, `ready` (the version 2 tag\nhas `data-payment-links=\"auto\"`, which is the whole integration for them) or `missing`\n(there are some and the tag does not reach them; the sentence says what to add).\nNull when the page was not read.\n"
          },
          "sentence": {
            "type": "string",
            "description": "A fixed sentence to show."
          },
          "checked_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TestSale": {
        "type": "object",
        "required": [
          "run_id",
          "steps"
        ],
        "properties": {
          "run_id": {
            "type": "string"
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TestSaleStep"
            }
          }
        }
      },
      "TestSaleStep": {
        "type": "object",
        "required": [
          "step",
          "run_id",
          "summary",
          "partner",
          "conversion",
          "offer",
          "webhooks",
          "test"
        ],
        "properties": {
          "step": {
            "type": "string",
            "enum": [
              "partner",
              "click",
              "lead",
              "sale",
              "refund"
            ]
          },
          "run_id": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "partner": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "id",
              "display_name",
              "link",
              "code"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "display_name": {
                "type": "string"
              },
              "link": {
                "type": "string",
                "description": "The test partner's link. Archived: no visitor can open it."
              },
              "code": {
                "type": "string"
              }
            }
          },
          "conversion": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Conversion"
              },
              {
                "type": "null"
              }
            ]
          },
          "offer": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Offer"
              },
              {
                "type": "null"
              }
            ]
          },
          "webhooks": {
            "type": "object",
            "required": [
              "queued",
              "test_endpoints"
            ],
            "properties": {
              "queued": {
                "type": "integer",
                "description": "Deliveries this step queued for endpoints that receive test events."
              },
              "test_endpoints": {
                "type": "integer"
              }
            }
          },
          "test": {
            "type": "boolean",
            "enum": [
              true
            ]
          }
        }
      },
      "TestDataCleared": {
        "type": "object",
        "required": [
          "conversions",
          "commissions",
          "grants",
          "customers",
          "clicks",
          "partners"
        ],
        "properties": {
          "conversions": {
            "type": "integer"
          },
          "commissions": {
            "type": "integer"
          },
          "grants": {
            "type": "integer"
          },
          "customers": {
            "type": "integer"
          },
          "clicks": {
            "type": "integer"
          },
          "partners": {
            "type": "integer"
          }
        }
      },
      "ReferralClickCreate": {
        "type": "object",
        "required": [
          "code"
        ],
        "additionalProperties": false,
        "properties": {
          "code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "description": "The partner link's code (`?via=`)."
          },
          "ip": {
            "type": "string",
            "maxLength": 64,
            "description": "The visitor's IP address",
            "as your server saw it. Hashed": null,
            "never stored.": null
          },
          "user_agent": {
            "type": "string",
            "maxLength": 1024,
            "description": "Classed as mobile",
            "tablet": null,
            "desktop or a robot; never stored.": null
          },
          "referrer": {
            "type": "string",
            "maxLength": 2048,
            "description": "The referring page. Only its host is kept."
          },
          "country": {
            "type": "string",
            "maxLength": 8,
            "description": "An ISO 3166 two-letter country code, if your edge sets one."
          },
          "landing_path": {
            "type": "string",
            "maxLength": 2048,
            "description": "The path the visitor landed on. Not stored."
          }
        }
      },
      "ReferralClickRecorded": {
        "type": "object",
        "required": [
          "recorded",
          "reason",
          "program_id"
        ],
        "properties": {
          "recorded": {
            "type": "boolean"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "bot",
              "allowance",
              null
            ]
          },
          "program_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "FlagRule": {
        "type": "string",
        "description": "Which rule raised a flag. `risk_review` is the risk engine asking a person (the score\nreached the program's `review_at`); `self_referral_card` is one card paying for the\npromoter's own customers; `payout_change` is a payout built inside the window after a\npartner changed where they are paid; `manual_hold` is \"Hold this promoter's payouts\".\n`other` is a rule this version of the API does not name yet.\n",
        "enum": [
          "self_referral",
          "click_velocity",
          "duplicate_payout",
          "risk_review",
          "self_referral_card",
          "payout_change",
          "manual_hold",
          "other"
        ]
      },
      "Flag": {
        "type": "object",
        "required": [
          "id",
          "rule",
          "score",
          "status",
          "subject",
          "created_at",
          "resolved_at",
          "note",
          "partner",
          "commission",
          "conversion",
          "link"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "rule": {
            "$ref": "#/components/schemas/FlagRule"
          },
          "score": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 100,
            "description": "The risk score behind a `risk_review` flag; null for the other rules."
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "dismissed",
              "confirmed"
            ]
          },
          "subject": {
            "type": "string",
            "enum": [
              "click",
              "partner",
              "conversion",
              "commission"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "resolved_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your note",
            "written when it was decided.": null
          },
          "partner": {
            "type": [
              "object",
              "null"
            ],
            "description": "The partner it is about, by their name in this program.",
            "required": [
              "id",
              "display_name"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "display_name": {
                "type": "string"
              }
            }
          },
          "commission": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "id",
              "status",
              "amount_minor",
              "currency"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "status": {
                "$ref": "#/components/schemas/CommissionStatus"
              },
              "amount_minor": {
                "type": "integer"
              },
              "currency": {
                "type": "string"
              }
            }
          },
          "conversion": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "id",
              "event",
              "amount_minor",
              "currency",
              "occurred_at",
              "customer_external_id"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "event": {
                "$ref": "#/components/schemas/ConversionEvent"
              },
              "amount_minor": {
                "type": "integer"
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "occurred_at": {
                "type": "string",
                "format": "date-time"
              },
              "customer_external_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Your own id for the customer."
              }
            }
          },
          "link": {
            "type": [
              "object",
              "null"
            ],
            "description": "A velocity flag's link, by its code and type.",
            "required": [
              "code",
              "kind",
              "placement"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "primary",
                  "placement"
                ]
              },
              "placement": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "bio",
                  "post",
                  "automation",
                  "manual",
                  null
                ]
              }
            }
          }
        }
      },
      "FlagList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "maxItems": 200,
            "items": {
              "$ref": "#/components/schemas/Flag"
            }
          }
        }
      },
      "FlagDetail": {
        "type": "object",
        "required": [
          "flag",
          "score",
          "reasons",
          "assessed_at",
          "timeline",
          "history"
        ],
        "properties": {
          "flag": {
            "$ref": "#/components/schemas/Flag"
          },
          "score": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 100
          },
          "reasons": {
            "type": "array",
            "description": "Every rule that counted, with its weight and what it saw, as a sentence.",
            "items": {
              "type": "object",
              "required": [
                "code",
                "weight",
                "sentence"
              ],
              "properties": {
                "code": {
                  "type": "string",
                  "description": "The rule, for example `fast_conversion`, `datacenter_click`, `paid_ads`, `code_leak_spike`."
                },
                "weight": {
                  "type": "integer"
                },
                "sentence": {
                  "type": "string",
                  "description": "What was seen, in counts and times: never an address, a card or an email."
                }
              }
            }
          },
          "assessed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "timeline": {
            "type": "array",
            "description": "The customer's click, signup, sale, refunds and disputes in order, with the seconds between them.",
            "items": {
              "type": "object",
              "required": [
                "kind",
                "at",
                "seconds_after_previous",
                "amount_minor",
                "currency",
                "flagged"
              ],
              "properties": {
                "kind": {
                  "type": "string",
                  "enum": [
                    "click",
                    "lead",
                    "sale",
                    "refund",
                    "dispute"
                  ]
                },
                "at": {
                  "type": "string",
                  "format": "date-time"
                },
                "seconds_after_previous": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "amount_minor": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "currency": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "flagged": {
                  "type": "boolean",
                  "description": "The step this flag is about."
                }
              }
            }
          },
          "history": {
            "type": [
              "object",
              "null"
            ],
            "description": "The partner's history in this program.",
            "required": [
              "age_days",
              "clicks30",
              "customers30",
              "conversion_rate_bps",
              "sales60",
              "refunded60",
              "other_flags"
            ],
            "properties": {
              "age_days": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Days since they were approved (or applied)."
              },
              "clicks30": {
                "type": "integer"
              },
              "customers30": {
                "type": "integer"
              },
              "conversion_rate_bps": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Customers per counted click",
                "in basis points.": null
              },
              "sales60": {
                "type": "integer"
              },
              "refunded60": {
                "type": "integer"
              },
              "other_flags": {
                "type": "object",
                "required": [
                  "open",
                  "dismissed",
                  "confirmed"
                ],
                "properties": {
                  "open": {
                    "type": "integer"
                  },
                  "dismissed": {
                    "type": "integer"
                  },
                  "confirmed": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        }
      },
      "FlagDecision": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "Kept on the flag and in the activity log."
          }
        }
      },
      "FlagConfirm": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "ban_partner": {
            "type": "boolean",
            "description": "Ban the partner from the program too (default false)."
          }
        }
      },
      "FlagsBulk": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "flag_ids",
          "note"
        ],
        "properties": {
          "flag_ids": {
            "type": "array",
            "minItems": 1,
            "maxItems": 200,
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "note": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500,
            "description": "One reason for all of them."
          }
        }
      },
      "FlagsBulkConfirm": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "flag_ids",
          "note"
        ],
        "properties": {
          "flag_ids": {
            "type": "array",
            "minItems": 1,
            "maxItems": 200,
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "note": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500,
            "description": "One reason for all of them."
          },
          "ban_partners": {
            "type": "boolean",
            "description": "Ban each flag's partner too (default false)."
          }
        }
      },
      "FlagResolution": {
        "type": "object",
        "required": [
          "flag",
          "commissions",
          "banned"
        ],
        "properties": {
          "flag": {
            "$ref": "#/components/schemas/Flag"
          },
          "commissions": {
            "type": "integer",
            "description": "Commissions this decision moved to `fraud`; 0 for a dismissal."
          },
          "banned": {
            "type": "boolean"
          }
        }
      },
      "FlagsResolution": {
        "type": "object",
        "required": [
          "decided",
          "commissions",
          "grants",
          "banned",
          "skipped"
        ],
        "properties": {
          "decided": {
            "type": "integer"
          },
          "commissions": {
            "type": "integer",
            "description": "Commissions moved to `fraud`."
          },
          "grants": {
            "type": "integer",
            "description": "Rewards that are not cash",
            "cancelled before they were given.": null
          },
          "banned": {
            "type": "integer"
          },
          "skipped": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "flag_id",
                "status"
              ],
              "properties": {
                "flag_id": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "not_found",
                    "not_open",
                    "in_payout"
                  ]
                }
              }
            }
          }
        }
      },
      "PayoutHold": {
        "type": "object",
        "required": [
          "flag",
          "already"
        ],
        "properties": {
          "flag": {
            "$ref": "#/components/schemas/Flag"
          },
          "already": {
            "type": "boolean",
            "description": "True when the partner was already held."
          }
        }
      },
      "RiskPolicy": {
        "type": "object",
        "required": [
          "hold_at",
          "review_at",
          "extra_hold_days",
          "min_seconds_lead",
          "min_seconds_sale",
          "min_seconds_shop",
          "allow_paid_search",
          "campaign_allowlist",
          "hold_new_partners"
        ],
        "properties": {
          "hold_at": {
            "type": "integer",
            "description": "From this score, a conversion's earnings wait `extra_hold_days` longer."
          },
          "review_at": {
            "type": "integer",
            "description": "From this score a person is asked (`risk_review`)."
          },
          "extra_hold_days": {
            "type": "integer"
          },
          "min_seconds_lead": {
            "type": "integer",
            "description": "Fewer seconds from click to signup than this look automated."
          },
          "min_seconds_sale": {
            "type": "integer"
          },
          "min_seconds_shop": {
            "type": "integer",
            "description": "The same for a shop order."
          },
          "allow_paid_search": {
            "type": "boolean",
            "description": "False when partners may not bid on your brand in paid search."
          },
          "campaign_allowlist": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Campaign ids that are allowed when paid search is not."
          },
          "hold_new_partners": {
            "type": "boolean",
            "description": "A promoter's first days in the program (90 by default) count toward the score, and their earnings wait a little longer. On unless you switch it off."
          }
        }
      },
      "RiskPolicyFields": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "hold_at": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          },
          "review_at": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "description": "Never below `hold_at`."
          },
          "extra_hold_days": {
            "type": "integer",
            "minimum": 0,
            "maximum": 365
          },
          "min_seconds_lead": {
            "type": "integer",
            "minimum": 0,
            "maximum": 3600
          },
          "min_seconds_sale": {
            "type": "integer",
            "minimum": 0,
            "maximum": 3600
          },
          "min_seconds_shop": {
            "type": "integer",
            "minimum": 0,
            "maximum": 3600
          },
          "allow_paid_search": {
            "type": "boolean"
          },
          "campaign_allowlist": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_.:-]{1,100}$"
            }
          },
          "hold_new_partners": {
            "type": "boolean"
          }
        }
      },
      "RiskPolicyView": {
        "type": "object",
        "required": [
          "program_id",
          "policy",
          "custom",
          "alert_email"
        ],
        "properties": {
          "program_id": {
            "type": "string",
            "format": "uuid"
          },
          "policy": {
            "$ref": "#/components/schemas/RiskPolicy"
          },
          "custom": {
            "$ref": "#/components/schemas/RiskPolicyFields"
          },
          "alert_email": {
            "type": "boolean",
            "description": "Email the program's managers when a promoter needs a look (at most once per promoter a day)."
          }
        }
      },
      "RiskPolicyPatch": {
        "type": "object",
        "additionalProperties": false,
        "description": "Only the fields you send change. A field sent as null goes back to the platform's value.",
        "properties": {
          "hold_at": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 100
          },
          "review_at": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 100,
            "description": "Never below `hold_at`."
          },
          "extra_hold_days": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 365
          },
          "min_seconds_lead": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 3600
          },
          "min_seconds_sale": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 3600
          },
          "min_seconds_shop": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 3600
          },
          "allow_paid_search": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "campaign_allowlist": {
            "type": [
              "array",
              "null"
            ],
            "maxItems": 50,
            "items": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_.:-]{1,100}$"
            }
          },
          "hold_new_partners": {
            "type": [
              "boolean",
              "null"
            ]
          }
        }
      },
      "RiskPolicyUpdate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "policy": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/RiskPolicyPatch"
              },
              {
                "type": "null"
              }
            ],
            "description": "The fields to change: only those you send change, and one sent as null goes back to the\nplatform's value. `policy: null` clears every field the program set.\n"
          },
          "alert_email": {
            "type": "boolean"
          }
        }
      },
      "PartnerApplication": {
        "type": "object",
        "description": "What the partner wrote when they applied, and what the checks saw. Counts only: never which\nother programs, which accounts, or an address.\n",
        "required": [
          "questions",
          "answers",
          "website",
          "account_age_days",
          "templated",
          "burst"
        ],
        "properties": {
          "questions": {
            "type": "array",
            "maxItems": 3,
            "items": {
              "type": "string"
            },
            "description": "The program's questions as the partner was asked them; empty when it asked none."
          },
          "answers": {
            "type": "array",
            "maxItems": 3,
            "items": {
              "type": "string"
            },
            "description": "In the order of `questions`."
          },
          "website": {
            "type": [
              "string",
              "null"
            ]
          },
          "account_age_days": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Days since the partner's Bolt Claw account was made."
          },
          "templated": {
            "type": [
              "integer",
              "null"
            ],
            "description": "\"Looks templated\": near-identical applications to other programs this week, or null."
          },
          "burst": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Accounts that applied from one network and device in 24 hours, or null when it was not a burst."
          }
        }
      },
      "Integration": {
        "type": "object",
        "required": [
          "id",
          "provider",
          "webhook_url",
          "has_secret",
          "customer_metadata_key",
          "status",
          "last_event_at",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "provider": {
            "$ref": "#/components/schemas/BillingProvider"
          },
          "webhook_url": {
            "type": "string"
          },
          "has_secret": {
            "type": "boolean"
          },
          "customer_metadata_key": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled",
              "disconnected"
            ],
            "description": "`disconnected`: a Stripe App connection that was uninstalled or disconnected."
          },
          "last_event_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "codes_and_credits": {
            "type": "object",
            "description": "Whether a restricted Stripe key is set, which lets Bolt Claw make codes and give credit\nand free months in your Stripe. The key itself is never returned, not even in part.\n",
            "required": [
              "connected",
              "mode",
              "set_at"
            ],
            "properties": {
              "connected": {
                "type": "boolean"
              },
              "mode": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "live",
                  "test",
                  null
                ]
              },
              "set_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "payload_format": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "snapshot",
              "thin",
              null
            ],
            "description": "The shape of the last verified Stripe event. `thin` events carry no data; choose Snapshot in Stripe."
          },
          "auth_mode": {
            "type": "string",
            "enum": [
              "signing_secret",
              "basic",
              "header_token",
              "oauth",
              "api_key",
              "shopify_app"
            ],
            "description": "How a delivery proves itself: a signing secret, Basic credentials Bolt Claw generated\n(Chargebee), a header token Bolt Claw generated (RevenueCat), the Stripe App (`oauth`),\nan unsigned ping confirmed through the provider's API with your token (Gumroad), or Bolt\nClaw's Shopify app (`shopify_app`), linked from the shop's own Shopify admin.\n"
          },
          "test_mode": {
            "type": "boolean",
            "description": "The Sandbox switch. Every event of this integration is recorded as test data."
          },
          "credential": {
            "type": "object",
            "description": "Whether the credential is saved (or generated), and since when. Never the value.",
            "required": [
              "saved",
              "set_at",
              "username"
            ],
            "properties": {
              "saved": {
                "type": "boolean"
              },
              "set_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "username": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Chargebee's Basic username. Not secret by itself."
              }
            }
          },
          "read_key": {
            "type": "object",
            "description": "Recurly's read-only key or Gumroad's token. Whether one is saved, never the value.",
            "required": [
              "saved",
              "set_at"
            ],
            "properties": {
              "saved": {
                "type": "boolean"
              },
              "set_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "provider_account": {
            "type": [
              "string",
              "null"
            ],
            "description": "Recurly's region (`us` or `eu`), or the Shopify shop that sent the first delivery."
          },
          "shopify": {
            "type": [
              "object",
              "null"
            ],
            "description": "Bolt Claw's Shopify app, linked to this program from the shop's own Shopify admin. Null\nfor every other kind of integration. `connected` is false once it was unlinked or the\nshop uninstalled the app.\n",
            "required": [
              "shop",
              "connected",
              "connected_at",
              "disconnected_at"
            ],
            "properties": {
              "shop": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The shop's myshopify.com name."
              },
              "connected": {
                "type": "boolean"
              },
              "connected_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "disconnected_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "app": {
            "type": [
              "object",
              "null"
            ],
            "description": "The Stripe App connection. Null for every other kind of integration.",
            "required": [
              "connected",
              "account_id",
              "livemode",
              "connected_at",
              "disconnected_at"
            ],
            "properties": {
              "connected": {
                "type": "boolean"
              },
              "account_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Your own Stripe account id."
              },
              "livemode": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "connected_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "disconnected_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "health": {
            "type": "object",
            "description": "What the setup status reads. Fixed words and counts; nothing from a delivery.",
            "required": [
              "failures_since_event",
              "last_failure_at",
              "last_failure_reason",
              "api_version",
              "event_types_seen",
              "last_livemode"
            ],
            "properties": {
              "failures_since_event": {
                "type": "integer",
                "description": "Deliveries refused since the last verified one."
              },
              "last_failure_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "last_failure_reason": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "not_configured",
                  "missing_signature",
                  "bad_signature",
                  "stale",
                  "bad_credentials",
                  "bad_body",
                  null
                ]
              },
              "api_version": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Stripe: the API version its events are rendered with."
              },
              "event_types_seen": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Up to 64 event types seen."
              },
              "last_livemode": {
                "type": [
                  "boolean",
                  "null"
                ]
              }
            }
          }
        }
      },
      "BillingProvider": {
        "type": "string",
        "description": "A billing system Bolt Claw connects to directly.",
        "enum": [
          "stripe",
          "paddle",
          "polar",
          "whop",
          "revenuecat",
          "woocommerce",
          "fastspring",
          "lemonsqueezy",
          "shopify",
          "chargebee",
          "recurly",
          "gumroad"
        ]
      },
      "Program": {
        "type": "object",
        "required": [
          "id",
          "name",
          "product_url",
          "currency",
          "cookie_days",
          "attribution_model",
          "hold_days",
          "min_payout_minor",
          "payout_schedule",
          "join_mode",
          "auto_approve",
          "signup_url",
          "terms",
          "terms_version",
          "preview_title",
          "preview_description",
          "preview_image_url",
          "customer_discount",
          "discount_code_prefix",
          "status",
          "created_at",
          "updated_at",
          "pending_partners",
          "integrations",
          "kind",
          "partner_monthly_cap_minor",
          "embed",
          "welcome",
          "late_code_days",
          "lead_grant_daily_cap",
          "invite_after_purchase",
          "ask_snooze_days"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "product_url": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "cookie_days": {
            "type": "integer"
          },
          "attribution_model": {
            "type": "string",
            "enum": [
              "last_click",
              "first_click"
            ]
          },
          "hold_days": {
            "type": "integer"
          },
          "min_payout_minor": {
            "type": "integer"
          },
          "payout_schedule": {
            "type": "string",
            "enum": [
              "manual",
              "monthly"
            ]
          },
          "join_mode": {
            "type": "string",
            "enum": [
              "apply",
              "invite"
            ]
          },
          "auto_approve": {
            "type": "boolean"
          },
          "signup_url": {
            "type": "string",
            "description": "The program's public join page."
          },
          "terms": {
            "type": [
              "string",
              "null"
            ]
          },
          "terms_version": {
            "type": "integer"
          },
          "preview_title": {
            "type": [
              "string",
              "null"
            ]
          },
          "preview_description": {
            "type": [
              "string",
              "null"
            ]
          },
          "preview_image_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_discount": {
            "type": [
              "string",
              "null"
            ]
          },
          "discount_code_prefix": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused",
              "archived"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "pending_partners": {
            "type": "integer"
          },
          "integrations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Integration"
            }
          },
          "kind": {
            "$ref": "#/components/schemas/ProgramKind"
          },
          "partner_monthly_cap_minor": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The most one partner earns from this program in a calendar month (UTC), bonuses included. Null for no ceiling."
          },
          "embed": {
            "$ref": "#/components/schemas/EmbedSettings"
          },
          "welcome": {
            "$ref": "#/components/schemas/WelcomeSettings"
          },
          "late_code_days": {
            "type": "integer",
            "description": "How many days after sign-up a referral code may still be entered; 0 for never after."
          },
          "lead_grant_daily_cap": {
            "type": "integer",
            "description": "The most rewards a day (UTC) that signups alone may earn, across the program."
          },
          "invite_after_purchase": {
            "type": "boolean",
            "description": "Whether a friend's first payment gives them a referral link of their own."
          },
          "ask_snooze_days": {
            "type": "integer",
            "description": "How long \"Not right now\" in the panel lasts before a person is asked again."
          },
          "application_questions": {
            "type": "array",
            "maxItems": 3,
            "items": {
              "type": "string"
            },
            "description": "Up to three questions a partner answers when they apply, shown on the program's join page."
          },
          "risk_alert_email": {
            "type": "boolean",
            "description": "Whether the program's managers are emailed when a promoter needs a look. Set with `PATCH /referrals/programs/{id}/risk-policy`."
          },
          "disclosure_text": {
            "type": "string",
            "description": "The sentence partners add when they share a link or a code (\"I earn a commission if you\nbuy through my link.\"). Never empty. Partners are reminded of it when they copy a link,\nwhen they put one in a post, a bio page or an automation, after every library snippet,\nand in the in-app panel.\n"
          },
          "leaderboard": {
            "description": "The leaderboard promoters see; null when they see none (you always see yours).",
            "oneOf": [
              {
                "$ref": "#/components/schemas/LeaderboardSettings"
              },
              {
                "type": "null"
              }
            ]
          },
          "waitlist": {
            "description": "A waitlist program's settings and its hosted page's address; null for any other program.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/WaitlistSettings"
              },
              {
                "type": "null"
              }
            ]
          },
          "second_tier": {
            "description": "The second tier; null while it is off. Set with `PATCH /referrals/programs/{id}/second-tier`.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/SecondTier"
              },
              {
                "type": "null"
              }
            ]
          },
          "terms_required_version": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The terms version every approved partner must accept before their next payout (a change to the second tier asks it)."
          },
          "setup": {
            "type": "object",
            "description": "The answers to \"How do you take payments?\" and \"How do people sign up?\", which choose the setup steps.",
            "required": [
              "payments",
              "signups",
              "audience_eu_uk"
            ],
            "properties": {
              "payments": {
                "$ref": "#/components/schemas/SetupPayments"
              },
              "signups": {
                "$ref": "#/components/schemas/SetupSignups"
              },
              "audience_eu_uk": {
                "type": "boolean",
                "description": "The program's audience includes the EU or the UK, so the setup status asks for the tag's consent mode."
              }
            }
          }
        }
      },
      "SetupPayments": {
        "type": [
          "string",
          "null"
        ],
        "enum": [
          "stripe",
          "paddle",
          "polar",
          "whop",
          "revenuecat",
          "woocommerce",
          "fastspring",
          "lemonsqueezy",
          "shopify",
          "chargebee",
          "recurly",
          "gumroad",
          "own_system",
          "not_sure",
          null
        ]
      },
      "SetupSignups": {
        "type": [
          "string",
          "null"
        ],
        "enum": [
          "own_backend",
          "supabase",
          "firebase",
          "clerk",
          "authjs",
          "nocode",
          "checkout_only",
          null
        ]
      },
      "WelcomeSettings": {
        "type": "object",
        "description": "The friend's welcome, as you set it.",
        "required": [
          "config",
          "show_name",
          "page",
          "allowed_hosts"
        ],
        "properties": {
          "config": {
            "$ref": "#/components/schemas/WelcomeConfig"
          },
          "show_name": {
            "type": "boolean",
            "description": "Whether a referrer's name (or an affiliate's, when they agreed) is shown to the friend."
          },
          "page": {
            "type": "boolean",
            "description": "Whether a person opening the link first sees a one-screen welcome before your product."
          },
          "allowed_hosts": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Hosts your website tag may read the offer from, beside the product URL's own."
          }
        }
      },
      "WelcomeConfig": {
        "type": "object",
        "additionalProperties": false,
        "description": "The welcome's look and words. Text is shown as plain text, never markup: angle brackets,\ncontrols and invisible marks are refused. The call to action needs both its label and its\naddress.\n",
        "properties": {
          "style": {
            "type": "string",
            "enum": [
              "bar",
              "corner",
              "sheet"
            ]
          },
          "position": {
            "type": "string",
            "enum": [
              "top",
              "bottom"
            ]
          },
          "accent": {
            "type": "string",
            "pattern": "^#[0-9a-fA-F]{6}$",
            "description": "A colour like `#1a2b3c`."
          },
          "radius": {
            "type": "integer",
            "minimum": 0,
            "maximum": 24
          },
          "theme": {
            "type": "string",
            "enum": [
              "light",
              "dark",
              "auto"
            ]
          },
          "headline": {
            "type": "string",
            "maxLength": 120,
            "description": "Your headline. `{name}` is the inviter (or \"A friend\"), `{offer}` the gift."
          },
          "conditions": {
            "type": "string",
            "maxLength": 160,
            "description": "The one line of conditions."
          },
          "cta_label": {
            "type": "string",
            "maxLength": 40
          },
          "cta_url": {
            "type": "string",
            "maxLength": 2048,
            "description": "An https address."
          }
        }
      },
      "ProgramKind": {
        "type": "string",
        "description": "The template a program was made from. It sets the starting values and the words the\nscreens use for promoters; it never switches a feature off. A `waitlist` program's\npromoters are the people on its waitlist.\n",
        "enum": [
          "affiliate",
          "refer_a_friend",
          "creator",
          "ambassador",
          "reseller",
          "shop",
          "waitlist"
        ]
      },
      "ProgramList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Program"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "ProgramFields": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "product_url": {
            "type": "string",
            "maxLength": 4096,
            "description": "An https URL."
          },
          "currency": {
            "type": "string",
            "maxLength": 8
          },
          "cookie_days": {
            "type": "integer",
            "description": "1 to 365."
          },
          "attribution_model": {
            "type": "string",
            "maxLength": 20,
            "description": "`last_click` or `first_click`."
          },
          "hold_days": {
            "type": "integer"
          },
          "min_payout_minor": {
            "type": "integer"
          },
          "payout_schedule": {
            "type": "string",
            "maxLength": 20,
            "description": "`manual` or `monthly`."
          },
          "join_mode": {
            "type": "string",
            "maxLength": 20,
            "description": "`apply` or `invite`."
          },
          "auto_approve": {
            "type": "boolean"
          },
          "terms": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20000
          },
          "preview_title": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 400
          },
          "preview_description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1000
          },
          "customer_discount": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1000
          },
          "discount_code_prefix": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40
          },
          "partner_monthly_cap_minor": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The most one partner earns in a calendar month (UTC), in minor units; null for no ceiling."
          },
          "embed_enabled": {
            "type": "boolean",
            "description": "Turns the in-app referral panel on or off. Off, no session can be minted and the panel does not load."
          },
          "embed_origins": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string",
              "maxLength": 255
            },
            "description": "The sites allowed to frame the panel, like `https://app.example.com`. Replaces the list. https only\n(`http://localhost:3000` for development), no wildcard, no path, at most 10.\n"
          },
          "embed_config": {
            "description": "The panel look, copy and sharing, replacing the old settings whole. Unknown keys are `400`.",
            "allOf": [
              {
                "$ref": "#/components/schemas/EmbedConfig"
              }
            ]
          },
          "approve_referrers": {
            "type": "boolean",
            "description": "True (default): a new referrer is approved at once. False: they are `pending` until you approve them."
          },
          "welcome_config": {
            "description": "The welcome's look and words, replacing the old settings whole; null for the defaults. Unknown keys are `400`.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/WelcomeConfig"
              },
              {
                "type": "null"
              }
            ]
          },
          "welcome_show_name": {
            "type": "boolean",
            "description": "Show a referrer's name to the friend (default true). An affiliate's is shown only when they agreed."
          },
          "welcome_page": {
            "type": "boolean",
            "description": "A one-screen welcome at the link before your product (default false)."
          },
          "allowed_hosts": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string",
              "maxLength": 253
            },
            "description": "Extra hosts your website tag may read the offer from, like `www.example.com`, beside the\nproduct URL's own and the panel's framing origins. Replaces the list. Host names only,\nat most 10. An AI assistant's key cannot change it.\n"
          },
          "late_code_days": {
            "type": "integer",
            "minimum": 0,
            "maximum": 365,
            "description": "How many days after sign-up a referral code may still be entered (default 14; 0 for never after)."
          },
          "lead_grant_daily_cap": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100000,
            "description": "The most rewards a day (UTC) signups alone may earn across the program (default 100)."
          },
          "invite_after_purchase": {
            "type": "boolean",
            "description": "Give a friend who makes their first payment a referral link of their own (default false)."
          },
          "ask_snooze_days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 365,
            "description": "How long \"Not right now\" in the panel lasts (default 30)."
          },
          "setup_payments": {
            "$ref": "#/components/schemas/SetupPayments"
          },
          "setup_signups": {
            "$ref": "#/components/schemas/SetupSignups"
          },
          "audience_eu_uk": {
            "type": "boolean",
            "description": "Your audience includes the EU or the UK (default false). The setup status then asks for the tag's consent mode."
          },
          "application_questions": {
            "type": "array",
            "maxItems": 3,
            "items": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Up to three questions a partner answers when they apply (each 1 to 200 characters, no\nhidden characters). Answers are shown on their application; an empty list asks nothing.\n"
          },
          "disclosure_text": {
            "type": "string",
            "minLength": 10,
            "maxLength": 280,
            "description": "The sentence partners add when they share (plain text, 10 to 280 characters). It may be\nedited, never emptied: partners must tell people they earn from the link (FTC, ASA).\nA new program starts with one for its currency's region.\n"
          },
          "leaderboard": {
            "$ref": "#/components/schemas/LeaderboardFields"
          },
          "waitlist": {
            "$ref": "#/components/schemas/WaitlistFields"
          }
        }
      },
      "ProgramCreate": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ProgramFields"
          },
          {
            "type": "object",
            "required": [
              "name",
              "product_url"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "maxLength": 40,
                "description": "Start from a template: `affiliate`, `refer_a_friend`, `creator`, `ambassador`,\n`reseller`, `shop` or `waitlist`. The program gets that kind's settings and\nreward rules (and levels, for `reseller`; the waitlist itself, for `waitlist`),\nwith anything you send yourself taking precedence, in one step. Left out, the\nprogram starts with no rules, as before.\n"
              }
            }
          }
        ],
        "unevaluatedProperties": false
      },
      "ProgramUpdate": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ProgramFields"
          }
        ],
        "unevaluatedProperties": false,
        "description": "Every field optional; a field left out keeps its value."
      },
      "PartnerStatus": {
        "type": "string",
        "enum": [
          "pending",
          "approved",
          "rejected",
          "banned"
        ]
      },
      "PartnerStats": {
        "type": "object",
        "required": [
          "clicks",
          "leads",
          "sales",
          "revenue_minor",
          "commission_minor",
          "conversion_rate",
          "epc_minor"
        ],
        "properties": {
          "clicks": {
            "type": "integer"
          },
          "leads": {
            "type": "integer"
          },
          "sales": {
            "type": "integer"
          },
          "revenue_minor": {
            "type": "integer"
          },
          "commission_minor": {
            "type": "integer"
          },
          "conversion_rate": {
            "type": [
              "number",
              "null"
            ],
            "description": "Sales per click, 0 to 1. Null with no clicks."
          },
          "epc_minor": {
            "type": [
              "number",
              "null"
            ],
            "description": "Earnings per click, in minor units. Null with no clicks."
          }
        }
      },
      "Partner": {
        "type": "object",
        "required": [
          "id",
          "display_name",
          "status",
          "country",
          "payout_method",
          "share_email",
          "email",
          "terms_version_accepted",
          "applied_at",
          "approved_at",
          "stats",
          "group",
          "group_pinned",
          "application"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The partner's membership in this program."
          },
          "display_name": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/PartnerStatus"
          },
          "country": {
            "type": [
              "string",
              "null"
            ]
          },
          "payout_method": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "paypal",
              "wise",
              "bank",
              "other",
              null
            ],
            "description": "The payout method type. Never the details."
          },
          "share_email": {
            "type": "boolean"
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Only when the partner chose to share it."
          },
          "terms_version_accepted": {
            "type": [
              "integer",
              "null"
            ]
          },
          "applied_at": {
            "type": "string",
            "format": "date-time"
          },
          "approved_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "stats": {
            "$ref": "#/components/schemas/PartnerStats"
          },
          "group": {
            "type": [
              "object",
              "null"
            ],
            "description": "The group or level the partner is in, or null.",
            "required": [
              "id",
              "name",
              "level"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "level": {
                "type": "boolean",
                "description": "True for a level (a group with a rank)."
              }
            }
          },
          "group_pinned": {
            "type": "boolean",
            "description": "True when the daily level job leaves this partner where you put them."
          },
          "application": {
            "$ref": "#/components/schemas/PartnerApplication"
          },
          "disclosure_acknowledged_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When they last acknowledged your disclosure reminder (shown when they copy a link, then monthly)."
          },
          "muted_broadcasts": {
            "type": "boolean",
            "description": "Whether they muted your broadcast emails (one click in any of them). They still see your messages on their program page."
          }
        }
      },
      "PartnerList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Partner"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "PartnerRef": {
        "type": [
          "object",
          "null"
        ],
        "required": [
          "id",
          "display_name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "display_name": {
            "type": "string"
          }
        }
      },
      "Commission": {
        "type": "object",
        "required": [
          "id",
          "status",
          "amount_minor",
          "gross_minor",
          "reversed_minor",
          "currency",
          "hold_until",
          "approved_at",
          "reversed_at",
          "created_at",
          "reason",
          "adjustment",
          "under_review",
          "partner",
          "conversion",
          "basis_text",
          "capped_minor",
          "bonus"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "$ref": "#/components/schemas/CommissionStatus"
          },
          "amount_minor": {
            "type": "integer",
            "description": "Net of reversals. Negative for a downward adjustment."
          },
          "gross_minor": {
            "type": "integer"
          },
          "reversed_minor": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "hold_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "approved_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "reversed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "adjustment": {
            "type": "boolean"
          },
          "under_review": {
            "type": "boolean"
          },
          "risk": {
            "type": [
              "object",
              "null"
            ],
            "description": "Why the fraud engine held this commission longer (`hold`) or sent it to a person\n(`review`): the score and every reason as a sentence, from counts and times. Null when\nit held nothing. See [Fraud and risk](/docs/referral-fraud).\n",
            "required": [
              "score",
              "action",
              "reasons"
            ],
            "properties": {
              "score": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100
              },
              "action": {
                "type": "string",
                "enum": [
                  "hold",
                  "review"
                ]
              },
              "reasons": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "code",
                    "sentence"
                  ],
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "sentence": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "partner": {
            "$ref": "#/components/schemas/PartnerRef"
          },
          "conversion": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "id",
              "event",
              "amount_minor",
              "currency",
              "occurred_at",
              "source",
              "customer_external_id"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "event": {
                "$ref": "#/components/schemas/ConversionEvent"
              },
              "amount_minor": {
                "type": "integer"
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "occurred_at": {
                "type": "string",
                "format": "date-time"
              },
              "source": {
                "type": "string",
                "enum": [
                  "stripe",
                  "api",
                  "manual",
                  "paddle",
                  "polar",
                  "whop",
                  "revenuecat",
                  "woocommerce",
                  "fastspring",
                  "lemonsqueezy",
                  "shopify",
                  "chargebee",
                  "recurly",
                  "gumroad"
                ],
                "description": "Where the conversion came from. Each billing system Bolt Claw connects to is its own source."
              },
              "customer_external_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Your own id for the customer."
              }
            }
          },
          "basis_text": {
            "type": "string",
            "description": "Why it is this amount: \"30% of 49.00 on Yearly plans (Gold level), capped at 120.00 per\ncustomer\". `Program rate` for a commission recorded before reward rules had reasons.\n"
          },
          "capped_minor": {
            "type": "integer",
            "description": "What the program's monthly ceiling per partner held back from this commission."
          },
          "bonus": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "milestone",
              "manual",
              null
            ],
            "description": "`milestone` for a bonus a partner earned by crossing a line, `manual` for one you paid\nby hand (which has no `conversion`), null for anything else.\n"
          },
          "test": {
            "type": "boolean",
            "description": "From TEST data, earned by the program's test partner. Never payable."
          },
          "tier": {
            "type": "integer",
            "enum": [
              1,
              2
            ],
            "description": "`2` for a recruiter's share of what a partner they recruited earned (the second tier); `1` for everything else."
          }
        }
      },
      "CommissionList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Commission"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "CommissionAdjust": {
        "type": "object",
        "required": [
          "amount_minor",
          "reason"
        ],
        "additionalProperties": false,
        "properties": {
          "amount_minor": {
            "type": "integer",
            "description": "Signed minor units; not 0."
          },
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500,
            "description": "Trimmed; recorded in the activity log."
          }
        }
      },
      "RewardConditions": {
        "type": "object",
        "additionalProperties": false,
        "description": "When a rule applies. Every key you send must match; a rule with none is its audience's\ndefault for its event. A sale without `items` matches no rule that names `products`,\n`prices`, `plans` or `intervals`. A signup takes none; a bonus takes exactly `milestone`.\n",
        "properties": {
          "products": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "type": "string",
              "maxLength": 200
            }
          },
          "prices": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "type": "string",
              "maxLength": 200
            }
          },
          "plans": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Matched without regard to case."
          },
          "intervals": {
            "type": "array",
            "maxItems": 3,
            "items": {
              "type": "string",
              "enum": [
                "month",
                "year",
                "one_time"
              ]
            }
          },
          "purchase": {
            "type": "string",
            "enum": [
              "first",
              "renewal",
              "any"
            ],
            "description": "The customer's first payment or a renewal. `any` is the same as leaving it out."
          },
          "currencies": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "type": "string",
              "maxLength": 8
            }
          },
          "min_amount_minor": {
            "type": "integer",
            "minimum": 0,
            "description": "Only sales of at least this."
          },
          "milestone": {
            "$ref": "#/components/schemas/Milestone"
          }
        }
      },
      "Milestone": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "metric",
          "window"
        ],
        "description": "A bonus's line: `revenue` in `amount_minor` (the program's currency), or `sales` or\n`customers` in `count`, over the partner's `lifetime` or each `calendar_month` (UTC). Paid\nonce per window, at the sale that crosses it. While it is on hold, a refund or dispute\nthat takes the partner back under the line takes it back, and crossing again pays it\nagain; once approved, it stays.\n",
        "properties": {
          "metric": {
            "type": "string",
            "enum": [
              "revenue",
              "sales",
              "customers"
            ]
          },
          "amount_minor": {
            "type": "integer",
            "minimum": 1
          },
          "count": {
            "type": "integer",
            "minimum": 1
          },
          "window": {
            "type": "string",
            "enum": [
              "lifetime",
              "calendar_month"
            ]
          }
        }
      },
      "Reward": {
        "type": "object",
        "required": [
          "id",
          "event",
          "kind",
          "percent_bps",
          "amount_minor",
          "duration",
          "months",
          "updated_at",
          "scope",
          "group_id",
          "partner_id",
          "label",
          "conditions",
          "cap_minor",
          "priority",
          "created_at",
          "summary"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "event": {
            "type": "string",
            "enum": [
              "lead",
              "sale",
              "bonus"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "percent",
              "flat"
            ]
          },
          "percent_bps": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Basis points: 2000 is 20%."
          },
          "amount_minor": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Minor units of the program's currency."
          },
          "duration": {
            "type": "string",
            "enum": [
              "once",
              "months",
              "lifetime"
            ]
          },
          "months": {
            "type": [
              "integer",
              "null"
            ]
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "scope": {
            "type": "string",
            "enum": [
              "program",
              "group",
              "partner"
            ],
            "description": "Who it pays. The most specific scope with a matching rule wins."
          },
          "group_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "partner_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "label": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your own words for when it applies."
          },
          "conditions": {
            "$ref": "#/components/schemas/RewardConditions"
          },
          "cap_minor": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The most it pays for one customer",
            "in total.": null
          },
          "priority": {
            "type": "integer",
            "description": "Breaks a tie between two equally specific rules; higher wins."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "summary": {
            "type": "string",
            "description": "The rule in one sentence."
          },
          "reward_type": {
            "$ref": "#/components/schemas/RewardType"
          },
          "beneficiary": {
            "type": "string",
            "enum": [
              "promoter",
              "referee"
            ],
            "description": "Who it rewards. `referee` is the friend who arrived through the link."
          },
          "period_count": {
            "type": [
              "integer",
              "null"
            ],
            "description": "A free period's length."
          },
          "period_unit": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "day",
              "month",
              null
            ]
          },
          "perk_text": {
            "type": [
              "string",
              "null"
            ],
            "description": "A perk",
            "in your own words.": null
          },
          "discount_duration": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "once",
              "months",
              "forever",
              null
            ],
            "description": "How long a friend's discount lasts."
          },
          "discount_months": {
            "type": [
              "integer",
              "null"
            ]
          },
          "stripe_coupon_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The coupon Bolt Claw made for this rule in your own Stripe, when it made one."
          }
        }
      },
      "RewardType": {
        "type": "string",
        "description": "What a rule gives. `cash` is a commission, paid out; the rest are grants, given in your own\nsystem or through your Stripe. A friend (`referee`) gets a `discount`, credit, a free period,\na perk or a gift card, never cash; a `discount` is only ever the friend's, at a sale, once\nper customer. A bonus is cash.\n",
        "enum": [
          "cash",
          "credit",
          "free_period",
          "discount",
          "perk",
          "gift_card"
        ]
      },
      "RewardList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "maxItems": 201,
            "description": "Every rule of the program, at most 200.",
            "items": {
              "$ref": "#/components/schemas/Reward"
            }
          }
        }
      },
      "RewardCreate": {
        "type": "object",
        "required": [
          "event"
        ],
        "description": "`kind` (`percent` or `flat`) is required for cash, credit, a gift card and a discount; a\nfree period and a perk have no amount and take none.\n",
        "additionalProperties": false,
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "lead",
              "sale",
              "bonus"
            ]
          },
          "scope": {
            "type": "string",
            "enum": [
              "program",
              "group",
              "partner"
            ],
            "description": "`program` (everyone) unless you say otherwise."
          },
          "group_id": {
            "type": "string",
            "format": "uuid",
            "description": "With `scope` `group`."
          },
          "partner_id": {
            "type": "string",
            "format": "uuid",
            "description": "With `scope` `partner`: one partner's own rate."
          },
          "kind": {
            "type": "string",
            "enum": [
              "percent",
              "flat"
            ]
          },
          "percent_bps": {
            "type": [
              "integer",
              "null"
            ],
            "description": "1 to 10000, for `percent`."
          },
          "amount_minor": {
            "type": [
              "integer",
              "null"
            ],
            "description": "For `flat`; a bonus's must be above 0."
          },
          "duration": {
            "type": "string",
            "enum": [
              "once",
              "months",
              "lifetime"
            ],
            "description": "Ignored for a bonus",
            "which is paid once per window.": null
          },
          "months": {
            "type": [
              "integer",
              "null"
            ],
            "description": "1 to 120, for `months`."
          },
          "cap_minor": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The most it pays for one customer; null for no ceiling."
          },
          "conditions": {
            "$ref": "#/components/schemas/RewardConditions"
          },
          "priority": {
            "type": "integer",
            "description": "-1000 to 1000; 0 by default."
          },
          "label": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "At most 60 characters after trimming."
          },
          "reward_type": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RewardType"
              }
            ],
            "description": "`cash` when left out. Fixed once the rule exists."
          },
          "beneficiary": {
            "type": "string",
            "enum": [
              "promoter",
              "referee"
            ],
            "description": "`promoter` when left out. Fixed once the rule exists."
          },
          "period_count": {
            "type": [
              "integer",
              "null"
            ],
            "description": "For `free_period`: 1 to 365 days, or 1 to 36 months."
          },
          "period_unit": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "day",
              "month",
              null
            ],
            "description": "For `free_period`."
          },
          "perk_text": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 400,
            "description": "For `perk`: at most 120 characters after trimming."
          },
          "discount_duration": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "once",
              "months",
              "forever",
              null
            ],
            "description": "For `discount`: how long it lasts."
          },
          "discount_months": {
            "type": [
              "integer",
              "null"
            ],
            "description": "For a `discount` of `months`: 1 to 36."
          }
        }
      },
      "RewardUpdate": {
        "type": "object",
        "additionalProperties": false,
        "description": "Every field optional. A rule's event and audience are fixed once it exists, and so are what\nit gives and to whom (`reward_type`, `beneficiary`): sending either is `400`.\n",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "percent",
              "flat"
            ]
          },
          "percent_bps": {
            "type": [
              "integer",
              "null"
            ]
          },
          "amount_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "duration": {
            "type": "string",
            "enum": [
              "once",
              "months",
              "lifetime"
            ]
          },
          "months": {
            "type": [
              "integer",
              "null"
            ]
          },
          "cap_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "conditions": {
            "$ref": "#/components/schemas/RewardConditions"
          },
          "priority": {
            "type": "integer"
          },
          "label": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "period_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "period_unit": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "day",
              "month",
              null
            ]
          },
          "perk_text": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 400
          },
          "discount_duration": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "once",
              "months",
              "forever",
              null
            ]
          },
          "discount_months": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "LevelMinimum": {
        "type": [
          "object",
          "null"
        ],
        "additionalProperties": false,
        "required": [
          "metric"
        ],
        "description": "What a partner needs to reach a level: `revenue` in `amount_minor`, or `sales` or\n`customers` in `count`, over the last `window_days` (30 to 365), or all time when null.\nCounted net of refunds. Null: everyone qualifies.\n",
        "properties": {
          "metric": {
            "type": "string",
            "enum": [
              "revenue",
              "sales",
              "customers"
            ]
          },
          "amount_minor": {
            "type": "integer",
            "minimum": 0
          },
          "count": {
            "type": "integer",
            "minimum": 0
          },
          "window_days": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "Group": {
        "type": "object",
        "required": [
          "id",
          "name",
          "description",
          "is_default",
          "rank",
          "level",
          "minimum",
          "keep_earned",
          "partners",
          "summary",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_default": {
            "type": "boolean",
            "description": "A newly approved partner starts here."
          },
          "rank": {
            "type": [
              "integer",
              "null"
            ],
            "description": "A level's place on the ladder (1 is the lowest); null for a plain group."
          },
          "level": {
            "type": "boolean"
          },
          "minimum": {
            "$ref": "#/components/schemas/LevelMinimum"
          },
          "keep_earned": {
            "type": "boolean",
            "description": "A partner who reached this level is never moved down by the daily job."
          },
          "partners": {
            "type": "integer",
            "description": "Approved and pending partners in it now."
          },
          "summary": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "GroupList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "maxItems": 51,
            "description": "Every group and level of the program, at most 50, levels first by rank.",
            "items": {
              "$ref": "#/components/schemas/Group"
            }
          }
        }
      },
      "GroupCreate": {
        "type": "object",
        "required": [
          "name"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "description": "1 to 60 characters after trimming; unique in the program."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1000
          },
          "is_default": {
            "type": "boolean"
          },
          "rank": {
            "type": [
              "integer",
              "null"
            ],
            "description": "1 to 100 makes it a level; null for a plain group."
          },
          "minimum": {
            "$ref": "#/components/schemas/LevelMinimum"
          },
          "keep_earned": {
            "type": "boolean"
          }
        }
      },
      "GroupUpdate": {
        "type": "object",
        "additionalProperties": false,
        "description": "Every field optional; a field left out keeps its value.",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1000
          },
          "is_default": {
            "type": "boolean"
          },
          "rank": {
            "type": [
              "integer",
              "null"
            ]
          },
          "minimum": {
            "$ref": "#/components/schemas/LevelMinimum"
          },
          "keep_earned": {
            "type": "boolean"
          }
        }
      },
      "PartnerUpdate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "group_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A group or level of the partner's program; null for none."
          },
          "pinned": {
            "type": "boolean",
            "description": "True keeps the daily level job from moving the partner."
          }
        }
      },
      "BonusCreate": {
        "type": "object",
        "required": [
          "amount_minor",
          "reason"
        ],
        "additionalProperties": false,
        "properties": {
          "amount_minor": {
            "type": "integer",
            "minimum": 1,
            "description": "Minor units of the program's currency."
          },
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 280,
            "description": "The partner sees it as the line's description. Recorded in the activity log."
          },
          "hold_days": {
            "type": "integer",
            "minimum": 0,
            "maximum": 365,
            "description": "Days before it may be paid. 0 (the default) makes it payable at once."
          }
        }
      },
      "ReferralDeleted": {
        "type": "object",
        "required": [
          "deleted"
        ],
        "properties": {
          "deleted": {
            "type": "boolean",
            "const": true
          }
        }
      },
      "ReferrerStatus": {
        "type": "string",
        "enum": [
          "approved",
          "pending",
          "banned"
        ],
        "description": "`approved` earns rewards. `pending` is waiting for you (when the program does not approve\nnew referrers at once, `embed.approve_referrers`); it can share its link and earns nothing\nyet. `banned` earns nothing; an erased referrer is also `banned`.\n"
      },
      "ReferrerView": {
        "type": "object",
        "description": "One of your own users who holds a referral link, as you see them. Never an email, a\nfingerprint or a Bolt Claw account: a referrer has none.\n",
        "required": [
          "id",
          "program_id",
          "external_id",
          "name",
          "code",
          "link_url",
          "status",
          "erased",
          "created_at",
          "clicks",
          "friends",
          "friends_paid",
          "earned_minor",
          "currency"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The referrer's membership in this program. Use it with `PATCH` and `DELETE /referrals/referrers/{id}`."
          },
          "program_id": {
            "type": "string",
            "format": "uuid"
          },
          "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Your own id for the user."
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 60,
            "description": "The first name or display name you sent. Null once erased."
          },
          "code": {
            "type": [
              "string",
              "null"
            ],
            "description": "The code in the referrer's link."
          },
          "link_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "The link the referrer shares."
          },
          "status": {
            "$ref": "#/components/schemas/ReferrerStatus"
          },
          "erased": {
            "type": "boolean",
            "description": "True for an erased referrer: a row that keeps what it earned, with no name."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "clicks": {
            "type": "integer",
            "description": "Clicks on the link",
            "all time.": null
          },
          "friends": {
            "type": "integer",
            "description": "Friends who signed up through the link."
          },
          "friends_paid": {
            "type": "integer",
            "description": "Friends who have paid."
          },
          "earned_minor": {
            "type": "integer",
            "description": "Rewards earned",
            "all time": null,
            "net of refunds": null,
            "in minor units of `currency`.": null
          },
          "currency": {
            "type": "string",
            "description": "The program's currency."
          }
        }
      },
      "ReferrerList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReferrerView"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "ReferrerUpsert": {
        "type": "object",
        "required": [
          "external_id"
        ],
        "additionalProperties": false,
        "properties": {
          "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Your own id for the user. Printable characters only, no space at either end."
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 60,
            "description": "A first name or display name for the panel's greeting, plain text. `null` clears it."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 400,
            "description": "Only used to recognise the same person again (a fingerprint is kept, never the address). Never returned."
          },
          "code": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40,
            "description": "The vanity code for their link: 3 to 40 lowercase letters and digits, single hyphens between words. Only when the referrer is created."
          }
        }
      },
      "EmbedTokenCreate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "referrer": {
            "type": "object",
            "required": [
              "external_id"
            ],
            "additionalProperties": false,
            "description": "The user the session is for; they are made a referrer if they are not one. Leave it out only for `mode` `preview`.",
            "properties": {
              "external_id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200,
                "description": "Your own id for the user."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 60
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 400,
                "description": "Only used to recognise the same person again; never stored as an address."
              },
              "code": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 40,
                "description": "A vanity code",
                "only when the referrer is created.": null
              }
            }
          },
          "ttl_seconds": {
            "type": "integer",
            "minimum": 60,
            "maximum": 86400,
            "default": 3600,
            "description": "How long the token works, in seconds."
          },
          "mode": {
            "type": "string",
            "enum": [
              "panel",
              "preview"
            ],
            "default": "panel",
            "description": "`panel` for a real referrer; `preview` shows sample data and needs no referrer."
          }
        }
      },
      "EmbedTokenCreated": {
        "type": "object",
        "required": [
          "token",
          "expires_at",
          "referrer"
        ],
        "properties": {
          "token": {
            "type": [
              "string",
              "null"
            ],
            "description": "`bcet_` and 43 characters. Shown once: hand it to the panel and do not store or log it.\nNull in a replay of the same `Idempotency-Key`: mint a new one.\n"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "referrer": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ReferrerView"
              },
              {
                "type": "null"
              }
            ],
            "description": "The referrer the session is for; null for a `preview` session."
          }
        }
      },
      "ReferrerStatusUpdate": {
        "type": "object",
        "required": [
          "status"
        ],
        "additionalProperties": false,
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "approved",
              "banned"
            ]
          }
        }
      },
      "ShareChannel": {
        "type": "string",
        "enum": [
          "native",
          "whatsapp",
          "x",
          "linkedin",
          "facebook",
          "messenger",
          "telegram",
          "sms",
          "email"
        ],
        "description": "A share button. `native` is the device's own share sheet."
      },
      "EmbedCopy": {
        "type": "object",
        "additionalProperties": false,
        "description": "Words you replace in the panel. Plain text only, never HTML. Any key left out keeps the\npanel's own English. Each value is 1 to the number of characters shown beside it.\n",
        "properties": {
          "headline": {
            "type": "string",
            "maxLength": 120
          },
          "subline": {
            "type": "string",
            "maxLength": 240
          },
          "step_1": {
            "type": "string",
            "maxLength": 120
          },
          "step_2": {
            "type": "string",
            "maxLength": 120
          },
          "step_3": {
            "type": "string",
            "maxLength": 120
          },
          "copy_button": {
            "type": "string",
            "maxLength": 30
          },
          "copied_label": {
            "type": "string",
            "maxLength": 30
          },
          "share_button": {
            "type": "string",
            "maxLength": 30
          },
          "email_button": {
            "type": "string",
            "maxLength": 30
          },
          "email_title": {
            "type": "string",
            "maxLength": 60
          },
          "terms_label": {
            "type": "string",
            "maxLength": 40
          },
          "empty_title": {
            "type": "string",
            "maxLength": 80
          },
          "empty_body": {
            "type": "string",
            "maxLength": 240
          },
          "payout_title": {
            "type": "string",
            "maxLength": 80
          },
          "payout_body": {
            "type": "string",
            "maxLength": 240
          },
          "rules_title": {
            "type": "string",
            "maxLength": 60
          },
          "history_title": {
            "type": "string",
            "maxLength": 60
          },
          "expired_title": {
            "type": "string",
            "maxLength": 80
          },
          "expired_body": {
            "type": "string",
            "maxLength": 240
          }
        }
      },
      "EmbedMessages": {
        "type": "object",
        "additionalProperties": false,
        "description": "The text each share button pre-fills, at most 280 characters, plain text. `{link}` is\nreplaced by the referrer's link (a message without it gets the link on a new line) and\n`{program}` by the program's name.\n",
        "properties": {
          "native": {
            "type": "string",
            "maxLength": 280
          },
          "whatsapp": {
            "type": "string",
            "maxLength": 280
          },
          "x": {
            "type": "string",
            "maxLength": 280
          },
          "linkedin": {
            "type": "string",
            "maxLength": 280
          },
          "facebook": {
            "type": "string",
            "maxLength": 280
          },
          "messenger": {
            "type": "string",
            "maxLength": 280
          },
          "telegram": {
            "type": "string",
            "maxLength": 280
          },
          "sms": {
            "type": "string",
            "maxLength": 280
          },
          "email": {
            "type": "string",
            "maxLength": 280
          }
        }
      },
      "EmbedConfig": {
        "type": "object",
        "additionalProperties": false,
        "description": "The panel's look, copy and sharing. Every key is optional; a key left out keeps its\ndefault, and an unknown key is `400`. Sent whole: a new `embed_config` replaces the old\none. At most 8 KB once written as JSON.\n",
        "properties": {
          "accent": {
            "type": "string",
            "pattern": "^#[0-9a-fA-F]{6}$",
            "description": "The accent colour, like `#2A9D8F`."
          },
          "radius": {
            "type": "integer",
            "minimum": 0,
            "maximum": 24,
            "description": "Corner radius in pixels."
          },
          "theme": {
            "type": "string",
            "enum": [
              "light",
              "dark",
              "auto"
            ],
            "description": "`auto` follows the visitor's device."
          },
          "link_mode": {
            "type": "string",
            "enum": [
              "boltclaw",
              "direct"
            ],
            "description": "`boltclaw` (default): the referrer's link goes through the program's own `/r/{code}`\nredirect, which counts clicks. `direct`: your own site with `?via={code}`, which names\nno other domain; clicks are then counted only when your server reports them.\n"
          },
          "invite_from_own_system": {
            "type": "boolean",
            "description": "Shows a \"send from my own system\" field. The panel hands the address a referrer types\nto your page (the `inviteRequested` event) and nothing else: Bolt Claw never sees or\nemails it.\n"
          },
          "channels": {
            "type": "array",
            "maxItems": 9,
            "items": {
              "$ref": "#/components/schemas/ShareChannel"
            },
            "description": "The share buttons to show, in order. Left out, all nine."
          },
          "copy": {
            "$ref": "#/components/schemas/EmbedCopy"
          },
          "messages": {
            "$ref": "#/components/schemas/EmbedMessages"
          },
          "email_subject": {
            "type": "string",
            "maxLength": 120
          },
          "email_body": {
            "type": "string",
            "maxLength": 1000
          }
        }
      },
      "EmbedSettings": {
        "type": "object",
        "required": [
          "enabled",
          "key",
          "panel_url",
          "origins",
          "generation",
          "approve_referrers",
          "config"
        ],
        "description": "A program's in-app referral panel. Change it with `embed_enabled`, `embed_origins`, `embed_config` and `approve_referrers` on `PATCH /referrals/programs/{id}`.",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "Whether sessions can be minted and the panel loads. Off, both fail."
          },
          "key": {
            "type": "string",
            "description": "The public embed key your page passes as `embedKey`. Not a secret; rotate it with `POST /referrals/programs/{id}/embed/rotate-key`."
          },
          "panel_url": {
            "type": "string",
            "description": "The address the panel's iframe loads."
          },
          "origins": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string"
            },
            "description": "The sites allowed to frame the panel, like `https://app.example.com`. https only\n(`http://localhost:3000`, with a port, is allowed for development), no\nwildcard, no path. Any other site that tries to frame it is refused by the browser.\n"
          },
          "generation": {
            "type": "integer",
            "minimum": 1,
            "description": "Goes up by one each time you end every live session."
          },
          "approve_referrers": {
            "type": "boolean",
            "description": "True (default): a new referrer is `approved` at once. False: `pending` until you approve."
          },
          "config": {
            "$ref": "#/components/schemas/EmbedConfig"
          }
        }
      },
      "PanelNoticeCode": {
        "type": "string",
        "enum": [
          "program_paused",
          "referrer_not_approved"
        ]
      },
      "ReasonCode": {
        "type": "string",
        "description": "The stable code beside each history sentence. Translate by code; the English `reason` is a\nconvenience. A code is never renamed or reused, and a new one only ever joins the end.\n\n`awaiting_payment`: the friend joined; nothing paid yet. `on_hold_until`: the friend paid;\nthe reward waits out the program's hold, until `hold_until`, in case of a refund.\n`reward_ready`: the hold is over; it goes out in the next payout. `reward_paid`: it has\nbeen paid. `under_review`: the program is checking it first. `refunded`: the friend's\npayment was refunded, so no reward. `no_reward`: the program pays nothing for this event.\n`referrer_not_approved`: the referrer is not approved, so nothing is earned.\n",
        "enum": [
          "awaiting_payment",
          "on_hold_until",
          "reward_ready",
          "reward_paid",
          "under_review",
          "refunded",
          "no_reward",
          "referrer_not_approved"
        ]
      },
      "HistoryStage": {
        "type": "string",
        "enum": [
          "joined",
          "paid",
          "rewarded",
          "not_counted"
        ],
        "description": "Where the friend is: `joined`, `paid`, `rewarded` (the reward is paid out) or `not_counted`."
      },
      "PanelHistoryRow": {
        "type": "object",
        "required": [
          "id",
          "label",
          "stage",
          "reason_code",
          "reason",
          "occurred_at",
          "hold_until",
          "reward"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable for a friend, like `f3`. Not an id of ours or yours."
          },
          "label": {
            "type": "string",
            "description": "`Friend 3`: friends are numbered in the order they joined, never named."
          },
          "stage": {
            "$ref": "#/components/schemas/HistoryStage"
          },
          "reason_code": {
            "$ref": "#/components/schemas/ReasonCode"
          },
          "reason": {
            "type": "string",
            "description": "One English sentence saying why the row is where it is. Translate by `reason_code`."
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "hold_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the reward is ready, for `on_hold_until`; otherwise null."
          },
          "reward": {
            "type": [
              "object",
              "null"
            ],
            "description": "The reward this friend has earned so far, net of refunds; null when there is none.",
            "required": [
              "amount_minor",
              "currency"
            ],
            "properties": {
              "amount_minor": {
                "type": "integer"
              },
              "currency": {
                "type": "string"
              }
            }
          }
        }
      },
      "PayoutMethodForReferrer": {
        "type": "string",
        "enum": [
          "paypal",
          "wise"
        ]
      },
      "ReferrerPanelView": {
        "type": "object",
        "description": "Everything the panel draws for one referrer, as data: the same View behind the hosted panel,\nthe browser client and `GET /referrals/programs/{id}/referrers/{external_id}`, so your own\nscreen can show exactly what the panel shows. Keys are snake_case; values are plain text.\n",
        "required": [
          "mode",
          "generated_at",
          "referrer",
          "program",
          "notice",
          "offer",
          "stats",
          "history",
          "payout",
          "share",
          "look",
          "copy",
          "grants",
          "ask",
          "thank_you"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "panel",
              "preview"
            ],
            "description": "`preview` is sample data."
          },
          "generated_at": {
            "type": "string",
            "format": "date-time"
          },
          "referrer": {
            "type": "object",
            "required": [
              "external_id",
              "name",
              "status",
              "code",
              "link_url"
            ],
            "properties": {
              "external_id": {
                "type": "string"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "status": {
                "$ref": "#/components/schemas/ReferrerStatus"
              },
              "code": {
                "type": "string"
              },
              "link_url": {
                "type": "string",
                "description": "The link to share."
              }
            }
          },
          "program": {
            "type": "object",
            "required": [
              "name",
              "currency",
              "status"
            ],
            "properties": {
              "name": {
                "type": "string"
              },
              "currency": {
                "type": "string"
              },
              "status": {
                "type": "string",
                "enum": [
                  "active",
                  "paused"
                ]
              }
            }
          },
          "notice": {
            "type": [
              "object",
              "null"
            ],
            "description": "A line above everything when something stops all rewards (a paused program, an unapproved referrer); otherwise null.",
            "required": [
              "code",
              "text"
            ],
            "properties": {
              "code": {
                "$ref": "#/components/schemas/PanelNoticeCode"
              },
              "text": {
                "type": "string"
              }
            }
          },
          "offer": {
            "type": "object",
            "required": [
              "headline",
              "subline",
              "reward_text",
              "friend_text",
              "steps",
              "rules",
              "faq",
              "terms"
            ],
            "properties": {
              "headline": {
                "type": "string"
              },
              "subline": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "reward_text": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "What the referrer earns, in one phrase from the rules in force."
              },
              "friend_text": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The friend's side as you wrote it (`customer_discount`)."
              },
              "steps": {
                "type": "array",
                "minItems": 3,
                "maxItems": 3,
                "items": {
                  "type": "string"
                }
              },
              "rules": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "kind",
                    "text"
                  ],
                  "properties": {
                    "kind": {
                      "type": "string",
                      "enum": [
                        "counts",
                        "doesnt_count",
                        "timing"
                      ]
                    },
                    "text": {
                      "type": "string"
                    }
                  }
                }
              },
              "faq": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "question",
                    "answer"
                  ],
                  "properties": {
                    "question": {
                      "type": "string"
                    },
                    "answer": {
                      "type": "string"
                    }
                  }
                }
              },
              "terms": {
                "type": [
                  "object",
                  "null"
                ],
                "required": [
                  "text",
                  "version"
                ],
                "properties": {
                  "text": {
                    "type": "string"
                  },
                  "version": {
                    "type": "integer"
                  }
                }
              }
            }
          },
          "stats": {
            "type": "object",
            "required": [
              "clicks",
              "friends_joined",
              "friends_paid",
              "rewards"
            ],
            "properties": {
              "clicks": {
                "type": "integer"
              },
              "friends_joined": {
                "type": "integer"
              },
              "friends_paid": {
                "type": "integer"
              },
              "rewards": {
                "type": "array",
                "description": "One entry per currency.",
                "items": {
                  "type": "object",
                  "required": [
                    "currency",
                    "waiting_minor",
                    "ready_minor",
                    "paid_minor"
                  ],
                  "properties": {
                    "currency": {
                      "type": "string"
                    },
                    "waiting_minor": {
                      "type": "integer",
                      "description": "Earned and still in the hold."
                    },
                    "ready_minor": {
                      "type": "integer",
                      "description": "Past the hold",
                      "goes out in the next payout.": null
                    },
                    "paid_minor": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "history": {
            "type": "array",
            "maxItems": 50,
            "description": "One row per friend, newest first, at most 50.",
            "items": {
              "$ref": "#/components/schemas/PanelHistoryRow"
            }
          },
          "payout": {
            "type": "object",
            "required": [
              "needed",
              "set",
              "method",
              "methods"
            ],
            "properties": {
              "needed": {
                "type": "boolean",
                "description": "True when there is a reward to receive and no payout details are saved."
              },
              "set": {
                "type": "boolean"
              },
              "method": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/PayoutMethodForReferrer"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "methods": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PayoutMethodForReferrer"
                }
              }
            }
          },
          "share": {
            "type": "object",
            "required": [
              "mode",
              "channels",
              "messages",
              "email_subject",
              "email_body",
              "invite_from_own_system"
            ],
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "boltclaw",
                  "direct"
                ]
              },
              "channels": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ShareChannel"
                }
              },
              "messages": {
                "$ref": "#/components/schemas/EmbedMessages"
              },
              "email_subject": {
                "type": "string"
              },
              "email_body": {
                "type": "string"
              },
              "invite_from_own_system": {
                "type": "boolean"
              }
            }
          },
          "look": {
            "type": "object",
            "required": [
              "accent",
              "radius",
              "theme"
            ],
            "properties": {
              "accent": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "radius": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "theme": {
                "type": "string",
                "enum": [
                  "light",
                  "dark",
                  "auto"
                ]
              }
            }
          },
          "copy": {
            "$ref": "#/components/schemas/EmbedCopy"
          },
          "grants": {
            "type": "array",
            "maxItems": 50,
            "description": "The referrer's own rewards that are not cash (\"1 month free\"), newest first, at most 50,\neach with where it stands in a sentence. Ids are this View's own (`g1`).\n",
            "items": {
              "type": "object",
              "required": [
                "id",
                "text",
                "status",
                "status_text",
                "occurred_at"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "text": {
                  "type": "string"
                },
                "status": {
                  "$ref": "#/components/schemas/GrantStatus"
                },
                "status_text": {
                  "type": "string"
                },
                "occurred_at": {
                  "type": "string"
                }
              }
            }
          },
          "ask": {
            "type": "object",
            "description": "Whether to ask the referrer to share now, or they said \"Not right now\" until `snoozed_until`.",
            "required": [
              "should_ask",
              "snoozed_until"
            ],
            "properties": {
              "should_ask": {
                "type": "boolean"
              },
              "snoozed_until": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "thank_you": {
            "type": "object",
            "description": "The words of the compact thank-you card, for a panel opened with `mode=\"thank-you\"`.",
            "required": [
              "headline",
              "after_share"
            ],
            "properties": {
              "headline": {
                "type": "string"
              },
              "after_share": {
                "type": "string"
              }
            }
          }
        }
      },
      "GrantStatus": {
        "type": "string",
        "description": "`pending` waits out the program's hold; `ready` is yours to give; `fulfilled` was given;\n`failed` could not be given yet; `cancelled` by a refund or dispute before it was given;\n`reversed` by one after.\n",
        "enum": [
          "pending",
          "ready",
          "fulfilled",
          "failed",
          "cancelled",
          "reversed"
        ]
      },
      "GrantType": {
        "type": "string",
        "enum": [
          "credit",
          "free_period",
          "discount",
          "perk",
          "gift_card"
        ]
      },
      "Grant": {
        "type": "object",
        "required": [
          "id",
          "program_id",
          "beneficiary",
          "reward_type",
          "value",
          "summary",
          "status",
          "status_text",
          "hold_until",
          "ready_at",
          "fulfilled_at",
          "ended_at",
          "fulfilment",
          "reference",
          "reason",
          "recipient",
          "partner",
          "customer_external_id",
          "conversion",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "program_id": {
            "type": "string",
            "format": "uuid"
          },
          "beneficiary": {
            "type": "string",
            "enum": [
              "promoter",
              "referee"
            ]
          },
          "reward_type": {
            "$ref": "#/components/schemas/GrantType"
          },
          "value": {
            "type": "object",
            "required": [
              "amount_minor",
              "currency",
              "percent_bps",
              "period_count",
              "period_unit",
              "perk_text",
              "discount_duration",
              "discount_months"
            ],
            "properties": {
              "amount_minor": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Credit, a gift card, or an amount off: minor units of `currency`."
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "percent_bps": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "A percentage off, in basis points."
              },
              "period_count": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "period_unit": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "day",
                  "month",
                  null
                ]
              },
              "perk_text": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "discount_duration": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "once",
                  "months",
                  "forever",
                  null
                ]
              },
              "discount_months": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            }
          },
          "summary": {
            "type": "string",
            "description": "What it is, in a few words: \"1 month free\", \"10.00 USD in credit\"."
          },
          "status": {
            "$ref": "#/components/schemas/GrantStatus"
          },
          "status_text": {
            "type": "string",
            "description": "Where it stands",
            "in a sentence.": null
          },
          "hold_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ready_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "fulfilled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ended_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When it was cancelled or reversed."
          },
          "fulfilment": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "merchant",
              "stripe",
              "tremendous",
              null
            ],
            "description": "How it was or will be given; `merchant` is your own system."
          },
          "reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your (or Stripe's) reference for it",
            "once given.": null
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why it was cancelled",
            "could not be given": null,
            "or is waiting for you.": null
          },
          "recipient": {
            "type": "object",
            "description": "Who receives it, as you know them: one of your own users with a link (`referrer`, by your\n`external_id`), an affiliate (`partner`, by their name in the program), or the friend\n(`customer`, by your own id for them).\n",
            "required": [
              "kind",
              "external_id",
              "display_name"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "referrer",
                  "partner",
                  "customer"
                ]
              },
              "external_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "display_name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "partner": {
            "type": [
              "object",
              "null"
            ],
            "description": "The promoter the sale was credited to.",
            "required": [
              "id",
              "display_name"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "display_name": {
                "type": "string"
              }
            }
          },
          "customer_external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The friend",
            "by your own id for them.": null
          },
          "conversion": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "id",
              "event",
              "occurred_at"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "event": {
                "$ref": "#/components/schemas/ConversionEvent"
              },
              "occurred_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "test": {
            "type": "boolean",
            "description": "From TEST data. Never given through Stripe, never counted."
          }
        }
      },
      "GrantList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Grant"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "GrantFulfil": {
        "type": "object",
        "required": [
          "reference"
        ],
        "additionalProperties": false,
        "properties": {
          "reference": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Your reference for the reward you gave, like an order or credit note number. The same one again is safe."
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "A note for your own team."
          }
        }
      },
      "GrantFail": {
        "type": "object",
        "required": [
          "reason"
        ],
        "additionalProperties": false,
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500,
            "description": "Why it cannot be given. Shown to you",
            "not to the person.": null
          }
        }
      },
      "Offer": {
        "type": "object",
        "description": "The friend's offer in the words the welcome shows. It names nobody the person did not\nagree to show, and carries no id of ours.\n",
        "required": [
          "inviter",
          "headline",
          "gift",
          "conditions",
          "disclosure",
          "discount",
          "stripe",
          "cta",
          "look"
        ],
        "properties": {
          "inviter": {
            "type": [
              "string",
              "null"
            ],
            "description": "The inviter's name",
            "only when they agreed to it being shown.": null
          },
          "headline": {
            "type": "string"
          },
          "gift": {
            "type": "string",
            "description": "The gift in a few words: \"20% off for 3 months\"."
          },
          "conditions": {
            "type": "string"
          },
          "disclosure": {
            "type": [
              "string",
              "null"
            ],
            "description": "\"Jane gets a reward if you buy\": present whenever the program rewards the person who\ninvited them, and not removable while it does.\n"
          },
          "discount": {
            "type": "object",
            "required": [
              "kind",
              "value",
              "currency",
              "unit",
              "duration",
              "months",
              "text",
              "code"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "percent",
                  "amount",
                  "free_period",
                  "credit",
                  "perk",
                  "gift_card"
                ]
              },
              "value": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Basis points for `percent`; minor units for `amount`, `credit` and `gift_card`; a count for `free_period`."
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "unit": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "day",
                  "month",
                  null
                ]
              },
              "duration": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "once",
                  "months",
                  "forever",
                  null
                ]
              },
              "months": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "text": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "A perk's own words."
              },
              "code": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The referrer's discount code, if they have one."
              }
            }
          },
          "stripe": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "promotion_code"
            ],
            "properties": {
              "promotion_code": {
                "type": "string",
                "description": "The referrer's promotion code in your Stripe (`promo_…`)."
              }
            }
          },
          "cta": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "label",
              "url"
            ],
            "properties": {
              "label": {
                "type": "string"
              },
              "url": {
                "type": "string"
              }
            }
          },
          "look": {
            "type": "object",
            "required": [
              "style",
              "position",
              "accent",
              "radius",
              "theme"
            ],
            "properties": {
              "style": {
                "type": "string",
                "enum": [
                  "bar",
                  "corner",
                  "sheet"
                ]
              },
              "position": {
                "type": "string",
                "enum": [
                  "top",
                  "bottom"
                ]
              },
              "accent": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "radius": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "theme": {
                "type": "string",
                "enum": [
                  "light",
                  "dark",
                  "auto"
                ]
              }
            }
          }
        }
      },
      "OfferReasonCode": {
        "type": "string",
        "description": "Why a customer may not have the offer: `unknown_code` (no live link of yours),\n`program_paused`, `self_referral` (the customer is the referrer), `already_referred`\n(another referrer has them), `not_new`.\n",
        "enum": [
          "unknown_code",
          "program_paused",
          "self_referral",
          "already_referred",
          "not_new"
        ]
      },
      "OfferAnswer": {
        "type": "object",
        "required": [
          "offer",
          "eligible",
          "reason_code",
          "reason",
          "coupon"
        ],
        "properties": {
          "offer": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Offer"
              },
              {
                "type": "null"
              }
            ]
          },
          "eligible": {
            "type": "boolean"
          },
          "reason_code": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/OfferReasonCode"
              },
              {
                "type": "null"
              }
            ]
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "One sentence to show at checkout instead of an error."
          },
          "coupon": {
            "type": [
              "string",
              "null"
            ],
            "description": "The friend's coupon in your Stripe, for `discounts: [{ coupon }]`."
          }
        }
      },
      "CustomerReferral": {
        "type": "object",
        "required": [
          "invited_by",
          "invited_at",
          "via"
        ],
        "properties": {
          "invited_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "The inviter's name",
            "only when they agreed to it being shown.": null
          },
          "invited_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "via": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "link",
              "discount_code",
              null
            ]
          }
        }
      },
      "DiscountCode": {
        "type": "object",
        "required": [
          "id",
          "code",
          "active",
          "created_at",
          "partner",
          "stripe_promotion_code",
          "shopify_discount",
          "shopify_error"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string",
            "description": "3 to 40 letters",
            "digits and hyphens. Unique in the program": null,
            "ignoring case.": null
          },
          "active": {
            "type": "boolean",
            "description": "False once the code was switched off."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "partner": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "id",
              "display_name"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "display_name": {
                "type": "string"
              }
            }
          },
          "stripe_promotion_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "The promotion code this code is in your own Stripe."
          },
          "shopify_discount": {
            "type": [
              "string",
              "null"
            ],
            "description": "The discount this code is in the program's linked Shopify shop."
          },
          "shopify_error": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "no_friend_discount",
              "refused",
              "unavailable",
              "taken",
              null
            ],
            "description": "Why the last Shopify sync could not make it there."
          }
        }
      },
      "FriendDiscount": {
        "type": "object",
        "description": "The discount a friend gets with a partner's code, from the program's friend's discount rule.",
        "required": [
          "kind",
          "percent_bps",
          "amount_minor",
          "currency",
          "discount_duration",
          "discount_months"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "percent",
              "amount"
            ]
          },
          "percent_bps": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Basis points: 1000 is 10%."
          },
          "amount_minor": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Minor units of `currency`."
          },
          "currency": {
            "type": "string"
          },
          "discount_duration": {
            "type": "string",
            "enum": [
              "once",
              "months",
              "forever"
            ]
          },
          "discount_months": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "DiscountCodeList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor",
          "friend_discount"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DiscountCode"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          },
          "friend_discount": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/FriendDiscount"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "InviteCreate": {
        "type": "object",
        "properties": {
          "max_uses": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100000,
            "description": "How many people may join with it. 1 by default."
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Within a year."
          }
        }
      },
      "Invite": {
        "type": "object",
        "required": [
          "id",
          "url",
          "max_uses",
          "used_count",
          "expires_at",
          "created_at",
          "state"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "description": "The link to send. Bolt Claw emails nobody."
          },
          "max_uses": {
            "type": "integer"
          },
          "used_count": {
            "type": "integer"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "state": {
            "type": "string",
            "enum": [
              "active",
              "expired",
              "used"
            ]
          }
        }
      },
      "ShopifyCodesResult": {
        "type": "object",
        "required": [
          "shop",
          "created",
          "matched",
          "deactivated",
          "failed",
          "remaining",
          "reason"
        ],
        "properties": {
          "shop": {
            "type": [
              "string",
              "null"
            ],
            "description": "The linked shop's myshopify.com name."
          },
          "created": {
            "type": "integer",
            "description": "Discounts made in the shop now."
          },
          "matched": {
            "type": "integer",
            "description": "Codes the shop already had with the same text",
            "now known.": null
          },
          "deactivated": {
            "type": "integer",
            "description": "Codes switched off in Bolt Claw",
            "now switched off in the shop.": null
          },
          "failed": {
            "type": "integer",
            "description": "Codes the shop refused or did not answer for. They still work as codes Bolt Claw knows."
          },
          "remaining": {
            "type": "integer",
            "description": "Codes waiting for the next call."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "not_linked",
              "no_friend_discount",
              "reconnect",
              null
            ],
            "description": "Why nothing could be synced: no linked shop, no friend's discount rule, or the shop must open the app again."
          }
        }
      },
      "PrivacyRequest": {
        "type": "object",
        "required": [
          "id",
          "program_id",
          "source",
          "kind",
          "shop",
          "status",
          "result",
          "received_at",
          "answered_at",
          "due_by"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "program_id": {
            "type": "string",
            "format": "uuid"
          },
          "source": {
            "type": "string",
            "enum": [
              "shopify"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "data_request",
              "customer_redact",
              "shop_redact"
            ]
          },
          "shop": {
            "type": "string",
            "description": "The shop's myshopify.com name."
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "answered",
              "done"
            ]
          },
          "result": {
            "type": [
              "object",
              "null"
            ],
            "description": "What an erasure made anonymous, as counts.",
            "required": [
              "customers",
              "conversions",
              "referrers"
            ],
            "properties": {
              "customers": {
                "type": "integer"
              },
              "conversions": {
                "type": "integer"
              },
              "referrers": {
                "type": "integer"
              }
            }
          },
          "received_at": {
            "type": "string",
            "format": "date-time"
          },
          "answered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "due_by": {
            "type": "string",
            "format": "date-time",
            "description": "Thirty days after Shopify asked."
          }
        }
      },
      "PrivacyRequestList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PrivacyRequest"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "DataSubject": {
        "type": "object",
        "description": "Everything Bolt Claw holds about one of your customers or in-app referrers in this workspace.",
        "required": [
          "external_id",
          "customers",
          "referrers"
        ],
        "properties": {
          "external_id": {
            "type": "string",
            "description": "The id you looked up."
          },
          "customers": {
            "type": "array",
            "description": "Each program the person was referred in, as a customer.",
            "items": {
              "type": "object",
              "required": [
                "id",
                "program_id",
                "program_name",
                "livemode",
                "first_seen_at",
                "attributed_at",
                "partner_id",
                "matched_by",
                "has_processor_customer_id",
                "has_email_fingerprint",
                "conversions",
                "grants"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "program_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "program_name": {
                  "type": "string"
                },
                "livemode": {
                  "type": "boolean"
                },
                "first_seen_at": {
                  "type": "string",
                  "format": "date-time"
                },
                "attributed_at": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "partner_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid",
                  "description": "The promoter they were credited to."
                },
                "matched_by": {
                  "type": "string",
                  "enum": [
                    "external_id",
                    "processor_customer_id"
                  ]
                },
                "has_processor_customer_id": {
                  "type": "boolean"
                },
                "has_email_fingerprint": {
                  "type": "boolean",
                  "description": "Whether an email fingerprint is held. Never its value."
                },
                "conversions": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "id",
                      "event",
                      "amount_minor",
                      "currency",
                      "source",
                      "occurred_at"
                    ],
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "event": {
                        "type": "string"
                      },
                      "amount_minor": {
                        "type": "integer"
                      },
                      "currency": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "source": {
                        "type": "string"
                      },
                      "occurred_at": {
                        "type": "string",
                        "format": "date-time"
                      }
                    }
                  }
                },
                "grants": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/DataSubjectGrant"
                  }
                }
              }
            }
          },
          "referrers": {
            "type": "array",
            "description": "Each program the person is an in-app referrer in.",
            "items": {
              "type": "object",
              "required": [
                "id",
                "program_id",
                "program_name",
                "display_name",
                "status",
                "created_at",
                "terms_version_accepted",
                "leaderboard_name",
                "has_email_fingerprint",
                "has_payout_details",
                "links",
                "commissions",
                "grants"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "program_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "program_name": {
                  "type": "string"
                },
                "display_name": {
                  "type": "string"
                },
                "status": {
                  "type": "string"
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time"
                },
                "terms_version_accepted": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "leaderboard_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "has_email_fingerprint": {
                  "type": "boolean"
                },
                "has_payout_details": {
                  "type": "boolean"
                },
                "links": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "The codes of their live links."
                },
                "commissions": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "id",
                      "status",
                      "amount_minor",
                      "currency",
                      "created_at"
                    ],
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "status": {
                        "type": "string"
                      },
                      "amount_minor": {
                        "type": "integer"
                      },
                      "currency": {
                        "type": "string"
                      },
                      "created_at": {
                        "type": "string",
                        "format": "date-time"
                      }
                    }
                  }
                },
                "grants": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/DataSubjectGrant"
                  }
                }
              }
            }
          }
        }
      },
      "DataSubjectGrant": {
        "type": "object",
        "required": [
          "id",
          "reward_type",
          "beneficiary",
          "status",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "reward_type": {
            "type": "string"
          },
          "beneficiary": {
            "type": "string",
            "enum": [
              "promoter",
              "referee"
            ]
          },
          "status": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DataSubjectErased": {
        "type": "object",
        "description": "What was erased, counted. Amounts and commissions stay as anonymous rows.",
        "required": [
          "external_id",
          "customers",
          "conversions",
          "referrers",
          "webhook_payloads"
        ],
        "properties": {
          "external_id": {
            "type": "string"
          },
          "customers": {
            "type": "integer",
            "description": "Customer records turned into anonymous ones."
          },
          "conversions": {
            "type": "integer",
            "description": "Their conversions",
            "kept with their amounts and no id.": null
          },
          "referrers": {
            "type": "integer",
            "description": "In-app referrers erased."
          },
          "webhook_payloads": {
            "type": "integer",
            "description": "Stored webhook bodies whose copy of the id was scrubbed."
          }
        }
      },
      "TermsDraftRequest": {
        "type": "object",
        "required": [
          "answers"
        ],
        "additionalProperties": false,
        "properties": {
          "answers": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "business_name",
              "contact_email",
              "governing_law",
              "courts"
            ],
            "properties": {
              "business_name": {
                "type": "string",
                "maxLength": 200,
                "description": "Your business's legal name",
                "as it signs contracts.": null
              },
              "contact_email": {
                "type": "string",
                "maxLength": 254,
                "description": "Where partners write about the program."
              },
              "who_may_join": {
                "type": "string",
                "enum": [
                  "anyone",
                  "customers",
                  "invited"
                ],
                "default": "anyone"
              },
              "minimum_age": {
                "type": "integer",
                "minimum": 16,
                "maximum": 21,
                "default": 18
              },
              "excluded_countries": {
                "type": "string",
                "maxLength": 300,
                "description": "Countries partners may not join from",
                "in words. Empty for none.": null
              },
              "forfeiture_months": {
                "type": "integer",
                "minimum": 0,
                "maximum": 24,
                "default": 6,
                "description": "Months money held for a missing tax form waits before it may be forfeited; 0 for never."
              },
              "governing_law": {
                "type": "string",
                "maxLength": 200,
                "description": "For example \"the laws of England and Wales\"."
              },
              "courts": {
                "type": "string",
                "maxLength": 200,
                "description": "For example \"the courts of England and Wales\"."
              }
            }
          }
        }
      },
      "TermsDraft": {
        "type": "object",
        "required": [
          "terms",
          "settings",
          "clauses",
          "notice"
        ],
        "properties": {
          "terms": {
            "type": "string",
            "description": "The assembled terms. Edit them as you wish before saving."
          },
          "settings": {
            "type": "object",
            "description": "What was read from the program's own settings, which the terms describe.",
            "additionalProperties": true
          },
          "clauses": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The template's clauses the draft holds",
            "by id.": null
          },
          "notice": {
            "type": "string",
            "description": "Based on Bolt Claw's template. Not legal advice; have it checked for the countries your partners live in."
          }
        }
      },
      "PrivacyRequestDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/PrivacyRequest"
          },
          {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "For a data request still waiting, what Bolt Claw holds about the customer it named.",
                "required": [
                  "note",
                  "customers"
                ],
                "properties": {
                  "note": {
                    "type": "string"
                  },
                  "customers": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "customer_id",
                        "first_seen_at",
                        "referred_by",
                        "has_email_fingerprint",
                        "conversions",
                        "grants",
                        "referrer"
                      ],
                      "properties": {
                        "customer_id": {
                          "type": "string",
                          "description": "Your id for the customer (or the Shopify customer id)."
                        },
                        "first_seen_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "referred_by": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The partner's display name."
                        },
                        "has_email_fingerprint": {
                          "type": "boolean"
                        },
                        "conversions": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "required": [
                              "event",
                              "amount_minor",
                              "currency",
                              "occurred_at",
                              "order"
                            ],
                            "properties": {
                              "event": {
                                "type": "string"
                              },
                              "amount_minor": {
                                "type": "integer"
                              },
                              "currency": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "occurred_at": {
                                "type": "string",
                                "format": "date-time"
                              },
                              "order": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The Shopify order id."
                              }
                            }
                          }
                        },
                        "grants": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "required": [
                              "reward_type",
                              "status",
                              "created_at"
                            ],
                            "properties": {
                              "reward_type": {
                                "type": "string"
                              },
                              "status": {
                                "type": "string"
                              },
                              "created_at": {
                                "type": "string",
                                "format": "date-time"
                              }
                            }
                          }
                        },
                        "referrer": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "required": [
                            "status",
                            "link"
                          ],
                          "properties": {
                            "status": {
                              "type": "string"
                            },
                            "link": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "StripeCodesResult": {
        "type": "object",
        "required": [
          "coupon",
          "created",
          "linked",
          "failed"
        ],
        "properties": {
          "coupon": {
            "type": "string",
            "description": "The coupon behind every code",
            "made from the friend's discount rule.": null
          },
          "created": {
            "type": "integer",
            "description": "Promotion codes made in Stripe now."
          },
          "linked": {
            "type": "integer",
            "description": "Codes Stripe already had with the same text",
            "now linked.": null
          },
          "failed": {
            "type": "integer",
            "description": "Codes Stripe refused. They still work as codes Bolt Claw knows."
          }
        }
      },
      "PayoutStatus": {
        "type": "string",
        "description": "`unclaimed`: the provider holds it until the promoter claims it (PayPal's sign-up link, a\nreward email that bounced). `returned`: it came back before it was paid, and its commissions\nare back in Ready to pay.\n",
        "enum": [
          "draft",
          "processing",
          "unclaimed",
          "paid",
          "failed",
          "returned",
          "cancelled"
        ]
      },
      "PayoutProviderName": {
        "type": "string",
        "description": "How a payout is paid. `manual`: a file you pay from yourself. `webhook`: your own payout\nsystem. The rest: your own account at that provider. BoltClaw never holds the money.\n",
        "enum": [
          "manual",
          "webhook",
          "paypal",
          "wise",
          "stripe_connect",
          "tremendous",
          "trolley",
          "tango"
        ]
      },
      "PayoutFailureCode": {
        "type": "string",
        "enum": [
          "invalid_destination",
          "recipient_not_ready",
          "insufficient_funds",
          "provider_refused",
          "unclaimed_expired",
          "returned",
          "not_funded",
          "cancelled_at_provider",
          "limit_exceeded",
          "delivery_failed",
          "other"
        ]
      },
      "LeftOutReason": {
        "type": [
          "string",
          "null"
        ],
        "enum": [
          "no_details",
          "other_currency",
          "not_paypal",
          "not_wise",
          "wise_currency",
          "wise_usd_outside_us",
          "payout_hold",
          null
        ]
      },
      "Payout": {
        "type": "object",
        "required": [
          "id",
          "batch_id",
          "status",
          "provider",
          "currency",
          "amount_minor",
          "short_reference",
          "reference",
          "paid_at",
          "created_at",
          "updated_at",
          "partner",
          "country",
          "exports",
          "delivery"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "batch_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "status": {
            "$ref": "#/components/schemas/PayoutStatus"
          },
          "provider": {
            "$ref": "#/components/schemas/PayoutProviderName"
          },
          "currency": {
            "type": "string"
          },
          "amount_minor": {
            "type": "integer"
          },
          "short_reference": {
            "type": "string"
          },
          "reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "paid_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "partner": {
            "$ref": "#/components/schemas/PartnerRef"
          },
          "country": {
            "type": [
              "string",
              "null"
            ]
          },
          "exports": {
            "type": [
              "object",
              "null"
            ],
            "description": "Which export file an open payout would go in; null where it would, else why not.",
            "required": [
              "method",
              "generic",
              "paypal",
              "wise"
            ],
            "properties": {
              "method": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "paypal",
                  "wise",
                  "bank",
                  "other",
                  null
                ]
              },
              "generic": {
                "$ref": "#/components/schemas/LeftOutReason"
              },
              "paypal": {
                "$ref": "#/components/schemas/LeftOutReason"
              },
              "wise": {
                "$ref": "#/components/schemas/LeftOutReason"
              }
            }
          },
          "delivery": {
            "$ref": "#/components/schemas/PayoutDelivery"
          }
        }
      },
      "PayoutDelivery": {
        "type": [
          "object",
          "null"
        ],
        "description": "How a payout sent through a provider went; null until it was sent.",
        "required": [
          "sent_at",
          "provider_ref",
          "awaiting_funding_at",
          "unclaimed_at",
          "failure",
          "returned_after_paid_at"
        ],
        "properties": {
          "sent_at": {
            "type": "string",
            "format": "date-time"
          },
          "provider_ref": {
            "type": [
              "string",
              "null"
            ],
            "description": "The provider's own id for it: what to look for in your provider account."
          },
          "awaiting_funding_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Waiting for you to fund it at the provider (Wise, Trolley), since when."
          },
          "unclaimed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "failure": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "code",
              "sentence"
            ],
            "properties": {
              "code": {
                "$ref": "#/components/schemas/PayoutFailureCode"
              },
              "sentence": {
                "type": "string"
              }
            }
          },
          "returned_after_paid_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "It came back AFTER it was paid. Nothing was changed; it is yours to decide."
          }
        }
      },
      "PayoutList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Payout"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "PayoutMarkPaid": {
        "type": "object",
        "required": [
          "reference"
        ],
        "additionalProperties": false,
        "properties": {
          "reference": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Your payment reference; trimmed."
          },
          "expected_minor": {
            "type": "integer",
            "minimum": 0,
            "description": "The amount you were shown."
          }
        }
      },
      "PayoutProvider": {
        "type": "object",
        "description": "A payout account a program pays through, by name and state. Never a credential.",
        "required": [
          "id",
          "provider",
          "name",
          "mode",
          "status",
          "problem",
          "verified_at",
          "last_used_at",
          "label",
          "needs"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "provider": {
            "type": "string",
            "enum": [
              "webhook",
              "paypal",
              "wise",
              "stripe_connect",
              "tremendous",
              "trolley",
              "tango"
            ]
          },
          "name": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "sandbox"
            ],
            "description": "A sandbox connection never pays a batch."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "error"
            ]
          },
          "problem": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why it is not working",
            "as a sentence.": null
          },
          "verified_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "label": {
            "type": [
              "string",
              "null"
            ],
            "description": "The campaign, profile or reward it pays from."
          },
          "needs": {
            "type": "string",
            "enum": [
              "email",
              "email_or_iban",
              "stripe_account",
              "trolley_recipient",
              "nothing"
            ],
            "description": "What a promoter must give before this provider can pay them."
          }
        }
      },
      "PayoutProviderList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PayoutProvider"
            }
          }
        }
      },
      "PayoutSettings": {
        "type": "object",
        "required": [
          "provider",
          "providers",
          "schedule",
          "payout_day",
          "auto_send",
          "auto_send_available",
          "max_payout_minor",
          "available"
        ],
        "properties": {
          "provider": {
            "$ref": "#/components/schemas/PayoutProviderName"
          },
          "providers": {
            "type": "array",
            "description": "The providers a promoter may choose among; the program's own provider is always one.",
            "items": {
              "$ref": "#/components/schemas/PayoutProviderName"
            }
          },
          "schedule": {
            "type": "string",
            "enum": [
              "manual",
              "monthly"
            ]
          },
          "payout_day": {
            "type": "integer",
            "minimum": 1,
            "maximum": 28,
            "description": "The day a monthly batch is made (UTC)."
          },
          "auto_send": {
            "type": "boolean"
          },
          "auto_send_available": {
            "type": "boolean",
            "description": "False until BoltClaw allows sending on a schedule, after a legal review; until then a person sends every batch."
          },
          "max_payout_minor": {
            "type": [
              "integer",
              "null"
            ],
            "description": "A payout above this needs confirming by name."
          },
          "available": {
            "type": "array",
            "description": "What this program can pay with now (a file, and every live connection that works).",
            "items": {
              "$ref": "#/components/schemas/PayoutProviderName"
            }
          }
        }
      },
      "PayoutSettingsUpdate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "provider": {
            "$ref": "#/components/schemas/PayoutProviderName"
          },
          "providers": {
            "type": "array",
            "maxItems": 8,
            "items": {
              "$ref": "#/components/schemas/PayoutProviderName"
            }
          },
          "auto_send": {
            "type": "boolean"
          },
          "payout_day": {
            "type": "integer",
            "minimum": 1,
            "maximum": 28
          },
          "max_payout_minor": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1
          }
        }
      },
      "FeeEstimate": {
        "type": "object",
        "description": "What the provider may charge YOUR account for one payout. BoltClaw charges nothing.",
        "required": [
          "min_minor",
          "max_minor",
          "sentence"
        ],
        "properties": {
          "min_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "max_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "sentence": {
            "type": "string"
          }
        }
      },
      "PayoutPreview": {
        "type": "object",
        "required": [
          "groups",
          "promoters",
          "waiting",
          "truncated"
        ],
        "properties": {
          "groups": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "currency",
                "provider",
                "provider_name",
                "promoters",
                "total_minor",
                "fee_min_minor",
                "fee_max_minor",
                "fee_label",
                "fee_sentence"
              ],
              "properties": {
                "currency": {
                  "type": "string"
                },
                "provider": {
                  "$ref": "#/components/schemas/PayoutProviderName"
                },
                "provider_name": {
                  "type": "string"
                },
                "promoters": {
                  "type": "integer"
                },
                "total_minor": {
                  "type": "integer"
                },
                "fee_min_minor": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "fee_max_minor": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "fee_label": {
                  "type": "string",
                  "description": "\"estimate, charged by <provider> to your account\""
                },
                "fee_sentence": {
                  "type": "string"
                }
              }
            }
          },
          "promoters": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "partner_id",
                "display_name",
                "currency",
                "owed_minor",
                "payable_minor",
                "provider",
                "provider_name",
                "wait",
                "wait_sentence",
                "waiting_since",
                "fee"
              ],
              "properties": {
                "partner_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "display_name": {
                  "type": "string"
                },
                "currency": {
                  "type": "string"
                },
                "owed_minor": {
                  "type": "integer"
                },
                "payable_minor": {
                  "type": "integer"
                },
                "provider": {
                  "$ref": "#/components/schemas/PayoutProviderName"
                },
                "provider_name": {
                  "type": "string"
                },
                "wait": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "`tax_form`: a tax form the program asks for is missing, expired or replaced. `invoice`: the program waits for the promoter's own invoice.",
                  "enum": [
                    "locked",
                    "details_changed",
                    "flagged",
                    "negative",
                    "under_minimum",
                    "needs_details",
                    "needs_account",
                    "not_connected",
                    "tax_form",
                    "invoice",
                    "terms",
                    null
                  ]
                },
                "wait_sentence": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "waiting_since": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time",
                  "description": "For `tax_form`, since when their approved money has waited for it; otherwise null."
                },
                "fee": {
                  "$ref": "#/components/schemas/FeeEstimate"
                }
              }
            }
          },
          "waiting": {
            "type": "integer"
          },
          "truncated": {
            "type": "boolean",
            "description": "More than the 2,000 largest balances are owed."
          }
        }
      },
      "PayoutBatchResult": {
        "type": "object",
        "required": [
          "status",
          "batch_id",
          "payouts",
          "carried",
          "held"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "created",
              "empty"
            ]
          },
          "batch_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "payouts": {
            "type": "integer"
          },
          "carried": {
            "type": "integer",
            "description": "Promoters whose total was below the minimum or negative."
          },
          "held": {
            "type": "integer",
            "description": "Approved commissions a flag or a payout-details hold kept out."
          }
        }
      },
      "CurrencyTotal": {
        "type": "object",
        "required": [
          "currency",
          "amount_minor"
        ],
        "properties": {
          "currency": {
            "type": "string"
          },
          "amount_minor": {
            "type": "integer"
          }
        }
      },
      "PayoutBatch": {
        "type": "object",
        "required": [
          "id",
          "program_id",
          "label",
          "created_at",
          "sent_at",
          "totals",
          "sendable",
          "lines",
          "waiting_for_funding",
          "auto_send"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "program_id": {
            "type": "string",
            "format": "uuid"
          },
          "label": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "totals": {
            "type": "array",
            "description": "What the batch held when it was made.",
            "items": {
              "$ref": "#/components/schemas/CurrencyTotal"
            }
          },
          "sendable": {
            "type": "object",
            "required": [
              "payout_ids",
              "totals",
              "providers",
              "large"
            ],
            "properties": {
              "payout_ids": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                }
              },
              "totals": {
                "type": "array",
                "description": "Send these back as `expected_totals`.",
                "items": {
                  "$ref": "#/components/schemas/CurrencyTotal"
                }
              },
              "providers": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PayoutProviderName"
                }
              },
              "large": {
                "type": "array",
                "description": "Payouts to name in `confirm_large`.",
                "items": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            }
          },
          "lines": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "partner_id",
                "display_name",
                "provider",
                "provider_name",
                "status",
                "amount_minor",
                "currency",
                "ready",
                "wait",
                "wait_sentence",
                "large",
                "average_minor",
                "fee",
                "delivery"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "partner_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "display_name": {
                  "type": "string"
                },
                "provider": {
                  "$ref": "#/components/schemas/PayoutProviderName"
                },
                "provider_name": {
                  "type": "string"
                },
                "status": {
                  "$ref": "#/components/schemas/PayoutStatus"
                },
                "amount_minor": {
                  "type": "integer"
                },
                "currency": {
                  "type": "string"
                },
                "ready": {
                  "type": "boolean"
                },
                "wait": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "manual",
                    "not_connected",
                    "changed",
                    "zero",
                    "held",
                    "flagged",
                    "needs_details",
                    "needs_account",
                    "tax_form",
                    "invoice",
                    "terms",
                    null
                  ]
                },
                "wait_sentence": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "large": {
                  "type": "boolean"
                },
                "average_minor": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "fee": {
                  "$ref": "#/components/schemas/FeeEstimate"
                },
                "delivery": {
                  "$ref": "#/components/schemas/PayoutDelivery"
                }
              }
            }
          },
          "waiting_for_funding": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "provider",
                "provider_name",
                "payouts",
                "link"
              ],
              "properties": {
                "provider": {
                  "$ref": "#/components/schemas/PayoutProviderName"
                },
                "provider_name": {
                  "type": "string"
                },
                "payouts": {
                  "type": "integer"
                },
                "link": {
                  "type": "string"
                }
              }
            }
          },
          "auto_send": {
            "type": "object",
            "required": [
              "requested_at",
              "send_at",
              "cancelled_at",
              "sent_at"
            ],
            "properties": {
              "requested_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "send_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "cancelled_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "sent_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          }
        }
      },
      "PayoutSend": {
        "type": "object",
        "required": [
          "expected_totals"
        ],
        "additionalProperties": false,
        "properties": {
          "payout_ids": {
            "type": "array",
            "maxItems": 5000,
            "description": "Send only these; every payout in `sendable` when absent.",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "expected_totals": {
            "type": "object",
            "description": "What you expect to send, per currency, in minor units: `{\"USD\": 12000}`. Must equal `sendable.totals`.",
            "additionalProperties": {
              "type": "integer",
              "minimum": 1
            }
          },
          "confirm_large": {
            "type": "array",
            "maxItems": 5000,
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        }
      },
      "PayoutSendResult": {
        "type": "object",
        "required": [
          "batch_id",
          "claimed",
          "results"
        ],
        "properties": {
          "batch_id": {
            "type": "string",
            "format": "uuid"
          },
          "claimed": {
            "type": "integer"
          },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "payout_id",
                "status",
                "failure"
              ],
              "properties": {
                "payout_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "paid",
                    "sent",
                    "awaiting_funding",
                    "unclaimed",
                    "requested",
                    "failed",
                    "unknown"
                  ],
                  "description": "`unknown`: the provider did not answer in time; BoltClaw asks it before anything is sent again."
                },
                "failure": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "AutoSendCancelled": {
        "type": "object",
        "required": [
          "cancelled"
        ],
        "properties": {
          "cancelled": {
            "type": "boolean"
          }
        }
      },
      "PayoutDetails": {
        "type": "object",
        "required": [
          "payout_id",
          "amount_minor",
          "currency",
          "reference",
          "method",
          "details",
          "external_id"
        ],
        "properties": {
          "payout_id": {
            "type": "string",
            "format": "uuid"
          },
          "amount_minor": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "reference": {
            "type": "string"
          },
          "method": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "paypal",
              "wise",
              "bank",
              "other",
              null
            ]
          },
          "details": {
            "type": [
              "object",
              "null"
            ],
            "description": "The promoter's own payout details for this method (an email, or bank fields), in snake_case.",
            "additionalProperties": {
              "type": "string"
            }
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "For one of your own users (a referrer), your own id for them."
          }
        }
      },
      "PayoutResult": {
        "type": "object",
        "required": [
          "status"
        ],
        "additionalProperties": false,
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "paid",
              "failed",
              "returned"
            ]
          },
          "reference": {
            "type": "string",
            "maxLength": 200,
            "description": "Your own payment reference (paid only)."
          },
          "reason": {
            "type": "string",
            "enum": [
              "invalid_destination",
              "recipient_not_ready",
              "insufficient_funds",
              "returned",
              "other"
            ],
            "description": "Why it failed or came back (required unless paid)."
          }
        }
      },
      "TaxRequirement": {
        "type": "string",
        "description": "Whether a program's payouts wait for a tax form. `off`: never. `us_only`: a US person needs\na W-9, and anyone else signs a short statement that they are not a US person. `all`:\neveryone needs a W-9, W-8BEN or W-8BEN-E.\n",
        "enum": [
          "off",
          "us_only",
          "all"
        ]
      },
      "TaxProfileStatus": {
        "type": "object",
        "description": "Whether your own tax details are complete. Never the details themselves: your EIN, legal\nname and addresses are entered and read in the dashboard only.\n",
        "required": [
          "country",
          "domestic_vat_bps",
          "payer_complete",
          "invoice_complete",
          "updated_at"
        ],
        "properties": {
          "country": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your business's country, two letters."
          },
          "domestic_vat_bps": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Your own VAT rate in basis points (2000 is 20%), used for a promoter in your own country."
          },
          "payer_complete": {
            "type": "boolean",
            "description": "Your 1099 payer details (legal name, EIN, US address, telephone) are entered. The 1099 files need them."
          },
          "invoice_complete": {
            "type": "boolean",
            "description": "Your invoicing details (legal name, address, country, VAT number) are entered. Self-billing needs them."
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "NecProvider": {
        "type": "object",
        "description": "One payout provider, and whether what it paid counts toward your 1099-NEC, with why.",
        "required": [
          "provider",
          "label",
          "included",
          "default_included",
          "note",
          "reason",
          "source"
        ],
        "properties": {
          "provider": {
            "$ref": "#/components/schemas/PayoutProviderName"
          },
          "label": {
            "type": "string"
          },
          "included": {
            "type": "boolean",
            "description": "Counted toward your 1099-NEC, as you set it."
          },
          "default_included": {
            "type": "boolean"
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your own note, where you changed the row."
          },
          "reason": {
            "type": "string",
            "description": "Why it is included or left out by default."
          },
          "source": {
            "type": "object",
            "required": [
              "title",
              "url"
            ],
            "properties": {
              "title": {
                "type": "string"
              },
              "url": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "TaxSettings": {
        "type": "object",
        "required": [
          "program_id",
          "tax_forms",
          "invoice_required",
          "self_billing",
          "profile",
          "providers",
          "sources_note",
          "disclaimer"
        ],
        "properties": {
          "program_id": {
            "type": "string",
            "format": "uuid"
          },
          "tax_forms": {
            "$ref": "#/components/schemas/TaxRequirement"
          },
          "invoice_required": {
            "type": "boolean",
            "description": "Each payout waits for the promoter's own invoice. A promoter who self-bills never waits for one."
          },
          "self_billing": {
            "type": "boolean",
            "description": "You issue self-billed invoices for the promoters who accept your self-billing agreement."
          },
          "profile": {
            "$ref": "#/components/schemas/TaxProfileStatus"
          },
          "providers": {
            "type": "array",
            "description": "Which payout providers count toward your 1099-NEC.",
            "items": {
              "$ref": "#/components/schemas/NecProvider"
            }
          },
          "sources_note": {
            "type": "string"
          },
          "disclaimer": {
            "type": "string",
            "description": "BoltClaw prepares these forms; it does not give tax advice or file anything for you."
          }
        }
      },
      "TaxSettingsUpdate": {
        "type": "object",
        "additionalProperties": false,
        "description": "Send only the fields you want to change. An empty object is `400`.",
        "properties": {
          "tax_forms": {
            "$ref": "#/components/schemas/TaxRequirement"
          },
          "invoice_required": {
            "type": "boolean"
          },
          "self_billing": {
            "type": "boolean",
            "description": "Turning it on needs your invoicing details first (`409` until then)."
          }
        }
      },
      "TaxFormType": {
        "type": "string",
        "description": "`w9`: Form W-9, from a US person. `w8ben`: Form W-8BEN, from a person outside the US.\n`w8bene`: Form W-8BEN-E, from an organisation outside the US. `foreign_statement`: a short\nstatement that they are not a US person, which a program set to `us_only` accepts instead\nof a W-8.\n",
        "enum": [
          "w9",
          "w8ben",
          "w8bene",
          "foreign_statement"
        ]
      },
      "TaxFormSummary": {
        "type": [
          "object",
          "null"
        ],
        "description": "A promoter's latest form for your business, by type and status; null when they have none. Never its contents.",
        "required": [
          "id",
          "type",
          "name",
          "revision",
          "status",
          "signed_at",
          "valid_until"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "$ref": "#/components/schemas/TaxFormType"
          },
          "name": {
            "type": "string",
            "description": "The form's name, such as Form W-9."
          },
          "revision": {
            "type": "string",
            "description": "The IRS revision it was built from."
          },
          "status": {
            "type": "string",
            "enum": [
              "signed",
              "superseded",
              "expired",
              "invalid"
            ],
            "description": "`superseded`: replaced after a change of name, tax number or address, and not signed\nagain yet. `expired`: a W-8 past its last day. `invalid`: it no longer matches what was\nsigned; ask them to sign again.\n"
          },
          "signed_at": {
            "type": "string",
            "format": "date-time"
          },
          "valid_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "A W-8's last day (31 December of the third year after it was signed). Null for a W-9 or the statement."
          }
        }
      },
      "TaxFormStatus": {
        "type": "object",
        "required": [
          "partner_id",
          "display_name",
          "origin",
          "form",
          "requirement_met",
          "waiting_since",
          "forfeitable_minor"
        ],
        "properties": {
          "partner_id": {
            "type": "string",
            "format": "uuid"
          },
          "display_name": {
            "type": "string"
          },
          "origin": {
            "type": "string",
            "enum": [
              "account",
              "managed"
            ],
            "description": "`managed`: one of your own users (a referrer)."
          },
          "form": {
            "$ref": "#/components/schemas/TaxFormSummary"
          },
          "requirement_met": {
            "type": "boolean",
            "description": "False while their payouts wait for a form."
          },
          "waiting_since": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Since when their approved money has waited for a form; null when nothing waits."
          },
          "forfeitable_minor": {
            "type": "integer",
            "description": "The approved money waiting for a form, in minor units, which a person may forfeit."
          }
        }
      },
      "TaxFormStatusList": {
        "type": "object",
        "required": [
          "program_id",
          "tax_forms",
          "data",
          "next_cursor",
          "disclaimer"
        ],
        "properties": {
          "program_id": {
            "type": "string",
            "format": "uuid"
          },
          "tax_forms": {
            "$ref": "#/components/schemas/TaxRequirement"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TaxFormStatus"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          },
          "disclaimer": {
            "type": "string"
          }
        }
      },
      "ForfeitRequest": {
        "type": "object",
        "required": [
          "reason"
        ],
        "additionalProperties": false,
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 3,
            "maxLength": 500,
            "description": "Why, recorded in the activity log."
          }
        }
      },
      "ForfeitResult": {
        "type": "object",
        "required": [
          "partner_id",
          "forfeited",
          "totals"
        ],
        "properties": {
          "partner_id": {
            "type": "string",
            "format": "uuid"
          },
          "forfeited": {
            "type": "integer",
            "description": "How many commissions were given up."
          },
          "totals": {
            "type": "array",
            "description": "What was given up, per currency.",
            "items": {
              "$ref": "#/components/schemas/CurrencyTotal"
            }
          }
        }
      },
      "NecReason": {
        "type": "string",
        "description": "Why a person is on your 1099-NEC file or not. `reason_text` says it in a sentence.",
        "enum": [
          "reportable",
          "below_threshold",
          "backup_withholding",
          "not_us_person",
          "corporation",
          "no_form",
          "nothing_reportable",
          "threshold_unset"
        ]
      },
      "NecWarning": {
        "type": "string",
        "description": "Something to fix or check before you file. `text` says it in a sentence.",
        "enum": [
          "no_form",
          "no_tin",
          "backup_withholding",
          "form_superseded",
          "w8_expired",
          "other_currency",
          "network_only"
        ]
      },
      "TaxYearRecipient": {
        "type": "object",
        "description": "One person your programs paid in the year (every membership of one account together), and\nwhether they belong on your 1099-NEC. No tax number and no legal name.\n",
        "required": [
          "reference",
          "partner_ids",
          "display_name",
          "origin",
          "form",
          "included",
          "listed_for_backup_withholding",
          "reason",
          "reason_text",
          "warnings",
          "reportable_minor",
          "excluded_minor",
          "other_currencies",
          "by_provider",
          "e_delivery"
        ],
        "properties": {
          "reference": {
            "type": "string",
            "description": "Their reference in the files (`P-` and ten characters). Never an account id."
          },
          "partner_ids": {
            "type": "array",
            "description": "Their memberships in your programs.",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "display_name": {
            "type": "string"
          },
          "origin": {
            "type": "string",
            "enum": [
              "account",
              "managed"
            ]
          },
          "form": {
            "$ref": "#/components/schemas/TaxFormSummary"
          },
          "included": {
            "type": "boolean",
            "description": "On your 1099-NEC file."
          },
          "listed_for_backup_withholding": {
            "type": "boolean",
            "description": "No W-9 or no tax number, and something reportable was paid: listed for your accountant\nwith a backup withholding mark (24%). BoltClaw does not withhold.\n"
          },
          "reason": {
            "$ref": "#/components/schemas/NecReason"
          },
          "reason_text": {
            "type": "string"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "code",
                "text"
              ],
              "properties": {
                "code": {
                  "$ref": "#/components/schemas/NecWarning"
                },
                "text": {
                  "type": "string"
                }
              }
            }
          },
          "reportable_minor": {
            "type": "integer",
            "description": "US cents paid through the providers you report."
          },
          "excluded_minor": {
            "type": "integer",
            "description": "US cents paid through the providers you leave out, such as PayPal."
          },
          "other_currencies": {
            "type": "array",
            "description": "Paid in other currencies through the providers you report. Never counted; listed for you to convert.",
            "items": {
              "$ref": "#/components/schemas/CurrencyTotal"
            }
          },
          "by_provider": {
            "type": "array",
            "description": "How they were paid in the year.",
            "items": {
              "type": "object",
              "required": [
                "provider",
                "currency",
                "amount_minor",
                "payouts"
              ],
              "properties": {
                "provider": {
                  "$ref": "#/components/schemas/PayoutProviderName"
                },
                "currency": {
                  "type": "string"
                },
                "amount_minor": {
                  "type": "integer"
                },
                "payouts": {
                  "type": "integer"
                }
              }
            }
          },
          "e_delivery": {
            "type": "boolean",
            "description": "They agreed to receive their Copy B electronically. Otherwise, send them paper."
          }
        }
      },
      "TaxYear": {
        "type": "object",
        "required": [
          "year",
          "threshold_minor",
          "payer_complete",
          "recipients",
          "counts",
          "providers",
          "sources_note",
          "disclaimer"
        ],
        "properties": {
          "year": {
            "type": "integer"
          },
          "threshold_minor": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The year's 1099-NEC threshold in US cents (200000 for 2026). Null until it is set for\nthe year, and until then no IRS file can be made for it.\n"
          },
          "payer_complete": {
            "type": "boolean",
            "description": "Your 1099 payer details are entered. The files need them."
          },
          "recipients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TaxYearRecipient"
            }
          },
          "counts": {
            "type": "object",
            "required": [
              "recipients",
              "included",
              "warnings"
            ],
            "properties": {
              "recipients": {
                "type": "integer"
              },
              "included": {
                "type": "integer"
              },
              "warnings": {
                "type": "integer",
                "description": "People with at least one warning."
              }
            }
          },
          "providers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NecProvider"
            }
          },
          "sources_note": {
            "type": "string"
          },
          "disclaimer": {
            "type": "string"
          }
        }
      },
      "Invoice": {
        "type": "object",
        "description": "An invoice for one payout: one you issued in the promoter's name (`self_billed`), or their\nown (`uploaded`). Read its PDF through `POST /referrals/invoices/{id}/link`.\n",
        "required": [
          "id",
          "payout_id",
          "partner_id",
          "kind",
          "status",
          "number",
          "currency",
          "gross_minor",
          "net_minor",
          "vat_minor",
          "vat_bps",
          "vat_treatment",
          "issued_at",
          "accept_by",
          "accepted_at",
          "disputed_at",
          "dispute_reason",
          "dispute_note",
          "replaced_by"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "payout_id": {
            "type": "string",
            "format": "uuid"
          },
          "partner_id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "self_billed",
              "uploaded"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "issued",
              "accepted",
              "disputed",
              "void",
              "received"
            ],
            "description": "`issued`: accepted unless the promoter disputes it by `accept_by` (14 days).\n`accepted`. `disputed`: paused until you issue a corrected one. `void`: replaced (see\n`replaced_by`). `received`: the promoter's own uploaded invoice.\n"
          },
          "number": {
            "type": [
              "string",
              "null"
            ],
            "description": "A self-billed invoice's number, in the promoter's own series with you, without gaps (`SB1A2B3C4D-00042`). Null for an uploaded one."
          },
          "currency": {
            "type": "string"
          },
          "gross_minor": {
            "type": "integer",
            "description": "The payout's amount."
          },
          "net_minor": {
            "type": "integer"
          },
          "vat_minor": {
            "type": "integer"
          },
          "vat_bps": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The VAT rate in basis points, for a domestic supply."
          },
          "vat_treatment": {
            "type": "string",
            "enum": [
              "domestic",
              "reverse_charge",
              "outside_scope",
              "none"
            ],
            "description": "`domestic`: the promoter is in your own country and charges your VAT rate.\n`reverse_charge`: cross-border business to business, so you account for the VAT (the\ninvoice says \"Reverse charge\"). `outside_scope`: you are outside the EU and the UK.\n`none`: an uploaded invoice.\n"
          },
          "issued_at": {
            "type": "string",
            "format": "date-time"
          },
          "accept_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "accepted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "disputed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "dispute_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "details_wrong",
              "amount_wrong",
              "vat_wrong",
              "not_mine",
              "other",
              null
            ]
          },
          "dispute_note": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the promoter wrote, up to 500 characters."
          },
          "replaced_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          }
        }
      },
      "InvoiceList": {
        "type": "object",
        "required": [
          "payout_id",
          "data"
        ],
        "properties": {
          "payout_id": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Invoice"
            }
          }
        }
      },
      "FileLink": {
        "type": "object",
        "required": [
          "url",
          "expires_at"
        ],
        "properties": {
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Downloads the PDF once, within ten minutes. Treat it as a secret. Null in a replay of\nthe same `Idempotency-Key`: ask for a new one.\n"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "BrandEffective": {
        "type": "object",
        "description": "The brand partners of this scope actually see, the program's override over the workspace's.",
        "required": [
          "name",
          "logo_light_url",
          "logo_dark_url",
          "favicon_url",
          "accent",
          "background",
          "radius",
          "show_powered_by",
          "sender_name",
          "reply_to"
        ],
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "logo_light_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "logo_dark_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "favicon_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "accent": {
            "type": [
              "string",
              "null"
            ],
            "description": "`#rrggbb`."
          },
          "background": {
            "type": [
              "string",
              "null"
            ],
            "description": "`#rrggbb`."
          },
          "radius": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Corner radius in pixels, 0 to 24."
          },
          "show_powered_by": {
            "type": "boolean",
            "description": "Whether \"Powered by BoltClaw\" shows. Off unless you turn it on."
          },
          "sender_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The name partners' emails come from; null for the default sender."
          },
          "reply_to": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "description": "The confirmed support address partners' replies go to; null until one is confirmed."
          }
        }
      },
      "Brand": {
        "type": "object",
        "required": [
          "scope",
          "program_id",
          "name",
          "accent",
          "background",
          "radius",
          "sender_name",
          "show_powered_by",
          "support_email",
          "support_email_verified",
          "logo_light_url",
          "logo_dark_url",
          "favicon_url",
          "effective",
          "updated_at"
        ],
        "properties": {
          "scope": {
            "type": "string",
            "enum": [
              "workspace",
              "program"
            ],
            "description": "`workspace`: the brand of every program. `program`: one program's override."
          },
          "program_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "What this brand sets; null follows the workspace's brand (or the program's own name)."
          },
          "accent": {
            "type": [
              "string",
              "null"
            ],
            "description": "`#rrggbb`, used for buttons and links."
          },
          "background": {
            "type": [
              "string",
              "null"
            ],
            "description": "`#rrggbb`, the portal's page colour."
          },
          "radius": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Corner radius in pixels, 0 to 24."
          },
          "sender_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The name partners' emails come from. The address stays BoltClaw's."
          },
          "show_powered_by": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "support_email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "support_email_verified": {
            "type": "boolean",
            "description": "Confirmed with a code; only then is it the reply-to of partners' emails."
          },
          "logo_light_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "logo_dark_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "favicon_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "effective": {
            "$ref": "#/components/schemas/BrandEffective"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "BrandUpdate": {
        "type": "object",
        "additionalProperties": false,
        "description": "Send only the fields you want to change; `null` clears one. An empty object is `400`.",
        "properties": {
          "program_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Change this program's override instead of the workspace's brand."
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80,
            "description": "No control characters and no angle brackets."
          },
          "accent": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^#[0-9a-fA-F]{6}$"
          },
          "background": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^#[0-9a-fA-F]{6}$"
          },
          "radius": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 24
          },
          "support_email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "description": "Unconfirmed until its code comes back."
          },
          "show_powered_by": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "sender_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 60,
            "description": "No control characters and none of these: \" < > @ , ; : \\"
          },
          "logo_light_path": {
            "type": [
              "string",
              "null"
            ],
            "description": "The `path` an upload answered, or null to remove the logo."
          },
          "logo_dark_path": {
            "type": [
              "string",
              "null"
            ],
            "description": "The `path` an upload answered, or null to remove the logo for dark backgrounds."
          },
          "favicon_path": {
            "type": [
              "string",
              "null"
            ],
            "description": "The `path` an upload answered, or null to remove the icon."
          }
        }
      },
      "BrandFileSlot": {
        "type": "string",
        "description": "`logo_light`: the logo on light backgrounds. `logo_dark`: on dark ones. `favicon`: the browser tab's icon.",
        "enum": [
          "logo_light",
          "logo_dark",
          "favicon"
        ]
      },
      "BrandUploadRequest": {
        "type": "object",
        "required": [
          "slot",
          "content_type"
        ],
        "additionalProperties": false,
        "properties": {
          "program_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "For a program's override."
          },
          "slot": {
            "$ref": "#/components/schemas/BrandFileSlot"
          },
          "content_type": {
            "type": "string",
            "enum": [
              "image/png",
              "image/jpeg",
              "image/webp"
            ]
          }
        }
      },
      "BrandUpload": {
        "type": "object",
        "required": [
          "upload_url",
          "path",
          "slot",
          "expires_at"
        ],
        "properties": {
          "upload_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "PUT the file's bytes here, with its content type. One file; treat it as a secret. Null\nin a replay of the same `Idempotency-Key`: ask for a new one.\n"
          },
          "path": {
            "type": "string",
            "description": "Send it to PATCH /referrals/brand as the slot's field once the file is uploaded."
          },
          "slot": {
            "$ref": "#/components/schemas/BrandFileSlot"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "BrandScope": {
        "type": "object",
        "additionalProperties": false,
        "description": "An empty object for the workspace's brand.",
        "properties": {
          "program_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A program's override."
          }
        }
      },
      "BrandEmailCodeSent": {
        "type": "object",
        "required": [
          "sent",
          "to"
        ],
        "properties": {
          "sent": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "to": {
            "type": "string",
            "format": "email",
            "description": "The support address the code went to: your own."
          }
        }
      },
      "BrandEmailCodeConfirm": {
        "type": "object",
        "required": [
          "code"
        ],
        "additionalProperties": false,
        "properties": {
          "program_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "code": {
            "type": "string",
            "pattern": "^[0-9]{6}$",
            "description": "The six digits from the email."
          }
        }
      },
      "DomainKind": {
        "type": "string",
        "description": "`links`: your referral links answer on it (`https://go.example.com/<code>`). `portal`: your partner portal and join pages, and links too.",
        "enum": [
          "links",
          "portal"
        ]
      },
      "DomainStatus": {
        "type": "string",
        "description": "`pending_ownership`: waiting for the TXT record that proves it is yours. `pending_dns`:\nwaiting for the CNAME record. `pending_certificate`: the certificate is being issued.\n`active`: answering. `detached`: its records stopped pointing at us, so it stopped\nanswering; put them back to reconnect it, or it is removed after 7 days (`remove_after`).\n",
        "enum": [
          "pending_ownership",
          "pending_dns",
          "pending_certificate",
          "active",
          "detached"
        ]
      },
      "DnsRecord": {
        "type": "object",
        "required": [
          "type",
          "name",
          "value",
          "purpose",
          "done"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "TXT",
              "CNAME"
            ]
          },
          "name": {
            "type": "string",
            "description": "The record's full name, as your DNS host may ask for it."
          },
          "value": {
            "type": [
              "string",
              "null"
            ],
            "description": "What it must hold; null for the CNAME until the TXT record is seen, when the target for this domain is known."
          },
          "purpose": {
            "type": "string",
            "enum": [
              "ownership",
              "routing",
              "provider"
            ],
            "description": "`ownership`: proves the domain is yours. `routing`: points it at us. `provider`: a second TXT record our hosting provider asks for."
          },
          "done": {
            "type": "boolean",
            "description": "In place, as last checked."
          }
        }
      },
      "Domain": {
        "type": "object",
        "required": [
          "id",
          "host",
          "kind",
          "status",
          "status_text",
          "all_programs",
          "program_ids",
          "records",
          "problem",
          "caa_warning",
          "url",
          "checked_at",
          "activated_at",
          "detached_at",
          "remove_after",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "host": {
            "type": "string",
            "description": "Lower case, international names in their punycode form."
          },
          "kind": {
            "$ref": "#/components/schemas/DomainKind"
          },
          "status": {
            "$ref": "#/components/schemas/DomainStatus"
          },
          "status_text": {
            "type": "string",
            "description": "The status, in a few words."
          },
          "all_programs": {
            "type": "boolean",
            "description": "It serves every program, new ones included."
          },
          "program_ids": {
            "type": "array",
            "description": "The programs it serves when `all_programs` is false; empty otherwise.",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "records": {
            "type": "array",
            "description": "Every DNS record it needs, in the order to set them.",
            "items": {
              "$ref": "#/components/schemas/DnsRecord"
            }
          },
          "problem": {
            "type": [
              "string",
              "null"
            ],
            "description": "What is in the way, as a sentence; null when nothing is."
          },
          "caa_warning": {
            "type": "boolean",
            "description": "A CAA record on your domain does not allow letsencrypt.org, which issues the certificate."
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "`https://<host>` once active."
          },
          "checked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "activated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "detached_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "remove_after": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When a detached domain is removed."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DomainList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Domain"
            }
          }
        }
      },
      "DomainCreate": {
        "type": "object",
        "required": [
          "host",
          "kind"
        ],
        "additionalProperties": false,
        "properties": {
          "host": {
            "type": "string",
            "maxLength": 300,
            "description": "A subdomain of yours, such as go.example.com. Never a domain's root, a wildcard or an IP address."
          },
          "kind": {
            "$ref": "#/components/schemas/DomainKind"
          },
          "all_programs": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Serve every program, new ones included. True when left out."
          },
          "program_ids": {
            "type": [
              "array",
              "null"
            ],
            "maxItems": 50,
            "description": "The programs it serves, when `all_programs` is false (1 to 50).",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        }
      },
      "DomainRemoved": {
        "type": "object",
        "required": [
          "removed",
          "host"
        ],
        "properties": {
          "removed": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "host": {
            "type": "string"
          }
        }
      },
      "DirectoryCategory": {
        "type": "string",
        "enum": [
          "software",
          "ai_tools",
          "education",
          "finance",
          "health",
          "ecommerce",
          "creator_tools",
          "agencies",
          "other"
        ]
      },
      "DirectoryCard": {
        "type": "object",
        "description": "A program as the partner directory shows it to signed-in partners. Nothing about who\npromotes it or how many do, and no id but the listing's own.\n",
        "required": [
          "listing_id",
          "name",
          "category",
          "category_label",
          "pitch",
          "logo_url",
          "rewards_summary",
          "cookie_days",
          "payout_schedule",
          "payout_day",
          "min_payout",
          "pays_through",
          "countries",
          "languages",
          "listed_since",
          "relation"
        ],
        "properties": {
          "listing_id": {
            "type": "string",
            "format": "uuid",
            "description": "The listing's own id: never the program's."
          },
          "name": {
            "type": "string"
          },
          "category": {
            "$ref": "#/components/schemas/DirectoryCategory"
          },
          "category_label": {
            "type": "string"
          },
          "pitch": {
            "type": [
              "string",
              "null"
            ]
          },
          "logo_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "rewards_summary": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Generated from the program's own reward rules, never typed."
          },
          "cookie_days": {
            "type": "integer"
          },
          "payout_schedule": {
            "type": "string",
            "enum": [
              "manual",
              "monthly"
            ]
          },
          "payout_day": {
            "type": [
              "integer",
              "null"
            ]
          },
          "min_payout": {
            "type": "object",
            "required": [
              "amount_minor",
              "currency"
            ],
            "properties": {
              "amount_minor": {
                "type": "integer"
              },
              "currency": {
                "type": "string"
              }
            }
          },
          "pays_through": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "manual",
                "webhook",
                "paypal",
                "wise",
                "stripe_connect",
                "tremendous",
                "trolley",
                "tango"
              ]
            }
          },
          "countries": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "listed_since": {
            "type": "string",
            "description": "YYYY-MM-DD."
          },
          "relation": {
            "type": "string",
            "enum": [
              "own",
              "member",
              "none"
            ],
            "description": "About the reader only."
          }
        }
      },
      "ListingSettings": {
        "type": "object",
        "required": [
          "program_id",
          "listing_id",
          "listed",
          "category",
          "pitch",
          "logo_url",
          "countries",
          "languages",
          "listed_since",
          "state",
          "missing",
          "preview"
        ],
        "properties": {
          "program_id": {
            "type": "string",
            "format": "uuid"
          },
          "listing_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Null until the listing is first saved."
          },
          "listed": {
            "type": "boolean"
          },
          "category": {
            "$ref": "#/components/schemas/DirectoryCategory"
          },
          "pitch": {
            "type": [
              "string",
              "null"
            ]
          },
          "logo_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "countries": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "listed_since": {
            "type": [
              "string",
              "null"
            ]
          },
          "state": {
            "type": "string",
            "enum": [
              "unlisted",
              "listed",
              "held",
              "delisted"
            ]
          },
          "missing": {
            "type": "array",
            "description": "Every condition not met yet. Listing is refused while any is.",
            "items": {
              "type": "object",
              "required": [
                "code",
                "sentence"
              ],
              "properties": {
                "code": {
                  "type": "string",
                  "enum": [
                    "program_inactive",
                    "invite_only",
                    "no_terms",
                    "no_reward",
                    "org_too_new",
                    "org_suspended",
                    "no_sale",
                    "delisted"
                  ]
                },
                "sentence": {
                  "type": "string"
                }
              }
            }
          },
          "preview": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/DirectoryCard"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "ListingUpdate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "listed": {
            "type": "boolean"
          },
          "category": {
            "$ref": "#/components/schemas/DirectoryCategory"
          },
          "pitch": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 280,
            "description": "Plain text: no HTML."
          },
          "countries": {
            "type": [
              "array",
              "null"
            ],
            "maxItems": 60,
            "items": {
              "type": "string",
              "pattern": "^[A-Za-z]{2}$"
            },
            "description": "Two-letter country codes the program welcomes partners from."
          },
          "languages": {
            "type": [
              "array",
              "null"
            ],
            "maxItems": 20,
            "items": {
              "type": "string",
              "pattern": "^[A-Za-z]{2}$"
            },
            "description": "Two-letter language codes, like en or de."
          },
          "logo_path": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "The `path` from the logo upload, or null to remove it."
          }
        }
      },
      "ImageUploadCreate": {
        "type": "object",
        "required": [
          "content_type"
        ],
        "additionalProperties": false,
        "properties": {
          "content_type": {
            "type": "string",
            "enum": [
              "image/png",
              "image/jpeg",
              "image/webp"
            ]
          }
        }
      },
      "ImageUpload": {
        "type": "object",
        "required": [
          "upload_url",
          "path",
          "expires_at"
        ],
        "properties": {
          "upload_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "PUT the file here. Null in a replay of the same Idempotency-Key."
          },
          "path": {
            "type": "string"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "BroadcastAudience": {
        "type": "object",
        "required": [
          "kind"
        ],
        "additionalProperties": false,
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "all",
              "group",
              "partners"
            ]
          },
          "group_id": {
            "type": "string",
            "format": "uuid",
            "description": "With `group`: the group or level."
          },
          "partner_ids": {
            "type": "array",
            "maxItems": 500,
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "With `partners`: the promoters you picked."
          }
        }
      },
      "BroadcastCreate": {
        "type": "object",
        "required": [
          "subject",
          "body"
        ],
        "additionalProperties": false,
        "properties": {
          "subject": {
            "type": "string",
            "maxLength": 120
          },
          "body": {
            "type": "string",
            "maxLength": 5000,
            "description": "Markdown subset: paragraphs, **bold**, *italic*, [links](https://...) and lists. No HTML,\nno images, https links only. `{name}`, `{link}` and `{code}` are filled per promoter.\n"
          },
          "audience": {
            "$ref": "#/components/schemas/BroadcastAudience"
          }
        }
      },
      "BroadcastUpdate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "subject": {
            "type": "string",
            "maxLength": 120
          },
          "body": {
            "type": "string",
            "maxLength": 5000
          },
          "audience": {
            "$ref": "#/components/schemas/BroadcastAudience"
          }
        }
      },
      "Broadcast": {
        "type": "object",
        "required": [
          "id",
          "program_id",
          "subject",
          "body",
          "audience",
          "status",
          "via",
          "link_hosts",
          "preview",
          "test_sends",
          "tested_at",
          "tested_current",
          "sent_at",
          "finished_at",
          "counts",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "program_id": {
            "type": "string",
            "format": "uuid"
          },
          "subject": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "audience": {
            "$ref": "#/components/schemas/BroadcastAudience"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "sending",
              "sent"
            ]
          },
          "via": {
            "type": "string",
            "enum": [
              "dashboard",
              "api"
            ]
          },
          "link_hosts": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Every host the message links to."
          },
          "preview": {
            "type": "string",
            "description": "The first line, with sample values in the merge fields."
          },
          "test_sends": {
            "type": "integer"
          },
          "tested_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "tested_current": {
            "type": "boolean",
            "description": "Whether the current text was test-sent."
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "counts": {
            "type": "object",
            "required": [
              "recipients",
              "emailed",
              "failed",
              "muted",
              "referrers",
              "skipped"
            ],
            "properties": {
              "recipients": {
                "type": "integer"
              },
              "emailed": {
                "type": "integer"
              },
              "failed": {
                "type": "integer"
              },
              "muted": {
                "type": "integer",
                "description": "Muted the program's emails; they still see it on their program page."
              },
              "referrers": {
                "type": "integer",
                "description": "Your own users in the in-app panel: never emailed by BoltClaw."
              },
              "skipped": {
                "type": "integer"
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "BroadcastList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Broadcast"
            }
          }
        }
      },
      "BroadcastTested": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Broadcast"
          },
          {
            "type": "object",
            "required": [
              "sent_to"
            ],
            "properties": {
              "sent_to": {
                "type": "string",
                "description": "The key creator's own address, where the test went."
              }
            }
          }
        ]
      },
      "BroadcastReferrer": {
        "type": "object",
        "required": [
          "referrer_id",
          "external_id",
          "display_name",
          "link",
          "code"
        ],
        "properties": {
          "referrer_id": {
            "type": "string",
            "format": "uuid"
          },
          "external_id": {
            "type": "string",
            "description": "Your own id for the user."
          },
          "display_name": {
            "type": "string"
          },
          "link": {
            "type": [
              "string",
              "null"
            ]
          },
          "code": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "BroadcastReferrerList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BroadcastReferrer"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ImportRow": {
        "type": "object",
        "required": [
          "name",
          "email"
        ],
        "additionalProperties": false,
        "properties": {
          "line": {
            "type": "integer",
            "minimum": 1,
            "description": "The row's line in your file, echoed in errors."
          },
          "name": {
            "type": "string",
            "maxLength": 80
          },
          "email": {
            "type": "string",
            "maxLength": 254,
            "description": "Hashed on arrival, never stored, never emailed."
          },
          "code": {
            "type": "string",
            "maxLength": 40,
            "description": "A vanity code held for this person."
          },
          "group": {
            "type": "string",
            "maxLength": 80,
            "description": "A group or level of the program, by name."
          },
          "rate_bps": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10000,
            "description": "A custom rate for this person, in basis points."
          },
          "balance_minor": {
            "type": "integer",
            "minimum": 1,
            "description": "An opening balance owed, paid as a bonus when they claim."
          },
          "currency": {
            "type": "string",
            "description": "The balance's currency: the program's own."
          }
        }
      },
      "ImportRequest": {
        "type": "object",
        "additionalProperties": false,
        "description": "Either `rows` or `csv`, not both.",
        "properties": {
          "rows": {
            "type": "array",
            "minItems": 1,
            "maxItems": 500,
            "items": {
              "$ref": "#/components/schemas/ImportRow"
            }
          },
          "csv": {
            "type": "string",
            "maxLength": 65000,
            "description": "A CSV with a header row; at most 500 rows a call."
          }
        }
      },
      "ImportCommit": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ImportRequest"
          },
          {
            "type": "object",
            "properties": {
              "import_id": {
                "type": "string",
                "format": "uuid",
                "description": "Continue an import begun by an earlier part of the file."
              }
            }
          }
        ],
        "unevaluatedProperties": false
      },
      "ImportRowError": {
        "type": "object",
        "required": [
          "line",
          "field",
          "message"
        ],
        "properties": {
          "line": {
            "type": "integer"
          },
          "field": {
            "type": "string",
            "enum": [
              "name",
              "email",
              "code",
              "group",
              "rate",
              "balance",
              "currency",
              "row"
            ]
          },
          "message": {
            "type": "string"
          }
        }
      },
      "ImportPreview": {
        "type": "object",
        "required": [
          "rows",
          "valid",
          "errors",
          "with_balance"
        ],
        "properties": {
          "rows": {
            "type": "integer"
          },
          "valid": {
            "type": "integer"
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ImportRowError"
            }
          },
          "with_balance": {
            "type": "integer"
          }
        }
      },
      "ImportInvite": {
        "type": "object",
        "required": [
          "id",
          "line",
          "name",
          "code",
          "group_id",
          "rate_bps",
          "balance",
          "link",
          "state",
          "claimed_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "line": {
            "type": [
              "integer",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "code": {
            "type": [
              "string",
              "null"
            ]
          },
          "group_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "rate_bps": {
            "type": [
              "integer",
              "null"
            ]
          },
          "balance": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "amount_minor",
              "currency"
            ],
            "properties": {
              "amount_minor": {
                "type": "integer"
              },
              "currency": {
                "type": "string"
              }
            }
          },
          "link": {
            "type": "string",
            "description": "This person's personal link. Send it from your own email."
          },
          "state": {
            "type": "string",
            "enum": [
              "open",
              "waiting",
              "claimed",
              "revoked"
            ]
          },
          "claimed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ImportCommitted": {
        "type": "object",
        "required": [
          "import_id",
          "invites"
        ],
        "properties": {
          "import_id": {
            "type": "string",
            "format": "uuid"
          },
          "invites": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ImportInvite"
            }
          }
        }
      },
      "ImportInviteList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ImportInvite"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Import": {
        "type": "object",
        "required": [
          "id",
          "program_id",
          "invites",
          "with_balance",
          "claimed",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "program_id": {
            "type": "string",
            "format": "uuid"
          },
          "invites": {
            "type": "integer"
          },
          "with_balance": {
            "type": "integer"
          },
          "claimed": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ImportList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Import"
            }
          }
        }
      },
      "LibraryItem": {
        "type": "object",
        "required": [
          "id",
          "program_id",
          "kind",
          "title",
          "body",
          "url",
          "host",
          "image_url",
          "ready",
          "group_id",
          "in_panel",
          "position",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "program_id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "image",
              "snippet",
              "link"
            ]
          },
          "title": {
            "type": "string"
          },
          "body": {
            "type": [
              "string",
              "null"
            ],
            "description": "A snippet's text, with `{link}` and `{code}` as written."
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          },
          "host": {
            "type": [
              "string",
              "null"
            ]
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "ready": {
            "type": "boolean",
            "description": "False for an image item whose file is not in yet."
          },
          "group_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "in_panel": {
            "type": "boolean"
          },
          "position": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "LibraryItemList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LibraryItem"
            }
          }
        }
      },
      "LibraryItemCreate": {
        "type": "object",
        "required": [
          "kind",
          "title"
        ],
        "additionalProperties": false,
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "image",
              "snippet",
              "link"
            ]
          },
          "title": {
            "type": "string",
            "maxLength": 120
          },
          "body": {
            "type": "string",
            "maxLength": 2000,
            "description": "A snippet's text."
          },
          "url": {
            "type": "string",
            "maxLength": 2048,
            "description": "A link's https address."
          },
          "group_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "in_panel": {
            "type": "boolean",
            "description": "A snippet only: shown to your own users in the in-app panel too."
          },
          "position": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000
          }
        }
      },
      "LibraryItemUpdate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 120
          },
          "body": {
            "type": "string",
            "maxLength": 2000
          },
          "url": {
            "type": "string",
            "maxLength": 2048
          },
          "group_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "in_panel": {
            "type": "boolean"
          },
          "position": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000
          },
          "image_path": {
            "type": "string",
            "maxLength": 200,
            "description": "An image item's file, from its upload."
          }
        }
      },
      "LibraryItemDeleted": {
        "type": "object",
        "required": [
          "deleted",
          "id"
        ],
        "properties": {
          "deleted": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "LinkHost": {
        "type": "object",
        "required": [
          "host",
          "source",
          "verified",
          "record",
          "checked_at",
          "problem"
        ],
        "properties": {
          "host": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "product",
              "allowed"
            ],
            "description": "`product`: the product URL's own host, needing no proof."
          },
          "verified": {
            "type": "boolean"
          },
          "record": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "type",
              "name",
              "value"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "TXT"
                ]
              },
              "name": {
                "type": "string"
              },
              "value": {
                "type": "string"
              }
            }
          },
          "checked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "problem": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "LinkHostList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LinkHost"
            }
          }
        }
      },
      "BoardMetric": {
        "type": "string",
        "enum": [
          "customers",
          "sales",
          "revenue",
          "friends"
        ],
        "description": "`customers`: paying customers brought; `sales`: payments; `revenue`: their amount, in minor units; `friends`: on a waitlist, friends who joined and confirmed."
      },
      "BoardPeriod": {
        "type": "string",
        "enum": [
          "month",
          "30d",
          "quarter",
          "all"
        ],
        "description": "`month` and `quarter` are calendar ones (UTC); `30d` the last 30 days; `all` since the program began."
      },
      "BoardNameChoice": {
        "type": "string",
        "enum": [
          "hidden",
          "initials",
          "display"
        ],
        "description": "How a promoter chose to appear to the program's other promoters. `hidden` is \"A promoter\", the default for a partner; one of your own users starts `initials`."
      },
      "LeaderboardSettings": {
        "type": "object",
        "required": [
          "metric",
          "period",
          "show",
          "size",
          "panel"
        ],
        "properties": {
          "metric": {
            "$ref": "#/components/schemas/BoardMetric"
          },
          "period": {
            "$ref": "#/components/schemas/BoardPeriod"
          },
          "show": {
            "type": "string",
            "enum": [
              "counts",
              "amounts"
            ],
            "description": "`counts`: promoters see places and counts, never money; `amounts`: revenue too."
          },
          "size": {
            "type": "integer",
            "minimum": 10,
            "maximum": 100,
            "description": "How many places promoters and the panel see."
          },
          "panel": {
            "type": "boolean",
            "description": "Whether the in-app panel shows a \"Top referrers\" card."
          }
        }
      },
      "LeaderboardFields": {
        "type": [
          "object",
          "null"
        ],
        "additionalProperties": false,
        "description": "The leaderboard promoters see, merged onto the current setting; null switches it off. You always see your own.",
        "properties": {
          "metric": {
            "$ref": "#/components/schemas/BoardMetric"
          },
          "period": {
            "$ref": "#/components/schemas/BoardPeriod"
          },
          "show": {
            "type": "string",
            "enum": [
              "counts",
              "amounts"
            ]
          },
          "size": {
            "type": "integer",
            "minimum": 10,
            "maximum": 100
          },
          "panel": {
            "type": "boolean"
          }
        }
      },
      "LeaderboardRow": {
        "type": "object",
        "required": [
          "rank",
          "value",
          "partner_id",
          "kind",
          "display_name",
          "external_id",
          "appears_as",
          "under_review",
          "last_activity_at"
        ],
        "properties": {
          "rank": {
            "type": "integer",
            "minimum": 1
          },
          "value": {
            "type": "integer",
            "description": "The count",
            "or revenue in minor units.": null
          },
          "partner_id": {
            "type": "string",
            "format": "uuid",
            "description": "The membership id."
          },
          "kind": {
            "type": "string",
            "enum": [
              "partner",
              "referrer"
            ],
            "description": "`referrer`: one of your own users."
          },
          "display_name": {
            "type": "string"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your own id",
            "for one of your users.": null
          },
          "appears_as": {
            "$ref": "#/components/schemas/BoardNameChoice"
          },
          "under_review": {
            "type": "boolean",
            "description": "An open fraud flag of theirs in the program."
          },
          "last_activity_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Leaderboard": {
        "type": "object",
        "required": [
          "program_id",
          "settings",
          "metric",
          "period",
          "title",
          "since",
          "until",
          "ranked",
          "computed_at",
          "rows"
        ],
        "properties": {
          "program_id": {
            "type": "string",
            "format": "uuid"
          },
          "settings": {
            "description": "The program's setting; null when promoters see no leaderboard.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/LeaderboardSettings"
              },
              {
                "type": "null"
              }
            ]
          },
          "metric": {
            "$ref": "#/components/schemas/BoardMetric"
          },
          "period": {
            "$ref": "#/components/schemas/BoardPeriod"
          },
          "title": {
            "type": "string",
            "description": "\"paying customers, this month\"."
          },
          "since": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ranked": {
            "type": "integer",
            "description": "How many promoters have something counted."
          },
          "computed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LeaderboardRow"
            }
          }
        }
      },
      "ContestStatus": {
        "type": "string",
        "enum": [
          "scheduled",
          "running",
          "ended",
          "awarded",
          "cancelled"
        ]
      },
      "ContestPrizeInput": {
        "type": "object",
        "required": [
          "rank",
          "kind"
        ],
        "additionalProperties": false,
        "properties": {
          "rank": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          },
          "through_rank": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "description": "The same prize for every place from `rank` to this one."
          },
          "kind": {
            "type": "string",
            "enum": [
              "cash",
              "grant"
            ]
          },
          "amount_minor": {
            "type": "integer",
            "minimum": 1,
            "description": "Cash, credit or a gift card, in the program's currency's minor units."
          },
          "reward_type": {
            "type": "string",
            "enum": [
              "credit",
              "gift_card",
              "free_period",
              "perk"
            ],
            "description": "A `grant`'s type."
          },
          "perk_text": {
            "type": "string",
            "maxLength": 120,
            "description": "A perk's words: \"A hoodie\"."
          },
          "period_count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 365,
            "description": "A free period's length: up to 365 days or 36 months."
          },
          "period_unit": {
            "type": "string",
            "enum": [
              "day",
              "month"
            ]
          }
        }
      },
      "ContestPrize": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ContestPrizeInput"
          },
          {
            "type": "object",
            "required": [
              "place",
              "text"
            ],
            "properties": {
              "place": {
                "type": "string",
                "description": "\"1st\", \"2nd to 5th\"."
              },
              "text": {
                "type": "string",
                "description": "\"500.00 USD\", \"A hoodie\", \"1 month free\"."
              }
            }
          }
        ]
      },
      "Contest": {
        "type": "object",
        "required": [
          "id",
          "program_id",
          "name",
          "description",
          "starts_at",
          "ends_at",
          "metric",
          "group_id",
          "prizes",
          "minimum",
          "status",
          "status_text",
          "ended_at",
          "awarded_at",
          "cancelled_at",
          "cancel_reason",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "program_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "ends_at": {
            "type": "string",
            "format": "date-time"
          },
          "metric": {
            "$ref": "#/components/schemas/BoardMetric"
          },
          "group_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Only one group or level may enter; null for every promoter."
          },
          "prizes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContestPrize"
            }
          },
          "minimum": {
            "type": "integer",
            "description": "What a promoter must reach to win a prize."
          },
          "status": {
            "$ref": "#/components/schemas/ContestStatus"
          },
          "status_text": {
            "type": "string"
          },
          "ended_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "awarded_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cancel_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ContestList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Contest"
            }
          }
        }
      },
      "ContestStanding": {
        "type": "object",
        "required": [
          "rank",
          "place",
          "qualified",
          "value",
          "partner_id",
          "kind",
          "display_name",
          "external_id",
          "appears_as",
          "under_review",
          "prize"
        ],
        "properties": {
          "rank": {
            "type": "integer"
          },
          "place": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Place among those who reached the minimum (what a prize goes by)."
          },
          "qualified": {
            "type": "boolean"
          },
          "value": {
            "type": "integer"
          },
          "partner_id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "partner",
              "referrer"
            ]
          },
          "display_name": {
            "type": "string"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "appears_as": {
            "$ref": "#/components/schemas/BoardNameChoice"
          },
          "under_review": {
            "type": "boolean"
          },
          "prize": {
            "type": [
              "string",
              "null"
            ],
            "description": "The prize this place wins as things stand."
          }
        }
      },
      "ContestDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Contest"
          },
          {
            "type": "object",
            "required": [
              "standings",
              "computed_at",
              "award"
            ],
            "properties": {
              "standings": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ContestStanding"
                }
              },
              "computed_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "award": {
                "type": "object",
                "required": [
                  "possible",
                  "reason",
                  "awardable_at",
                  "winners",
                  "under_review"
                ],
                "properties": {
                  "possible": {
                    "type": "boolean",
                    "description": "Whether the prizes may be awarded now."
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Why not",
                    "when not.": null
                  },
                  "awardable_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "When the last hold on what the winners counted passes."
                  },
                  "winners": {
                    "type": "integer"
                  },
                  "under_review": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        ]
      },
      "ContestFields": {
        "type": "object",
        "properties": {
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1000
          },
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "ends_at": {
            "type": "string",
            "format": "date-time",
            "description": "At most 366 days after the start."
          },
          "metric": {
            "$ref": "#/components/schemas/BoardMetric"
          },
          "group_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "prizes": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/ContestPrizeInput"
            }
          },
          "minimum": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "ContestCreate": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ContestFields"
          },
          {
            "type": "object",
            "required": [
              "name",
              "starts_at",
              "ends_at",
              "prizes"
            ],
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 80
              }
            }
          }
        ],
        "unevaluatedProperties": false
      },
      "ContestUpdate": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ContestFields"
          },
          {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 80
              }
            }
          }
        ],
        "unevaluatedProperties": false
      },
      "ContestAward": {
        "type": "object",
        "required": [
          "contest",
          "awarded"
        ],
        "properties": {
          "contest": {
            "$ref": "#/components/schemas/Contest"
          },
          "awarded": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "partner_id",
                "place",
                "commission_id",
                "grant_id"
              ],
              "properties": {
                "partner_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "place": {
                  "type": "integer"
                },
                "commission_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid",
                  "description": "A cash prize's bonus."
                },
                "grant_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid",
                  "description": "Any other prize's reward."
                }
              }
            }
          }
        }
      },
      "ReasonBody": {
        "type": "object",
        "required": [
          "reason"
        ],
        "additionalProperties": false,
        "properties": {
          "reason": {
            "type": "string",
            "minLength": 3,
            "maxLength": 500
          }
        }
      },
      "ReportLink": {
        "type": "object",
        "required": [
          "type",
          "url",
          "expires_at",
          "filename"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "commissions",
              "conversions",
              "partners",
              "payouts",
              "grants",
              "contests"
            ]
          },
          "url": {
            "type": "string",
            "description": "Open it once, within ten minutes, to download the CSV. Only its fingerprint is kept."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "filename": {
            "type": "string",
            "description": "What the file is called when it downloads, like `acme-partners-commissions-2026-10-27.csv`."
          }
        }
      },
      "ReferralLink": {
        "type": "object",
        "required": [
          "id",
          "code",
          "url",
          "kind",
          "placement",
          "archived_at",
          "created_at",
          "partner"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "`https://boltclaw.ai/r/<code>`, or on your own link domain."
          },
          "kind": {
            "type": "string",
            "enum": [
              "primary",
              "placement"
            ]
          },
          "placement": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "bio",
              "post",
              "automation",
              "manual",
              null
            ],
            "description": "Where BoltClaw placed it, by type only (never the partner's own note)."
          },
          "archived_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "partner": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "id",
              "display_name"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "display_name": {
                "type": "string"
              }
            }
          }
        }
      },
      "ReferralLinkList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReferralLink"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "InviteSummary": {
        "type": "object",
        "description": "An invite without its link (shown once, when the invite is made).",
        "required": [
          "id",
          "max_uses",
          "used_count",
          "expires_at",
          "created_at",
          "state"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "max_uses": {
            "type": "integer"
          },
          "used_count": {
            "type": "integer"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "state": {
            "type": "string",
            "enum": [
              "active",
              "expired",
              "used"
            ]
          }
        }
      },
      "InviteSummaryList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InviteSummary"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "CodeCreate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "code": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 64,
            "description": "3 to 40 letters, digits and hyphens. Left out, the program's suggestion."
          }
        }
      },
      "CodeUpdate": {
        "type": "object",
        "required": [
          "stripe_promotion_code"
        ],
        "additionalProperties": false,
        "properties": {
          "stripe_promotion_code": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255,
            "description": "`promo_…` in your own Stripe, or null to forget it."
          }
        }
      },
      "CommissionApproveRequest": {
        "type": "object",
        "required": [
          "reason"
        ],
        "additionalProperties": false,
        "properties": {
          "reason": {
            "type": "string",
            "maxLength": 500,
            "description": "Why it is paid before its hold ends. Kept in the activity log."
          }
        }
      },
      "PayoutCancel": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "cancelled",
              "failed"
            ],
            "description": "`cancelled` (the default), or `failed` when you tried to pay it and the money did not arrive."
          }
        }
      },
      "LevelRecalculation": {
        "type": "object",
        "required": [
          "moved"
        ],
        "properties": {
          "moved": {
            "type": "integer",
            "description": "Partners moved to another level."
          }
        }
      },
      "WaitlistSettings": {
        "type": "object",
        "required": [
          "bump",
          "page",
          "pitch",
          "closed",
          "page_url",
          "ended_at"
        ],
        "properties": {
          "bump": {
            "type": "integer",
            "description": "Places a confirmed friend moves their referrer up."
          },
          "page": {
            "type": "boolean",
            "description": "Whether the hosted waitlist page is on."
          },
          "pitch": {
            "type": [
              "string",
              "null"
            ],
            "description": "The hosted page's few lines about the product."
          },
          "closed": {
            "type": "boolean",
            "description": "No new entries; everybody keeps their place."
          },
          "page_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "The hosted page's address. It is unguessable and noindex; POST /referrals/programs/{id}/waitlist/rotate-page makes a new one."
          },
          "ended_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "WaitlistFields": {
        "type": [
          "object",
          "null"
        ],
        "additionalProperties": false,
        "description": "A waitlist program's settings, merged onto the current ones.",
        "properties": {
          "bump": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000,
            "description": "Places a confirmed friend moves their referrer up (default 5)."
          },
          "page": {
            "type": "boolean",
            "description": "Switch the hosted page at `/r/w/<token>` on (default false)."
          },
          "pitch": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "closed": {
            "type": "boolean"
          }
        }
      },
      "WaitlistEntryStatus": {
        "type": "string",
        "enum": [
          "waiting",
          "invited",
          "joined"
        ]
      },
      "WaitlistEntry": {
        "type": "object",
        "required": [
          "id",
          "external_id",
          "name",
          "code",
          "link_url",
          "source",
          "status",
          "position",
          "friends",
          "verified",
          "disqualified",
          "disqualify_reason",
          "flags",
          "joined_at",
          "invited_at",
          "referred_by"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "external_id": {
            "type": "string",
            "description": "Your own id for the person; the hosted page makes one, `wl_…`."
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "code": {
            "type": [
              "string",
              "null"
            ]
          },
          "link_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Their link to invite friends."
          },
          "source": {
            "type": "string",
            "enum": [
              "api",
              "hosted"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/WaitlistEntryStatus"
          },
          "position": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Their place in line; null until they confirm."
          },
          "friends": {
            "type": "integer",
            "description": "Confirmed friends who count."
          },
          "verified": {
            "type": "boolean"
          },
          "disqualified": {
            "type": "boolean"
          },
          "disqualify_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "flags": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "velocity",
                "self"
              ]
            },
            "description": "`velocity`: one of a flood from one address; `self`: the referrer themselves. Either moves nobody."
          },
          "joined_at": {
            "type": "string",
            "format": "date-time"
          },
          "invited_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "referred_by": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "external_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "WaitlistState": {
        "type": "object",
        "required": [
          "verified",
          "status",
          "disqualified",
          "position",
          "position_text",
          "total",
          "friends",
          "pending_friends",
          "bump",
          "move_up_text",
          "code",
          "link_url",
          "milestones"
        ],
        "properties": {
          "verified": {
            "type": "boolean"
          },
          "status": {
            "$ref": "#/components/schemas/WaitlistEntryStatus"
          },
          "disqualified": {
            "type": "boolean"
          },
          "position": {
            "type": [
              "integer",
              "null"
            ]
          },
          "position_text": {
            "type": [
              "string",
              "null"
            ],
            "description": "\"You are number 1,204\"."
          },
          "total": {
            "type": "integer"
          },
          "friends": {
            "type": "integer"
          },
          "pending_friends": {
            "type": "integer",
            "description": "Friends who joined and have not confirmed yet."
          },
          "bump": {
            "type": "integer"
          },
          "move_up_text": {
            "type": "string"
          },
          "code": {
            "type": [
              "string",
              "null"
            ]
          },
          "link_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "milestones": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "target",
                "current",
                "reached",
                "reward",
                "text"
              ],
              "properties": {
                "target": {
                  "type": "integer"
                },
                "current": {
                  "type": "integer"
                },
                "reached": {
                  "type": "boolean"
                },
                "reward": {
                  "type": "string"
                },
                "text": {
                  "type": "string",
                  "description": "\"2 of 3 friends joined: one more for early access\"."
                }
              }
            }
          }
        }
      },
      "WaitlistEntryDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/WaitlistEntry"
          },
          {
            "type": "object",
            "required": [
              "state"
            ],
            "properties": {
              "state": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/WaitlistState"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          }
        ]
      },
      "WaitlistEntryList": {
        "type": "object",
        "required": [
          "data",
          "next_cursor",
          "total",
          "line"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WaitlistEntry"
            }
          },
          "next_cursor": {
            "$ref": "#/components/schemas/NextCursor"
          },
          "total": {
            "type": "integer",
            "description": "Entries matching the filter."
          },
          "line": {
            "type": "integer",
            "description": "Confirmed entries",
            "the line's length.": null
          }
        }
      },
      "WaitlistEntryUpsert": {
        "type": "object",
        "required": [
          "external_id"
        ],
        "additionalProperties": false,
        "properties": {
          "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "email": {
            "type": "string",
            "maxLength": 320,
            "description": "Kept only as a keyed fingerprint for the self-referral check."
          },
          "code": {
            "type": "string",
            "maxLength": 40,
            "description": "A vanity code for their own link, on creation only."
          },
          "referral_code": {
            "type": "string",
            "maxLength": 40,
            "description": "The code of the link they arrived with."
          },
          "verified": {
            "type": "boolean"
          },
          "status": {
            "$ref": "#/components/schemas/WaitlistEntryStatus"
          }
        }
      },
      "WaitlistEntryUpdate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "verified": {
            "type": "boolean"
          },
          "status": {
            "$ref": "#/components/schemas/WaitlistEntryStatus"
          }
        }
      },
      "WaitlistInvite": {
        "type": "object",
        "required": [
          "entry_ids",
          "subject",
          "message"
        ],
        "additionalProperties": false,
        "properties": {
          "entry_ids": {
            "type": "array",
            "minItems": 1,
            "maxItems": 500,
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "subject": {
            "type": "string",
            "minLength": 1,
            "maxLength": 140,
            "description": "One line."
          },
          "message": {
            "type": "string",
            "maxLength": 6000,
            "description": "The broadcasts' Markdown subset: paragraphs, **bold**, *italic*, lists and https links."
          }
        }
      },
      "WaitlistInvited": {
        "type": "object",
        "required": [
          "invited",
          "emailed",
          "entry_ids"
        ],
        "properties": {
          "invited": {
            "type": "integer"
          },
          "emailed": {
            "type": "integer",
            "description": "Entries from the hosted page",
            "emailed by BoltClaw.": null
          },
          "entry_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        }
      },
      "WaitlistEnded": {
        "type": "object",
        "required": [
          "forgotten",
          "ended_at"
        ],
        "properties": {
          "forgotten": {
            "type": "integer",
            "description": "How many entries' addresses and links were deleted by this call."
          },
          "ended_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the waitlist ended (the first end",
            "if it had ended before).": null
          }
        }
      },
      "WaitlistPageAddress": {
        "type": "object",
        "required": [
          "page_url"
        ],
        "properties": {
          "page_url": {
            "type": "string",
            "description": "The hosted page's new address. It is unguessable and noindex."
          }
        }
      },
      "SecondTier": {
        "type": "object",
        "required": [
          "percent_bps",
          "months",
          "scope"
        ],
        "properties": {
          "percent_bps": {
            "type": "integer",
            "minimum": 1,
            "maximum": 2000,
            "description": "The recruiter's share of a recruit's own commission, in basis points (2000 is 20%)."
          },
          "months": {
            "type": "integer",
            "minimum": 1,
            "maximum": 24,
            "description": "How many months after the recruit joined."
          },
          "scope": {
            "type": "string",
            "enum": [
              "cash"
            ],
            "description": "Cash commissions on sales only."
          }
        }
      },
      "SecondTierSettings": {
        "type": "object",
        "required": [
          "program_id",
          "second_tier",
          "rule",
          "terms_version",
          "terms_required_version",
          "clause",
          "fence"
        ],
        "properties": {
          "program_id": {
            "type": "string",
            "format": "uuid"
          },
          "second_tier": {
            "description": "Null while it is off.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/SecondTier"
              },
              {
                "type": "null"
              }
            ]
          },
          "rule": {
            "type": [
              "string",
              "null"
            ],
            "description": "The rule in one sentence",
            "as partners read it.": null
          },
          "terms_version": {
            "type": "integer"
          },
          "terms_required_version": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The terms version partners must accept before their next payout."
          },
          "clause": {
            "type": "string",
            "description": "The paragraph the terms carry while it is on."
          },
          "fence": {
            "type": "string",
            "description": "Why it is limited the way it is."
          }
        }
      },
      "SecondTierUpdate": {
        "type": "object",
        "required": [
          "enabled"
        ],
        "additionalProperties": false,
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "percent_bps": {
            "type": "integer",
            "minimum": 1,
            "maximum": 2000
          },
          "months": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 24
          }
        }
      },
      "StatsBucket": {
        "type": "object",
        "required": [
          "start",
          "clicks",
          "untracked_clicks",
          "leads",
          "sales",
          "revenue_minor",
          "commission_minor",
          "conversion_rate",
          "epc_minor"
        ],
        "properties": {
          "start": {
            "type": "string",
            "format": "date",
            "description": "The bucket's first day (UTC; weeks start on Monday)."
          },
          "clicks": {
            "type": "integer"
          },
          "untracked_clicks": {
            "type": [
              "integer",
              "null"
            ]
          },
          "leads": {
            "type": "integer"
          },
          "sales": {
            "type": "integer"
          },
          "revenue_minor": {
            "type": "integer"
          },
          "commission_minor": {
            "type": "integer"
          },
          "conversion_rate": {
            "type": [
              "number",
              "null"
            ]
          },
          "epc_minor": {
            "type": [
              "number",
              "null"
            ]
          }
        }
      },
      "ProgramStats": {
        "type": "object",
        "required": [
          "currency",
          "from",
          "to",
          "bucket",
          "totals",
          "untracked_clicks",
          "untracked_by_month",
          "series",
          "partners"
        ],
        "properties": {
          "currency": {
            "type": "string"
          },
          "from": {
            "type": "string",
            "format": "date-time"
          },
          "to": {
            "type": "string",
            "format": "date-time"
          },
          "bucket": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "day",
              "week",
              "month",
              null
            ]
          },
          "totals": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PartnerStats"
              },
              {
                "type": "object",
                "required": [
                  "refunded_minor",
                  "other_currency_sales"
                ],
                "properties": {
                  "refunded_minor": {
                    "type": "integer"
                  },
                  "other_currency_sales": {
                    "type": "integer"
                  }
                }
              }
            ]
          },
          "untracked_clicks": {
            "type": "integer"
          },
          "untracked_by_month": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "month",
                "clicks"
              ],
              "properties": {
                "month": {
                  "type": "string"
                },
                "clicks": {
                  "type": "integer"
                }
              }
            }
          },
          "series": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/StatsBucket"
            }
          },
          "partners": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/PartnerStats"
                },
                {
                  "type": "object",
                  "required": [
                    "id",
                    "display_name"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "display_name": {
                      "type": "string"
                    }
                  }
                }
              ]
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "`VALIDATION_ERROR`: a body that is not JSON, a field that breaks its rule (named in\n`fields`), a bad query value or cursor, or a malformed `Idempotency-Key`.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "`UNAUTHORIZED`: no key, or one that is malformed, unknown or revoked. It never says which.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "PlanLimit": {
        "description": "`PLAN_LIMIT`: the organisation's plan does not allow more of this. The message names the allowance.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Forbidden": {
        "description": "`INSUFFICIENT_SCOPE`: the key lacks this operation's scope. Or `ORGANISATION_SUSPENDED`:\nthe key's organisation is suspended. Never about another workspace's object, which is 404.\n",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "ApprovalRequired": {
        "description": "`APPROVAL_REQUIRED` on the first try, `APPROVAL_PENDING` on a retry before a person has\ndecided. NOTHING HAS HAPPENED either way. The body carries `approval` with a `url`: show\nit to the person, and once they approve, send the identical request again with the\napproval id in `BoltClaw-Approval`. Only an AI assistant's request is ever answered this\nway.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "NotFound": {
        "description": "`NOT_FOUND`: no such object, or it belongs to another workspace. The two are deliberately\nthe same answer and never 403. If the id is right, check you are using the right key.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Conflict": {
        "description": "`CONFLICT`: the request clashes with the object's current state (the message says how).\n`IDEMPOTENCY_KEY_CONFLICT` / `IDEMPOTENCY_KEY_IN_FLIGHT`: the `Idempotency-Key` was used\nwith another request, or its first request is still running.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "`PAYLOAD_TOO_LARGE`: the body is over 64 KB.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Unprocessable": {
        "description": "`VALIDATION_ERROR`: the request is well formed but cannot be done as asked, such as a\nnetwork that does not support it. Or `UPSTREAM_ERROR`: the network refused it.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "RateLimited": {
        "description": "`RATE_LIMITED`: too many requests. Wait for `Retry-After` seconds.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "InternalError": {
        "description": "`INTERNAL_ERROR`: ours. Safe to retry; send the same `Idempotency-Key` on a POST.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Upstream": {
        "description": "`UPSTREAM_ERROR`: the social network failed the call. The message says what happened.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "UpstreamUnavailable": {
        "description": "`UPSTREAM_ERROR`: the social network is temporarily unavailable; retrying later may work.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    }
  }
}
