{
  "openapi": "3.1.0",
  "info": {
    "title": "TODAQ Micro Payment API",
    "version": "4.0.0",
    "summary": "Micropayments between providers and micropayers.",
    "description": "The TODAQ Micro Payment API moves value between **providers** (who sell\nsomething) and **micropayers** (who buy it), one small transaction at a time.\n\nVersion 4 is the long-term-support API. It combines the account and twin\nprimitives of v2 with the persona, delegation and paywall model of v3, and adds\nthe MCP tool registry used for agent-driven commerce. New integrations should\nbuild against v4; v2 and v3 remain routed for existing consumers but are no\nlonger extended.\n\n## Core concepts\n\n**Twin** — a digital wallet and merchant account in one. A twin is identified\nby a hostname such as `00000000000000000000000000000000.micro.biz.todaq.net`\nand authenticated with a key. It holds balances of digital quantities and owns\nthe commodities it sells.\n\n**Digital Quantity (DQ)** — the unit of value. A DQ is identified by the hash of\nits defining JSON schema, so `412964f2…` denotes one specific currency-like\nquantity. Balances on a twin are always expressed per DQ.\n\n**Commodity** — something a provider sells, priced in a DQ and owned by a twin.\nPaying for a commodity moves the DQ from the payer's twin to the owner's twin.\n\n**Persona** — a user's identity within the system. A persona can **delegate**\nspending authority to another party, which is what lets an agent or an embedded\nexperience pay on a user's behalf without holding their credentials.\n\n**Paywall** — the orchestration endpoint. A single `POST /v4/paywall` call\nresolves the caller, checks their authorization path, moves the DQ and proxies\nthe request through to the upstream service being paid for.\n\n## Authentication\n\nMost endpoints authenticate with a bearer token:\n\n```http\nAuthorization: Bearer <access_token>\n```\n\nTokens are issued by `POST /v4/account/oauth/token` for **account** principals\n(a provider integrating the API) and by `POST /v4/user/oauth/token` for **user**\nprincipals (an individual micropayer). Which principal a token carries decides\nwhat it may reach: account tokens manage twins, commodities and tool\nregistrations, while user tokens act on that user's personas and payments.\n\nSome endpoints instead accept a `public_secret` in the request body. This is the\npublishable credential used by browser-side embeds, and it only ever grants the\nnarrow access that embed needs.\n\nRequests also carry a session, taken from the `session_id` cookie or an\n`X-Session-ID` header. The API issues one if neither is present.\n\n## Errors\n\nErrors return a JSON body with a human-readable `error` and the HTTP status:\n\n```json\n{ \"error\": \"Authentication required\", \"status\": 401 }\n```\n\n`400` means the request was malformed or missing a parameter, `401` that\nauthentication was absent or expired, `403` that the principal is authenticated\nbut not permitted, `404` that the addressed resource does not exist, and `502`\nthat an upstream twin node or payment provider failed.\n\n## Environments\n\n| Environment | Base URL |\n| --- | --- |\n| Production | `https://pay.m.todaq.net` |\n| Staging | `https://pay.stage.m.todaq.net` |\n| Local | `http://localhost:8500` |",
    "license": {
      "name": "Proprietary",
      "url": "https://todaq.net"
    },
    "contact": {
      "name": "TODAQ Micro",
      "url": "https://docs.m.todaq.net"
    }
  },
  "servers": [
    {
      "url": "https://pay.m.todaq.net",
      "description": "Production"
    },
    {
      "url": "https://pay.stage.m.todaq.net",
      "description": "Staging"
    },
    {
      "url": "http://localhost:8500",
      "description": "Local development"
    }
  ],
  "components": {
    "schemas": {
      "Account": {
        "type": "object",
        "description": "A provider integrating the API.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Balances": {
        "type": "object",
        "description": "Balance per digital quantity, keyed by DQ hash. Amounts are in the smallest unit of that quantity.",
        "additionalProperties": {
          "type": "number"
        },
        "examples": [
          {
            "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73": 1250
          }
        ]
      },
      "DigitalQuantityHash": {
        "type": "string",
        "description": "Identifies a digital quantity by the SHA-256 hash of its defining schema.",
        "example": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73"
      },
      "DispatchParam": {
        "type": "object",
        "description": "A header, query parameter or URL value payment adds when it calls a catalog item's `external_endpoint` to fulfil a purchase. Its value is stored encrypted.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Identifies the parameter. Send it back to keep the parameter."
          },
          "kind": {
            "type": "string",
            "enum": [
              "header",
              "query",
              "url"
            ],
            "description": "An HTTP header; a query parameter added to the endpoint URL; or a URL value, which is sent only where `external_endpoint` says `{secret.NAME}`, percent-encoded."
          },
          "name": {
            "type": "string",
            "description": "The header or query parameter name, or for a URL value the NAME in `{secret.NAME}`: 1-64 letters, digits, `_` or `-`, compared exactly."
          },
          "sensitive": {
            "type": "boolean",
            "description": "Whether the value is a secret. A sensitive value is never returned once saved."
          },
          "value": {
            "type": [
              "string",
              "null"
            ],
            "description": "The value, when the parameter is not sensitive. Always `null` for a sensitive one."
          },
          "value_set": {
            "type": "boolean",
            "description": "Whether a usable value is stored. `false` means the stored value can no longer be read on this server; fulfilment for the item fails until it is entered again."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the parameter last changed."
          }
        },
        "required": [
          "id",
          "kind",
          "name",
          "sensitive",
          "value",
          "value_set",
          "updated_at"
        ]
      },
      "Error": {
        "type": "object",
        "description": "The error shape returned for every non-2xx response.",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable description of what went wrong."
          },
          "status": {
            "type": "integer",
            "description": "The HTTP status code, repeated in the body."
          }
        },
        "required": [
          "error"
        ],
        "examples": [
          {
            "error": "Authentication required",
            "status": 401
          }
        ]
      },
      "Twin": {
        "type": "object",
        "description": "A digital wallet and merchant account.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "hostname": {
            "type": "string",
            "description": "Unique hostname identifying the twin.",
            "example": "00000000000000000000000000000000.micro.biz.todaq.net"
          },
          "key": {
            "type": "string",
            "format": "uuid",
            "description": "Secret key authenticating operations on the twin."
          },
          "balances": {
            "$ref": "#/components/schemas/Balances"
          }
        },
        "required": [
          "id",
          "hostname"
        ]
      },
      "User": {
        "type": "object",
        "description": "An individual micropayer.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "An access token from `POST /v4/account/oauth/token` (account principal) or `POST /v4/user/oauth/token` (user principal). The principal the token carries determines which resources it can reach."
      },
      "basicAuth": {
        "type": "http",
        "scheme": "basic",
        "description": "Client credentials, as `client_id:client_secret` base64-encoded. Used only to obtain tokens from the OAuth token endpoints."
      },
      "sessionId": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Session-ID",
        "description": "Identifies the browsing session. Sent automatically as the `session_id` cookie by browser clients; server-side callers can pass this header instead."
      }
    }
  },
  "tags": [
    {
      "name": "Accounts",
      "description": "An account is a provider integrating the API. It holds the client credentials used to obtain tokens, and owns the twins that get paid."
    },
    {
      "name": "Billing",
      "description": "What an account pays for its own use of the platform: the plan it is on, the card on file, and its invoices. This is separate from the payments your users make to you.\n\nBilling runs on Stripe. Card details never pass through this API: render [Stripe Elements](https://docs.stripe.com/payments/elements) with the publishable key from `GET /v4/billing/plans`, confirm the SetupIntent this API creates for the account, and send the resulting payment method id (`pm_...`) when choosing a paid plan or replacing the card."
    },
    {
      "name": "Authentication",
      "description": "Second-factor authentication. A request is raised against a user, pushed to their device, and polled until they approve or reject it."
    },
    {
      "name": "Users",
      "description": "Users are the individual micropayers. A user has an email identity, one or more twins to pay from, and personas that represent them."
    },
    {
      "name": "Sessions",
      "description": "A session is a short-lived token standing in for a bearer token, so that browser-side code can act as the user without holding their credentials."
    },
    {
      "name": "Twins",
      "description": "A twin is a digital wallet and merchant account in one. It holds balances of digital quantities, owns the commodities it sells, and is addressed by its hostname."
    },
    {
      "name": "Digital Quantities",
      "description": "A digital quantity is the unit of value twins hold balances of. Each is identified by the hash of the JSON schema that defines it."
    },
    {
      "name": "Commodities",
      "description": "A commodity is something a provider sells: a price in a digital quantity, attached to the twin that gets paid for it."
    },
    {
      "name": "Catalog",
      "description": "A catalog item is something you sell to agents and MCP clients. Each item is backed by a commodity, which holds its name, description and price; payment for the item is received by the commodity's twin. A commodity has at most one catalog item, and the item adds what the buyer is paying for: the endpoint called to fulfil a purchase and the arguments it accepts."
    },
    {
      "name": "Checkout",
      "description": "A checkout is a single-use link for buying one catalog item. Create it from your server with your access token and send the shopper to the `url` it returns: the hosted checkout page signs them in with an emailed code, lets them top up a wallet by card if they need to, and completes the purchase. Each checkout can be paid once, and completing it is idempotent, so a retry never charges the shopper twice."
    },
    {
      "name": "Carts",
      "description": "A cart holds several catalog items, from one merchant or many, for a shopper to pay for at once. Make one with your access token, add items to it, then check it out and send the shopper to its `url`: the hosted checkout page signs them in, lets them top up a wallet by card once for the whole cart, and pays every item. Each item is still its own purchase, recorded and delivered to its merchant as a checkout link's would be.\n\nOnly the token that made a cart can read or change it."
    },
    {
      "name": "Payments",
      "description": "Buying a commodity. A payment moves the commodity’s cost from the payer’s twin to the seller’s twin and returns a nonce the seller can validate."
    },
    {
      "name": "Paywall",
      "description": "The paywall is the single call that turns a request for a paid resource into a completed micropayment. It resolves who is paying, obtains their authorization, moves the digital quantity and proxies the request through to the service being paid for — the caller gets back whatever that service returned."
    },
    {
      "name": "Transfers",
      "description": "Moving a digital quantity directly between wallets, rather than in payment for something. A transfer is sent either to another twin or to an email address, in which case the recipient is invited to claim it."
    },
    {
      "name": "Agent approvals",
      "description": "An AI agent connected to the user's account asks before it spends; the user answers in the TODAQ app or at a link. Only an approved request lets the agent pay, once, for exactly what was approved."
    },
    {
      "name": "Agent accounts",
      "description": "The business account an AI agent connected to the user may read. The user grants one when they sign the agent in, and can choose or change it later at a link the agent hands them, without reconnecting."
    },
    {
      "name": "Personas",
      "description": "A persona is a user’s identity within the system, anchored to a twin. Personas are what get verified and what grant delegated spending authority to agents and embedded experiences."
    },
    {
      "name": "Delegates",
      "description": "A delegate is spending authority a persona has handed to another party, so an agent or an embedded experience can pay on the user’s behalf without ever holding their credentials."
    },
    {
      "name": "Notifications",
      "description": "Push notifications are how the system reaches a user’s device to ask for approval of a payment. A device registers its FCM token here; the paywall and preauthorization flows then use it to prompt."
    },
    {
      "name": "Embeds",
      "description": "Embeds are the browser-side surfaces of the payment system. Each returns an HTML document meant to be loaded in an iframe — the snippet returned alongside a commodity does that for you — and each takes query parameters that style it to match the host page."
    },
    {
      "name": "MCP Tools",
      "description": "Managed MCP servers expose your catalog to AI agents and MCP clients: featured catalog items become callable tools, and the rest can be found and bought through catalog search. These endpoints attach a hosted agent to a server and report how its tools are being used."
    },
    {
      "name": "Events",
      "description": "The event log records what happened and who did it — sign-ins, transfers, second factors, emails. Clients can add their own entries so that browser-side and device-side steps appear in the same audit trail as server-side ones."
    },
    {
      "name": "OIDC",
      "description": "An OpenID Connect provider sits in front of the API, issuing the OAuth tokens that agents and MCP clients authenticate with. These are the browser-facing endpoints of that flow; the standard provider endpoints — `/v4/oidc/token`, `/v4/oidc/jwks`, `/v4/oidc/token/introspection`, registration and the discovery document at `/v4/oidc/.well-known/openid-configuration` — are served directly by the provider and behave exactly as the specifications describe."
    },
    {
      "name": "Meta",
      "description": "Machine-readable description of the API itself."
    }
  ],
  "x-tagGroups": [
    {
      "name": "Identity",
      "tags": [
        "Accounts",
        "Billing",
        "Authentication",
        "Users",
        "Sessions",
        "Personas",
        "Delegates",
        "OIDC",
        "Agent accounts"
      ]
    },
    {
      "name": "Value",
      "tags": [
        "Twins",
        "Digital Quantities",
        "Commodities",
        "Catalog"
      ]
    },
    {
      "name": "Money movement",
      "tags": [
        "Checkout",
        "Carts",
        "Payments",
        "Paywall",
        "Transfers",
        "Agent approvals"
      ]
    },
    {
      "name": "Integration",
      "tags": [
        "Embeds",
        "MCP Tools",
        "Notifications",
        "Events"
      ]
    },
    {
      "name": "Operations",
      "tags": [
        "Meta"
      ]
    }
  ],
  "paths": {
    "/v4/account": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "summary": "Get your account",
        "description": "Returns the account behind the bearer token, along with its secrets: the publishable `public_secret` used by browser-side embeds, and the `client_id`/`client_secret` pair used to obtain tokens. Keep the latter on your server.",
        "operationId": "getV4Account",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The account and its secrets.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Account"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "secrets": {
                          "type": "array",
                          "description": "Two entries: the publishable secret, then the client credentials.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "public_secret": {
                                "type": "string",
                                "description": "Safe to expose in a browser."
                              },
                              "client_id": {
                                "type": "string"
                              },
                              "client_secret": {
                                "type": "string",
                                "description": "Server-side only."
                              },
                              "created_at": {
                                "type": "integer"
                              },
                              "updated_at": {
                                "type": "integer"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The token does not identify an account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Accounts"
        ],
        "summary": "Create an account",
        "description": "Provisions a new account and its initial secrets. The response is the only time the client secret is returned, so store it when you receive it.",
        "operationId": "postV4Account",
        "security": [],
        "responses": {
          "201": {
            "description": "The account was created.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Account"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "secrets": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Account creation is not permitted for this caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The account could not be created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Accounts"
        ],
        "summary": "Update your account",
        "description": "Sets the account's `name` and `businessName`. Buyers see the business name (or, without one, the name) as the seller of the account's catalog items. An account made through the API has no users, so this is how it names itself; a user updates an account they belong to with `PUT /v4/user/accounts/{id}`. Fields left out are unchanged; `null` clears one.",
        "operationId": "putV4Account",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "businessName": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "example": {
                "businessName": "Northwind Traders Ltd"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The account after the update.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "businessName": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "integer"
                    },
                    "updated_at": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Neither field was given, or one is not a string or `null`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not identify an account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/account/oauth/token": {
      "post": {
        "tags": [
          "Accounts"
        ],
        "summary": "Get or refresh an access token",
        "description": "Issues the tokens every other authenticated call depends on. There are two ways in, chosen by the `Authorization` header:\n\n**Client credentials** — send `Basic <base64(client_id:client_secret)>`. An access token with at least five minutes left is returned as-is rather than being replaced, so calling this repeatedly is cheap. New access tokens last an hour, and are refused once `expires_at` has passed.\n\n**Refresh** — send `Bearer <refresh_token>`. A fresh access token (one hour) and refresh token (24 hours) are issued and the refresh token you presented is destroyed, so each one works exactly once. Store the new pair immediately.\n\nThe endpoint is mounted at both `/v4/account/oauth/token` and `/v4/user/oauth/token`; which principal you get depends on the credentials you present, not on the path.",
        "operationId": "postV4AccountOauthToken",
        "security": [
          {
            "basicAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The token pair.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "access_token": {
                      "type": "string",
                      "example": "tqmt_9f2c…"
                    },
                    "expires_at": {
                      "type": "integer",
                      "description": "Unix time the access token expires."
                    },
                    "refresh_token": {
                      "type": "string"
                    },
                    "refresh_expires_at": {
                      "type": "integer",
                      "description": "Unix time the refresh token expires."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization` header, unrecognised or revoked client credentials, or an invalid, expired or revoked refresh token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The `Authorization` header is neither Basic nor Bearer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/account/register": {
      "post": {
        "tags": [
          "Accounts"
        ],
        "summary": "Register an account, twin and user",
        "description": "The one-call way to get started: provisions an account, a primary twin transacting in `dq`, and a user for `email`, all in a single transaction. If any step fails nothing is created.\n\nThe response carries the account’s client credentials. Fetch the twin key with them from `GET /v4/twins`; no user credentials are returned — the user signs in by code.",
        "operationId": "postV4AccountRegister",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "dq": {
                    "$ref": "#/components/schemas/DigitalQuantityHash",
                    "description": "The digital quantity the twin will transact in."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "The address the user is created against."
                  }
                },
                "required": [
                  "dq",
                  "email"
                ]
              },
              "example": {
                "dq": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73",
                "email": "ada@example.com"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The account, twin and user were created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account": {
                      "$ref": "#/components/schemas/Account"
                    },
                    "twin": {
                      "$ref": "#/components/schemas/Twin"
                    },
                    "user": {
                      "$ref": "#/components/schemas/User"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`dq` or `email` was not supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A user with this email already exists; they sign in instead. Nothing was created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Provisioning failed; nothing was created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/agent/account": {
      "get": {
        "tags": [
          "Agent accounts"
        ],
        "summary": "The account this connection reads",
        "description": "The business account the user granted this agent's connection, as it stands now: a choice made at an account-choice link applies at once. `choosable` says whether the user can choose one at all: they belong to at least one account, and the connection is one a choice can be kept for.",
        "operationId": "getV4AgentAccount",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The account, or null for none.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "name": {
                          "type": "string",
                          "nullable": true
                        }
                      },
                      "nullable": true
                    },
                    "choosable": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not an agent's OIDC token for a user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/agent/account-changes": {
      "post": {
        "tags": [
          "Agent approvals"
        ],
        "summary": "Make an approved change to the business account",
        "description": "Makes the change an `account_change` approval was given for, once, as the user: the one stored with the approval and shown to the user, never one the caller sends. Called with the token of an agent the user granted that business account at sign-in.\n\n`update_item` changes an item's name, description, receipt line (`descriptor`), price, method, endpoint or inputs, activates or deactivates it, archives or restores it (a restored item comes back inactive), keeps it private to the account's MCP server or lists it publicly, or features it on the account's MCP server. `create_item` makes a new item, with its own wallet, as the dashboard does; it can take a minute. `rename_wallet` renames one of the account's wallets. `update_account` changes the account's name, business name, business address, billing email or tax number, never its plan. A `reboot_server` or `update_server` is made by mcp-manager, not here.\n\nA change that fails before anything was made leaves the approval unspent, to try again. One that fails part-way keeps what was made (`changed: true`), as the dashboard does.",
        "operationId": "postV4AgentAccountChanges",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "approval_id": {
                    "type": "string"
                  }
                },
                "required": [
                  "approval_id"
                ]
              },
              "example": {
                "approval_id": "aar_6f1c2b9e-3d4a-4b8f-9a2e-1c0d5e7f8a90"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Made.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "approval_id": {
                      "type": "string"
                    },
                    "change": {
                      "type": "object"
                    },
                    "result": {
                      "type": "object",
                      "description": "What was made: `item` (never its endpoint) and `commodity` for an item, `twin` for a wallet."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The change can no longer be made as approved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not the OIDC token of an app the user connected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`account_not_granted`: the agent was granted no account, or another. `approval_required`: the approval is pending, declined, expired or used (`state` says which).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No account change approval of this user's by that id, or nothing it names is the account's now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Not a change made here (a reboot or update), or one that conflicts with the account as it is now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "A twin refused the change.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/agent/account-changes/spend": {
      "post": {
        "tags": [
          "Agent approvals"
        ],
        "summary": "Spend an approved account change made elsewhere",
        "description": "For a change another service makes: a `reboot_server` or `update_server`, which mcp-manager makes with the agent's token. Spends the approval once, only for exactly the `change` given, which must be the one approved, and only while the server is still the account's and running (and, for an update, still behind the latest release).",
        "operationId": "postV4AgentAccountChangesSpend",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "approval_id": {
                    "type": "string"
                  },
                  "change": {
                    "type": "object"
                  }
                },
                "required": [
                  "approval_id",
                  "change"
                ]
              },
              "example": {
                "approval_id": "aar_6f1c2b9e-3d4a-4b8f-9a2e-1c0d5e7f8a90",
                "change": {
                  "action": "reboot_server",
                  "server_id": 12
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Spent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "approval": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not a change made elsewhere.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not the OIDC token of an app the user connected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not this account's, not approved, or not the change approved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No account change approval by that id, or no such server of the account's.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The server is not running, or (for an update) already runs the latest release.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/agent/account-choices": {
      "post": {
        "tags": [
          "Agent accounts"
        ],
        "summary": "Ask the user to choose the account this connection reads",
        "description": "Raised with the agent's OIDC token. `choose_url` is a page where the user signs in with their own credentials and picks one of their business accounts; the connection reads that one from then on, for as long as it lasts, until they choose again. The link can be answered once, within fifteen minutes. Nothing changes until the user answers it.",
        "operationId": "postV4AgentAccountChoices",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Raised.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "choice": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Names the request: `acr_` and a UUID."
                        },
                        "state": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "chosen",
                            "expired"
                          ],
                          "description": "A `pending` request past `expires_at`, or whose connection is gone (the user disconnected the app), is `expired`. It can be chosen once."
                        },
                        "chosen_account_id": {
                          "type": "integer",
                          "nullable": true
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "choose_url": {
                          "type": "string",
                          "description": "A page where the user signs in and chooses."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not an agent's OIDC token for a user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`no_accounts`: the user belongs to no business account. `account_choice_unavailable`: the token is not one a choice can be kept for; the user reconnects instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/agent/account-choices/{id}": {
      "get": {
        "tags": [
          "Agent accounts"
        ],
        "summary": "An account-choice request, and the accounts to choose from",
        "description": "For the choice page. First-party credentials only: the user's client credentials (Basic) or a token from `POST /v4/user/oauth/token`, never an OIDC access token. Answers only the user the request was raised for.",
        "operationId": "getV4AgentAccountChoicesById",
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The request, the app that asks, the account the connection reads now, and the user's accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "choice": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Names the request: `acr_` and a UUID."
                        },
                        "state": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "chosen",
                            "expired"
                          ],
                          "description": "A `pending` request past `expires_at`, or whose connection is gone (the user disconnected the app), is `expired`. It can be chosen once."
                        },
                        "chosen_account_id": {
                          "type": "integer",
                          "nullable": true
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "choose_url": {
                          "type": "string",
                          "description": "A page where the user signs in and chooses."
                        },
                        "current_account_id": {
                          "type": "integer",
                          "nullable": true
                        }
                      }
                    },
                    "client": {
                      "type": "object",
                      "description": "The app whose connection this is: the name it registered under (its own claim) and the site its sign-in returned to.",
                      "properties": {
                        "name": {
                          "type": "string",
                          "nullable": true
                        },
                        "site": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    },
                    "accounts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "name": {
                            "type": "string",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No first-party credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The user has no request with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The request: `acr_` and a UUID.",
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "post": {
        "tags": [
          "Agent accounts"
        ],
        "summary": "Choose the account",
        "description": "The user's answer from the choice page, with the same credentials as GET. The agent's connection reads this account from now on.",
        "operationId": "postV4AgentAccountChoicesById",
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "account_id": {
                    "type": "integer"
                  }
                },
                "required": [
                  "account_id"
                ]
              },
              "example": {
                "account_id": 42
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "choice": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Names the request: `acr_` and a UUID."
                        },
                        "state": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "chosen",
                            "expired"
                          ],
                          "description": "A `pending` request past `expires_at`, or whose connection is gone (the user disconnected the app), is `expired`. It can be chosen once."
                        },
                        "chosen_account_id": {
                          "type": "integer",
                          "nullable": true
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "choose_url": {
                          "type": "string",
                          "description": "A page where the user signs in and chooses."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`account_id` is missing, or `account_not_found`: not one of the user's.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No first-party credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The user has no request with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already chosen, or expired (lapsed, or the app was disconnected). `choice` says which.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "choice": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Names the request: `acr_` and a UUID."
                        },
                        "state": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "chosen",
                            "expired"
                          ],
                          "description": "A `pending` request past `expires_at`, or whose connection is gone (the user disconnected the app), is `expired`. It can be chosen once."
                        },
                        "chosen_account_id": {
                          "type": "integer",
                          "nullable": true
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "choose_url": {
                          "type": "string",
                          "description": "A page where the user signs in and chooses."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The request: `acr_` and a UUID.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/agent/account-choices/{id}/choose": {
      "get": {
        "tags": [
          "Agent accounts"
        ],
        "summary": "The account-choice page",
        "description": "An HTML page, the `choose_url` of a request: the user signs in with a passkey or an emailed code and picks one of their accounts. It shows nothing about the request until they have signed in as its user.",
        "operationId": "getV4AgentAccountChoicesByIdChoose",
        "security": [],
        "responses": {
          "200": {
            "description": "The page."
          },
          "404": {
            "description": "Not a request id."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The request: `acr_` and a UUID.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/agent/approvals": {
      "post": {
        "tags": [
          "Agent approvals"
        ],
        "summary": "Ask the user to approve a purchase, a cart, a top-up or an account change",
        "description": "Raised with the OIDC token of an app the user connected, such as an MCP client.\n\nA **purchase** names a catalog item and the arguments it will be bought with; the arguments are checked against the item first, and nothing is raised for ones it cannot take. A purchase priced at or under the user's auto-approve limit is approved at once (`decided_via: auto`). A free item needs no approval.\n\nA **cart** is paying for what is left of one of the user's checked-out carts (`cart_id`) from their agent wallet, at the prices it was checked out at (see `POST /v4/carts/{id}/pay`). It is for the cart as it is now: one checked out again, or re-priced by a seller, needs a new approval. Approved at once when its total is within the user's auto-approve limit.\n\nA **top-up** moves `amount` from the user's primary wallet into their agent wallet. It always asks.\n\nAn **account change** is one `change` to the business account the user granted the agent when they signed it in: `update_item`, `create_item`, `rename_wallet`, `update_account`, `reboot_server` or `update_server` (see `POST /v4/agent/account-changes`). It is checked against that account and described in `label`, always asks, and is answered at `approve_url` only: the TODAQ app does not show account changes yet.\n\nOtherwise the user is sent a push to the TODAQ app, and `approve_url` is a page where they can sign in and answer instead. The first answer stands. A request waits fifteen minutes; an approval is good for fifteen minutes and one use.",
        "operationId": "postV4AgentApprovals",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "purchase",
                      "top_up",
                      "account_change",
                      "cart"
                    ]
                  },
                  "catalog_item_id": {
                    "type": "integer",
                    "description": "The item, for a purchase."
                  },
                  "cart_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The cart, for a cart: its `id`."
                  },
                  "change": {
                    "type": "object",
                    "description": "The change, for an account change: `{ action, ... }`."
                  },
                  "arguments": {
                    "type": "object",
                    "description": "The item's inputs, for a purchase, as they will be sent."
                  },
                  "amount": {
                    "type": "number",
                    "description": "How much to move, for a top-up."
                  },
                  "dq": {
                    "$ref": "#/components/schemas/DigitalQuantityHash",
                    "description": "The quantity to move, for a top-up. Omit when only one is configured."
                  }
                },
                "required": [
                  "kind"
                ]
              },
              "example": {
                "kind": "purchase",
                "catalog_item_id": 42,
                "arguments": {
                  "latitude": 43.65
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The item, or what is left of the cart, is free: nothing needs approving.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "required": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Raised, and approved already if the purchase is under the auto-approve limit.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "approval": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Names the request: `aar_` and a UUID. Pass it as `approval_id` to spend it."
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "purchase",
                            "top_up",
                            "account_change",
                            "cart"
                          ]
                        },
                        "state": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "approved",
                            "denied",
                            "expired",
                            "used"
                          ],
                          "description": "`approved` can be spent once, before `expires_at`; `used` has been. A `pending` request past `expires_at` is `expired`."
                        },
                        "catalog_item_id": {
                          "type": "integer",
                          "nullable": true
                        },
                        "amount": {
                          "type": "string",
                          "nullable": true,
                          "description": "What will be paid or moved, to three decimals; null for an account change."
                        },
                        "dq": {
                          "$ref": "#/components/schemas/DigitalQuantityHash",
                          "nullable": true
                        },
                        "label": {
                          "type": "string",
                          "description": "What the user is shown: the item, the top-up, or the account change in words."
                        },
                        "merchant": {
                          "type": "string",
                          "nullable": true,
                          "description": "The seller; for an account change, the account."
                        },
                        "account_id": {
                          "type": "integer",
                          "description": "An account change's account."
                        },
                        "change": {
                          "type": "object",
                          "description": "An account change, as it will be made."
                        },
                        "cart": {
                          "type": "object",
                          "description": "A cart approval's cart, as the user is shown it: `id`, `revision`, and what is left to pay (`lines` with `name`, `merchant`, `quantity`, `unit_price`, `subtotal`; `item_count`; `total`)."
                        },
                        "decided_via": {
                          "type": "string",
                          "enum": [
                            "app",
                            "link",
                            "auto"
                          ],
                          "nullable": true
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "approve_url": {
                          "type": "string",
                          "description": "A page where the user can sign in and answer."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request, or the purchase's arguments, cannot be approved as given.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not the OIDC token of an app the user connected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "An account change from an agent granted no account (`account_not_granted`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such catalog item, nothing of the account's by that id, or no cart of the user's by that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A top-up for a user with no agent wallet, an account change that cannot be made now, or a cart that is not checked out (`cart_not_ready`), is paid or finished, is being paid at its link (`cart_taken`), or is being paid under an approval already (`approval_held`, naming it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/agent/approvals/{id}": {
      "get": {
        "tags": [
          "Agent approvals"
        ],
        "summary": "Where an approval request stands",
        "description": "Answers for the user the request belongs to. With `wait`, a pending request is held open for up to that many seconds (at most 25) and answered as soon as the user decides.",
        "operationId": "getV4AgentApprovalsById",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "basicAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The request.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "approval": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Names the request: `aar_` and a UUID. Pass it as `approval_id` to spend it."
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "purchase",
                            "top_up",
                            "account_change",
                            "cart"
                          ]
                        },
                        "state": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "approved",
                            "denied",
                            "expired",
                            "used"
                          ],
                          "description": "`approved` can be spent once, before `expires_at`; `used` has been. A `pending` request past `expires_at` is `expired`."
                        },
                        "catalog_item_id": {
                          "type": "integer",
                          "nullable": true
                        },
                        "amount": {
                          "type": "string",
                          "nullable": true,
                          "description": "What will be paid or moved, to three decimals; null for an account change."
                        },
                        "dq": {
                          "$ref": "#/components/schemas/DigitalQuantityHash",
                          "nullable": true
                        },
                        "label": {
                          "type": "string",
                          "description": "What the user is shown: the item, the top-up, or the account change in words."
                        },
                        "merchant": {
                          "type": "string",
                          "nullable": true,
                          "description": "The seller; for an account change, the account."
                        },
                        "account_id": {
                          "type": "integer",
                          "description": "An account change's account."
                        },
                        "change": {
                          "type": "object",
                          "description": "An account change, as it will be made."
                        },
                        "cart": {
                          "type": "object",
                          "description": "A cart approval's cart, as the user is shown it: `id`, `revision`, and what is left to pay (`lines` with `name`, `merchant`, `quantity`, `unit_price`, `subtotal`; `item_count`; `total`)."
                        },
                        "decided_via": {
                          "type": "string",
                          "enum": [
                            "app",
                            "link",
                            "auto"
                          ],
                          "nullable": true
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "approve_url": {
                          "type": "string",
                          "description": "A page where the user can sign in and answer."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in as a user: a bearer token, or the user's client credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The user has no request with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The request: `aar_` and a UUID.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "wait",
            "in": "query",
            "description": "Seconds to wait for an answer, 0 to 25.",
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v4/agent/approvals/{id}/decision": {
      "post": {
        "tags": [
          "Agent approvals"
        ],
        "summary": "Approve or decline an agent's request",
        "description": "The user's answer from the approval page. First-party credentials only: the user's client credentials (Basic) or a token from `POST /v4/user/oauth/token`, never an OIDC access token, so the agent that asked cannot answer. The TODAQ app answers with `PUT /v4/paywall/authorizations` instead. Whichever answers first decides; an approval is good for fifteen minutes and one payment.",
        "operationId": "postV4AgentApprovalsByIdDecision",
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "approved": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "approved"
                ]
              },
              "example": {
                "approved": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "approval": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Names the request: `aar_` and a UUID. Pass it as `approval_id` to spend it."
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "purchase",
                            "top_up",
                            "account_change",
                            "cart"
                          ]
                        },
                        "state": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "approved",
                            "denied",
                            "expired",
                            "used"
                          ],
                          "description": "`approved` can be spent once, before `expires_at`; `used` has been. A `pending` request past `expires_at` is `expired`."
                        },
                        "catalog_item_id": {
                          "type": "integer",
                          "nullable": true
                        },
                        "amount": {
                          "type": "string",
                          "nullable": true,
                          "description": "What will be paid or moved, to three decimals; null for an account change."
                        },
                        "dq": {
                          "$ref": "#/components/schemas/DigitalQuantityHash",
                          "nullable": true
                        },
                        "label": {
                          "type": "string",
                          "description": "What the user is shown: the item, the top-up, or the account change in words."
                        },
                        "merchant": {
                          "type": "string",
                          "nullable": true,
                          "description": "The seller; for an account change, the account."
                        },
                        "account_id": {
                          "type": "integer",
                          "description": "An account change's account."
                        },
                        "change": {
                          "type": "object",
                          "description": "An account change, as it will be made."
                        },
                        "cart": {
                          "type": "object",
                          "description": "A cart approval's cart, as the user is shown it: `id`, `revision`, and what is left to pay (`lines` with `name`, `merchant`, `quantity`, `unit_price`, `subtotal`; `item_count`; `total`)."
                        },
                        "decided_via": {
                          "type": "string",
                          "enum": [
                            "app",
                            "link",
                            "auto"
                          ],
                          "nullable": true
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "approve_url": {
                          "type": "string",
                          "description": "A page where the user can sign in and answer."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`approved` is not a boolean.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No first-party credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The user has no request with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already answered, or expired. `approval` says which.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "approval": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Names the request: `aar_` and a UUID. Pass it as `approval_id` to spend it."
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "purchase",
                            "top_up",
                            "account_change",
                            "cart"
                          ]
                        },
                        "state": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "approved",
                            "denied",
                            "expired",
                            "used"
                          ],
                          "description": "`approved` can be spent once, before `expires_at`; `used` has been. A `pending` request past `expires_at` is `expired`."
                        },
                        "catalog_item_id": {
                          "type": "integer",
                          "nullable": true
                        },
                        "amount": {
                          "type": "string",
                          "nullable": true,
                          "description": "What will be paid or moved, to three decimals; null for an account change."
                        },
                        "dq": {
                          "$ref": "#/components/schemas/DigitalQuantityHash",
                          "nullable": true
                        },
                        "label": {
                          "type": "string",
                          "description": "What the user is shown: the item, the top-up, or the account change in words."
                        },
                        "merchant": {
                          "type": "string",
                          "nullable": true,
                          "description": "The seller; for an account change, the account."
                        },
                        "account_id": {
                          "type": "integer",
                          "description": "An account change's account."
                        },
                        "change": {
                          "type": "object",
                          "description": "An account change, as it will be made."
                        },
                        "cart": {
                          "type": "object",
                          "description": "A cart approval's cart, as the user is shown it: `id`, `revision`, and what is left to pay (`lines` with `name`, `merchant`, `quantity`, `unit_price`, `subtotal`; `item_count`; `total`)."
                        },
                        "decided_via": {
                          "type": "string",
                          "enum": [
                            "app",
                            "link",
                            "auto"
                          ],
                          "nullable": true
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "approve_url": {
                          "type": "string",
                          "description": "A page where the user can sign in and answer."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The request: `aar_` and a UUID.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/agent/approvals/{id}/review": {
      "get": {
        "tags": [
          "Agent approvals"
        ],
        "summary": "The approval page",
        "description": "An HTML page, the `approve_url` of a request: the user signs in with an emailed code, sees what the agent is asking for, and approves or declines. It shows nothing about the request until they have signed in as its user.",
        "operationId": "getV4AgentApprovalsByIdReview",
        "security": [],
        "responses": {
          "200": {
            "description": "The page."
          },
          "404": {
            "description": "Not a request id."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The request: `aar_` and a UUID.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/auth/embed/{hash}": {
      "get": {
        "tags": [
          "Embeds"
        ],
        "summary": "Render a sign-in embed",
        "description": "Returns the HTML for the sign-in iframe, which walks a user through identifying themselves and completing a second factor without the host page ever seeing their credentials.",
        "operationId": "getV4AuthEmbedByHash",
        "security": [],
        "responses": {
          "200": {
            "description": "The embed document.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "`theme` is not `light` or `dark`."
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Hash identifying the embed context.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "theme",
            "in": "query",
            "description": "Colour theme to render with.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/auth/otp": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Email a one-time sign-in code",
        "description": "Starts a passwordless sign-in for a first-party application. A six-digit code is emailed to `email`; the response carries `created_at`, which identifies this attempt and must be sent back as `tonce` to `POST /v4/auth/otp/verify` together with the code the user types.\n\nNo credentials are required. A code is valid for 10 minutes, and requesting a new one invalidates any earlier code for the same address. Asking again within a few seconds of the previous request returns the same `created_at` without sending a second email.\n\n`code_length` says how many digits to ask for: 6 for an emailed code, or 12 for an App or Play review account, which is sent nothing and signs in with its configured code.\n\nRequests are rate limited per caller and per address. When a limit is reached the response is `429` with a `Retry-After` header; wait at least that long before retrying.",
        "operationId": "postV4AuthOtp",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "The address to send the code to. This is the identity being signed in."
                  }
                },
                "required": [
                  "email"
                ]
              },
              "example": {
                "email": "shopper@example.com"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "A code was sent (or a very recent one is still current).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "created_at": {
                      "type": "integer",
                      "description": "Unix time, in seconds, at which the code was issued. Send it back as `tonce` when verifying."
                    },
                    "code_length": {
                      "type": "integer",
                      "enum": [
                        6,
                        12
                      ],
                      "description": "How many digits the code has: 6 for an emailed code, 12 for an App or Play review account. A client sizes its code field from it."
                    }
                  },
                  "required": [
                    "created_at",
                    "code_length"
                  ]
                },
                "example": {
                  "created_at": 1758729600,
                  "code_length": 6
                }
              }
            }
          },
          "400": {
            "description": "`email` is missing or not a valid address, or the code could not be emailed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing": {
                    "summary": "No email",
                    "value": {
                      "error": "email is required",
                      "status": 400
                    }
                  },
                  "invalid": {
                    "summary": "Malformed email",
                    "value": {
                      "error": "Invalid email format",
                      "status": 400
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many codes have been requested. Retry after the interval in `Retry-After`.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before requesting another code.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Too many verification codes requested. Please wait and try again.",
                  "status": 429
                }
              }
            }
          }
        }
      }
    },
    "/v4/auth/otp/verify": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Verify a one-time sign-in code",
        "description": "Exchanges the code emailed by `POST /v4/auth/otp` for the user it identifies. A user is created on first sign-in, so the same call serves sign-up and sign-in.\n\nThe response includes the user's OAuth client credentials. Exchange them for an access token at `POST /v4/user/oauth/token` (HTTP Basic, `client_id:client_secret`) and keep them server-side or in secure storage; treat `client_secret` like a password.\n\nA code can be used once. Wrong, expired and already-used codes are all answered with the same `400`, and a code stops working after repeated wrong attempts; request a new one with `POST /v4/auth/otp`.",
        "operationId": "postV4AuthOtpVerify",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "The address the code was sent to."
                  },
                  "code": {
                    "type": "string",
                    "pattern": "^\\d{6}$",
                    "description": "The six-digit code from the email."
                  },
                  "tonce": {
                    "type": "integer",
                    "description": "The `created_at` value returned when the code was requested."
                  }
                },
                "required": [
                  "email",
                  "code",
                  "tonce"
                ]
              },
              "example": {
                "email": "shopper@example.com",
                "code": "123456",
                "tonce": 1758729600
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The code was accepted. The user, with their OAuth client credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "email": {
                      "type": "string",
                      "format": "email"
                    },
                    "created_at": {
                      "type": "string",
                      "description": "Unix time, in seconds, as a string."
                    },
                    "updated_at": {
                      "type": "string",
                      "description": "Unix time, in seconds, as a string."
                    },
                    "deleted_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "secrets": {
                      "type": "array",
                      "description": "The user's active OAuth client credentials.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "client_id": {
                            "type": "string"
                          },
                          "client_secret": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string",
                            "description": "Unix time, in seconds, as a string."
                          },
                          "updated_at": {
                            "type": "string",
                            "description": "Unix time, in seconds, as a string."
                          },
                          "deleted_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "client_id",
                          "client_secret"
                        ]
                      }
                    },
                    "passkey_offer_token": {
                      "type": "string",
                      "description": "Present when the user should be offered a passkey, so they can skip the code next time. It authorizes `POST /v4/oidc/passkey/register/options`, `…/register/verify` and `POST /v4/oidc/passkey/decline` for this user for 15 minutes. Absent once they have a passkey, and between offers."
                    }
                  },
                  "required": [
                    "id",
                    "email",
                    "secrets"
                  ]
                },
                "example": {
                  "id": 4821,
                  "email": "shopper@example.com",
                  "created_at": "1758729610",
                  "updated_at": "1758729610",
                  "deleted_at": null,
                  "secrets": [
                    {
                      "client_id": "mid_00000000000000000000000000000000",
                      "client_secret": "mc_00000000000000000000000000000000",
                      "created_at": "1758729610",
                      "updated_at": "1758729610",
                      "deleted_at": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A field is missing, or the code is wrong, expired or already used.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing": {
                    "summary": "Missing field",
                    "value": {
                      "error": "email, code and tonce are required",
                      "status": 400
                    }
                  },
                  "invalid": {
                    "summary": "Wrong or used code",
                    "value": {
                      "error": "Invalid verification code",
                      "status": 400
                    }
                  },
                  "expired": {
                    "summary": "Expired code",
                    "value": {
                      "error": "Verification code has expired",
                      "status": 400
                    }
                  },
                  "format": {
                    "summary": "Not six digits",
                    "value": {
                      "error": "Code must be 6 digits",
                      "status": 400
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "The code was accepted but the user account could not be created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Failed to create user account",
                  "status": 500
                }
              }
            }
          }
        }
      }
    },
    "/v4/auth/passkey/options": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Start a first-party passkey sign-in",
        "description": "Returns the options to pass to `navigator.credentials.get()` (as `PublicKeyCredentialRequestOptionsJSON`), with a single-use challenge that expires after ten minutes and no account: the browser offers whichever of the user's passkeys it has. Send the result to `POST /v4/auth/passkey/verify`.",
        "operationId": "postV4AuthPasskeyOptions",
        "security": [],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "WebAuthn request options.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "challenge": {
                      "type": "string"
                    },
                    "rpId": {
                      "type": "string"
                    },
                    "timeout": {
                      "type": "integer"
                    },
                    "userVerification": {
                      "type": "string",
                      "enum": [
                        "required"
                      ]
                    }
                  },
                  "required": [
                    "challenge",
                    "rpId"
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Passkeys are not configured on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "A machine-readable code."
                    },
                    "error_description": {
                      "type": "string",
                      "description": "A message that can be shown to the user."
                    }
                  },
                  "required": [
                    "error",
                    "error_description"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v4/auth/passkey/verify": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Finish a first-party passkey sign-in",
        "description": "Verifies what `navigator.credentials.get()` returned (as `AuthenticationResponseJSON`) and answers as `POST /v4/auth/otp/verify` does for a correct code: the user and their OAuth client credentials. The user must have verified themselves on the authenticator (biometric or screen lock).\n\nEach challenge works once. `challenge_expired` means start again with `POST /v4/auth/passkey/options`; `unknown_credential` means the passkey is not, or no longer, registered, and the page may tell the authenticator to forget it.",
        "operationId": "postV4AuthPasskeyVerify",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "response": {
                    "type": "object",
                    "description": "The credential, as `AuthenticationResponseJSON`."
                  }
                },
                "required": [
                  "response"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in. The user, with their OAuth client credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "email": {
                      "type": "string",
                      "format": "email"
                    },
                    "secrets": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  },
                  "required": [
                    "id",
                    "email",
                    "secrets"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`response` is missing, the challenge is spent or expired (`challenge_expired`), the passkey is not registered (`unknown_credential`), or it did not verify (`verification_failed`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "A machine-readable code."
                    },
                    "error_description": {
                      "type": "string",
                      "description": "A message that can be shown to the user."
                    }
                  },
                  "required": [
                    "error",
                    "error_description"
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Passkeys are not configured on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "A machine-readable code."
                    },
                    "error_description": {
                      "type": "string",
                      "description": "A message that can be shown to the user."
                    }
                  },
                  "required": [
                    "error",
                    "error_description"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v4/billing/plans": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "List plans",
        "description": "Returns the plan catalog, cheapest first, and the Stripe publishable key to initialise Stripe Elements with. Any valid bearer token will do, user or account.",
        "operationId": "getV4BillingPlans",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The plans and the publishable key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "plans": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "enum": [
                              "starter",
                              "pro",
                              "team",
                              "enterprise"
                            ],
                            "description": "Pass this to change plan. `starter` is the free Builder plan."
                          },
                          "name": {
                            "type": "string",
                            "description": "Display name."
                          },
                          "price_cents": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "Monthly price in US cents. `0` for the free plan; `null` when the plan is priced by arrangement."
                          },
                          "interval": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "enum": [
                              "month",
                              null
                            ],
                            "description": "Billing interval; `null` for plans without a subscription."
                          },
                          "blurb": {
                            "type": "string",
                            "description": "One-line pitch."
                          },
                          "features": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "self_serve": {
                            "type": "boolean",
                            "description": "Whether an account can move itself onto this plan. `false` means contact sales."
                          }
                        }
                      }
                    },
                    "publishable_key": {
                      "type": "string",
                      "description": "Stripe publishable key (`pk_...`). Safe to expose in a browser."
                    }
                  },
                  "required": [
                    "plans",
                    "publishable_key"
                  ]
                },
                "example": {
                  "plans": [
                    {
                      "id": "starter",
                      "name": "Builder",
                      "price_cents": 0,
                      "interval": null,
                      "blurb": "Put one endpoint behind a paywall and get paid.",
                      "features": [
                        "1 agent",
                        "Up to 5 catalog items",
                        "Up to 25 transactions / month",
                        "Account API keys"
                      ],
                      "self_serve": true
                    },
                    {
                      "id": "pro",
                      "name": "Pro",
                      "price_cents": 2000,
                      "interval": "month",
                      "blurb": "A real catalog, several agents, twin credentials.",
                      "features": [
                        "3 agents",
                        "Up to 50 catalog items",
                        "Up to 250 transactions / month",
                        "Twin URLs and API keys"
                      ],
                      "self_serve": true
                    },
                    {
                      "id": "enterprise",
                      "name": "Enterprise",
                      "price_cents": null,
                      "interval": null,
                      "blurb": "Total control. Run Qatom on-premises or in your own VPC.",
                      "features": [
                        "Unlimited agents, items, transactions",
                        "Issue your own assets",
                        "VPC, on-premises or air-gapped",
                        "You hold compute, storage and keys"
                      ],
                      "self_serve": false
                    }
                  ],
                  "publishable_key": "pk_test_51ExamplePublishableKey"
                }
              }
            }
          },
          "401": {
            "description": "No valid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "status": 401
                }
              }
            }
          }
        }
      }
    },
    "/v4/carts": {
      "post": {
        "tags": [
          "Carts"
        ],
        "summary": "Create a cart",
        "description": "Makes a cart, empty or with `lines`. Every line is checked as it would be by `POST /v4/carts/{id}/lines`, and the cart is made with all of them or not at all.",
        "operationId": "postV4Carts",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lines": {
                    "type": "array",
                    "maxItems": 25,
                    "items": {
                      "type": "object",
                      "properties": {
                        "catalog_item_id": {
                          "type": "integer",
                          "minimum": 1,
                          "description": "The catalog item. It must be active and priced."
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 20,
                          "default": 1,
                          "description": "How many. Each one is a separate purchase: a separate payment and a separate call to the merchant."
                        },
                        "arguments": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "The item's inputs (its `service_schema`), as for a checkout link. Every required input must be present; keys the schema does not declare are dropped; a value of the wrong type is refused. Adding the same item with the same inputs adds to that line's quantity."
                        }
                      },
                      "required": [
                        "catalog_item_id"
                      ]
                    }
                  }
                }
              },
              "example": {
                "lines": [
                  {
                    "catalog_item_id": 128,
                    "quantity": 2,
                    "arguments": {
                      "postcode": "M5V 2T6"
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The cart was made.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "cart": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Names the cart here and in its checkout link."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "open",
                            "checkout",
                            "completed",
                            "closed",
                            "abandoned"
                          ],
                          "description": "`open`: lines can change. `checkout`: locked for the shopper, who can pay at `url`. `completed`: every item is paid for. `closed`: finished with part of it paid; the rest was cancelled. `abandoned`: discarded."
                        },
                        "revision": {
                          "type": "integer",
                          "description": "Bumped every time the cart is checked out, and when a seller's price change re-prices it. A payment is for one revision: the shopper confirms the one they pay, and an agent's approval is for one."
                        },
                        "payer": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "link",
                            "agent",
                            null
                          ],
                          "description": "Who is paying for it, once someone has started: a shopper at its link, or the user's agent."
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer",
                                "description": "Names the line in this cart."
                              },
                              "catalog_item_id": {
                                "type": "integer"
                              },
                              "name": {
                                "type": "string"
                              },
                              "description": {
                                "type": "string"
                              },
                              "merchant": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Who sells it."
                              },
                              "unit_price": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The price of one, to three decimals: today's while the cart is open, the one it was checked out at once it is."
                              },
                              "quantity": {
                                "type": "integer"
                              },
                              "subtotal": {
                                "type": "string"
                              },
                              "arguments": {
                                "type": "object",
                                "additionalProperties": true
                              },
                              "available": {
                                "type": "boolean",
                                "description": "False when the item has been withdrawn from sale since it was added."
                              },
                              "units_paid": {
                                "type": "integer",
                                "description": "Once checked out: how many of this line the shopper has paid for."
                              }
                            }
                          }
                        },
                        "item_count": {
                          "type": "integer",
                          "description": "Every unit in the cart, all lines together."
                        },
                        "total": {
                          "type": "string",
                          "description": "What the cart costs at today's prices, to three decimals."
                        },
                        "currency": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "dq": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The digital quantity every line is priced in."
                        },
                        "url": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uri",
                          "description": "The checkout page for this cart, once checked out. Send the shopper here. It carries the cart's own random hash, not its `id`, and is the same every time the cart is checked out."
                        },
                        "units_total": {
                          "type": "integer",
                          "description": "Once checked out: the units to pay."
                        },
                        "units_paid": {
                          "type": "integer",
                          "description": "Once checked out: the units paid."
                        },
                        "amount_paid": {
                          "type": "string",
                          "description": "Once checked out: what has been paid."
                        },
                        "outstanding": {
                          "type": "string",
                          "description": "Once checked out: what is left to pay. Zero once closed."
                        },
                        "approval_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The approval the user's agent is paying it under."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "checked_out_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "completed_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "id",
                        "status",
                        "lines",
                        "item_count",
                        "total",
                        "url"
                      ]
                    },
                    "reopened": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Present when this change reopened a checked-out cart. Its link shows the cart as not ready until it is checked out again; the link itself does not change."
                    }
                  },
                  "required": [
                    "cart"
                  ]
                },
                "example": {
                  "cart": {
                    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
                    "status": "open",
                    "revision": 0,
                    "payer": null,
                    "lines": [
                      {
                        "id": 41,
                        "catalog_item_id": 128,
                        "name": "Carbonara kit",
                        "description": "Everything for carbonara, serves 4.",
                        "merchant": "La Dispensa",
                        "unit_price": "24.000",
                        "quantity": 2,
                        "subtotal": "48.000",
                        "arguments": {
                          "postcode": "M5V 2T6"
                        },
                        "available": true
                      }
                    ],
                    "item_count": 2,
                    "total": "48.000",
                    "currency": "USD-TDN",
                    "dq": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73",
                    "url": null,
                    "approval_id": null,
                    "created_at": "2026-10-08T14:03:11.482Z",
                    "updated_at": "2026-10-08T14:05:40.019Z",
                    "checked_out_at": null,
                    "completed_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "The line cannot be added: the item is unknown, inactive or unpriced, the inputs do not fit it, it is priced in another currency than the cart, or a limit would be passed (25 lines, 20 of one line, 50 units in all). The error names the line by `line_index`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingArguments": {
                    "summary": "A required input is missing",
                    "value": {
                      "error": "Missing required details: postcode",
                      "code": "invalid_arguments",
                      "status": 400,
                      "catalog_item_id": 128,
                      "missing_arguments": [
                        "postcode"
                      ]
                    }
                  },
                  "inactive": {
                    "summary": "Unknown or inactive item",
                    "value": {
                      "error": "Catalog item not found or inactive",
                      "code": "item_unavailable",
                      "status": 400,
                      "catalog_item_id": 128
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "No bearer token was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Forbidden",
                  "status": 403
                }
              }
            }
          }
        }
      }
    },
    "/v4/carts/{id}": {
      "delete": {
        "tags": [
          "Carts"
        ],
        "summary": "Discard a cart",
        "description": "Discards the cart; its link stops working. Refused once a shopper has started paying at its link: finishing it is theirs.\n\nA cart the user's agent has paid part of (`POST /v4/carts/{id}/pay`) is closed instead: what was bought stays bought, the rest is cancelled, and the receipt for what was bought is emailed to the user.",
        "operationId": "deleteV4CartsById",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The cart, now `abandoned`, or `closed` if part of it was paid. Discarding one twice is not an error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "cart": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Names the cart here and in its checkout link."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "open",
                            "checkout",
                            "completed",
                            "closed",
                            "abandoned"
                          ],
                          "description": "`open`: lines can change. `checkout`: locked for the shopper, who can pay at `url`. `completed`: every item is paid for. `closed`: finished with part of it paid; the rest was cancelled. `abandoned`: discarded."
                        },
                        "revision": {
                          "type": "integer",
                          "description": "Bumped every time the cart is checked out, and when a seller's price change re-prices it. A payment is for one revision: the shopper confirms the one they pay, and an agent's approval is for one."
                        },
                        "payer": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "link",
                            "agent",
                            null
                          ],
                          "description": "Who is paying for it, once someone has started: a shopper at its link, or the user's agent."
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer",
                                "description": "Names the line in this cart."
                              },
                              "catalog_item_id": {
                                "type": "integer"
                              },
                              "name": {
                                "type": "string"
                              },
                              "description": {
                                "type": "string"
                              },
                              "merchant": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Who sells it."
                              },
                              "unit_price": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The price of one, to three decimals: today's while the cart is open, the one it was checked out at once it is."
                              },
                              "quantity": {
                                "type": "integer"
                              },
                              "subtotal": {
                                "type": "string"
                              },
                              "arguments": {
                                "type": "object",
                                "additionalProperties": true
                              },
                              "available": {
                                "type": "boolean",
                                "description": "False when the item has been withdrawn from sale since it was added."
                              },
                              "units_paid": {
                                "type": "integer",
                                "description": "Once checked out: how many of this line the shopper has paid for."
                              }
                            }
                          }
                        },
                        "item_count": {
                          "type": "integer",
                          "description": "Every unit in the cart, all lines together."
                        },
                        "total": {
                          "type": "string",
                          "description": "What the cart costs at today's prices, to three decimals."
                        },
                        "currency": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "dq": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The digital quantity every line is priced in."
                        },
                        "url": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uri",
                          "description": "The checkout page for this cart, once checked out. Send the shopper here. It carries the cart's own random hash, not its `id`, and is the same every time the cart is checked out."
                        },
                        "units_total": {
                          "type": "integer",
                          "description": "Once checked out: the units to pay."
                        },
                        "units_paid": {
                          "type": "integer",
                          "description": "Once checked out: the units paid."
                        },
                        "amount_paid": {
                          "type": "string",
                          "description": "Once checked out: what has been paid."
                        },
                        "outstanding": {
                          "type": "string",
                          "description": "Once checked out: what is left to pay. Zero once closed."
                        },
                        "approval_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The approval the user's agent is paying it under."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "checked_out_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "completed_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "id",
                        "status",
                        "lines",
                        "item_count",
                        "total",
                        "url"
                      ]
                    },
                    "reopened": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Present when this change reopened a checked-out cart. Its link shows the cart as not ready until it is checked out again; the link itself does not change."
                    }
                  },
                  "required": [
                    "cart"
                  ]
                },
                "example": {
                  "cart": {
                    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
                    "status": "abandoned",
                    "revision": 0,
                    "payer": null,
                    "lines": [
                      {
                        "id": 41,
                        "catalog_item_id": 128,
                        "name": "Carbonara kit",
                        "description": "Everything for carbonara, serves 4.",
                        "merchant": "La Dispensa",
                        "unit_price": "24.000",
                        "quantity": 2,
                        "subtotal": "48.000",
                        "arguments": {
                          "postcode": "M5V 2T6"
                        },
                        "available": true
                      }
                    ],
                    "item_count": 2,
                    "total": "48.000",
                    "currency": "USD-TDN",
                    "dq": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73",
                    "url": null,
                    "approval_id": null,
                    "created_at": "2026-10-08T14:03:11.482Z",
                    "updated_at": "2026-10-08T14:05:40.019Z",
                    "checked_out_at": null,
                    "completed_at": null
                  }
                }
              }
            }
          },
          "403": {
            "description": "No bearer token was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Forbidden",
                  "status": 403
                }
              }
            }
          },
          "404": {
            "description": "No cart of the caller's has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Cart not found",
                  "code": "cart_not_found",
                  "status": 404
                }
              }
            }
          },
          "409": {
            "description": "The cart cannot change: the shopper has started paying (`payment_started`), it is paid (`cart_completed`), or it was discarded (`cart_abandoned`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The shopper has started paying for this cart, so it can no longer change.",
                  "code": "payment_started",
                  "status": 409
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The cart's id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
          }
        ]
      },
      "get": {
        "tags": [
          "Carts"
        ],
        "summary": "Get a cart",
        "description": "The cart with every line, its inputs and the total at today's prices. Once checked out, `url` is the shopper's link and `units_paid` says how much of it they have paid for; `status` is `completed` when all of it is.",
        "operationId": "getV4CartsById",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The cart.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "cart": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Names the cart here and in its checkout link."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "open",
                            "checkout",
                            "completed",
                            "closed",
                            "abandoned"
                          ],
                          "description": "`open`: lines can change. `checkout`: locked for the shopper, who can pay at `url`. `completed`: every item is paid for. `closed`: finished with part of it paid; the rest was cancelled. `abandoned`: discarded."
                        },
                        "revision": {
                          "type": "integer",
                          "description": "Bumped every time the cart is checked out, and when a seller's price change re-prices it. A payment is for one revision: the shopper confirms the one they pay, and an agent's approval is for one."
                        },
                        "payer": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "link",
                            "agent",
                            null
                          ],
                          "description": "Who is paying for it, once someone has started: a shopper at its link, or the user's agent."
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer",
                                "description": "Names the line in this cart."
                              },
                              "catalog_item_id": {
                                "type": "integer"
                              },
                              "name": {
                                "type": "string"
                              },
                              "description": {
                                "type": "string"
                              },
                              "merchant": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Who sells it."
                              },
                              "unit_price": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The price of one, to three decimals: today's while the cart is open, the one it was checked out at once it is."
                              },
                              "quantity": {
                                "type": "integer"
                              },
                              "subtotal": {
                                "type": "string"
                              },
                              "arguments": {
                                "type": "object",
                                "additionalProperties": true
                              },
                              "available": {
                                "type": "boolean",
                                "description": "False when the item has been withdrawn from sale since it was added."
                              },
                              "units_paid": {
                                "type": "integer",
                                "description": "Once checked out: how many of this line the shopper has paid for."
                              }
                            }
                          }
                        },
                        "item_count": {
                          "type": "integer",
                          "description": "Every unit in the cart, all lines together."
                        },
                        "total": {
                          "type": "string",
                          "description": "What the cart costs at today's prices, to three decimals."
                        },
                        "currency": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "dq": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The digital quantity every line is priced in."
                        },
                        "url": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uri",
                          "description": "The checkout page for this cart, once checked out. Send the shopper here. It carries the cart's own random hash, not its `id`, and is the same every time the cart is checked out."
                        },
                        "units_total": {
                          "type": "integer",
                          "description": "Once checked out: the units to pay."
                        },
                        "units_paid": {
                          "type": "integer",
                          "description": "Once checked out: the units paid."
                        },
                        "amount_paid": {
                          "type": "string",
                          "description": "Once checked out: what has been paid."
                        },
                        "outstanding": {
                          "type": "string",
                          "description": "Once checked out: what is left to pay. Zero once closed."
                        },
                        "approval_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The approval the user's agent is paying it under."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "checked_out_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "completed_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "id",
                        "status",
                        "lines",
                        "item_count",
                        "total",
                        "url"
                      ]
                    },
                    "reopened": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Present when this change reopened a checked-out cart. Its link shows the cart as not ready until it is checked out again; the link itself does not change."
                    }
                  },
                  "required": [
                    "cart"
                  ]
                },
                "example": {
                  "cart": {
                    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
                    "status": "open",
                    "revision": 0,
                    "payer": null,
                    "lines": [
                      {
                        "id": 41,
                        "catalog_item_id": 128,
                        "name": "Carbonara kit",
                        "description": "Everything for carbonara, serves 4.",
                        "merchant": "La Dispensa",
                        "unit_price": "24.000",
                        "quantity": 2,
                        "subtotal": "48.000",
                        "arguments": {
                          "postcode": "M5V 2T6"
                        },
                        "available": true
                      }
                    ],
                    "item_count": 2,
                    "total": "48.000",
                    "currency": "USD-TDN",
                    "dq": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73",
                    "url": null,
                    "approval_id": null,
                    "created_at": "2026-10-08T14:03:11.482Z",
                    "updated_at": "2026-10-08T14:05:40.019Z",
                    "checked_out_at": null,
                    "completed_at": null
                  }
                }
              }
            }
          },
          "403": {
            "description": "No bearer token was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Forbidden",
                  "status": 403
                }
              }
            }
          },
          "404": {
            "description": "No cart of the caller's has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Cart not found",
                  "code": "cart_not_found",
                  "status": 404
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The cart's id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
          }
        ]
      }
    },
    "/v4/carts/{id}/checkout": {
      "post": {
        "tags": [
          "Carts"
        ],
        "summary": "Check out a cart",
        "description": "Locks the cart for payment and returns the hosted checkout page for it. Send the shopper to `url`; nothing is charged until they pay there.\n\nEvery line is checked again: the item must still be on sale and priced, and its inputs must still fit it. Checking out a cart that is already checked out returns the same link.",
        "operationId": "postV4CartsByIdCheckout",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The cart is checked out.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "cart": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Names the cart here and in its checkout link."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "open",
                            "checkout",
                            "completed",
                            "closed",
                            "abandoned"
                          ],
                          "description": "`open`: lines can change. `checkout`: locked for the shopper, who can pay at `url`. `completed`: every item is paid for. `closed`: finished with part of it paid; the rest was cancelled. `abandoned`: discarded."
                        },
                        "revision": {
                          "type": "integer",
                          "description": "Bumped every time the cart is checked out, and when a seller's price change re-prices it. A payment is for one revision: the shopper confirms the one they pay, and an agent's approval is for one."
                        },
                        "payer": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "link",
                            "agent",
                            null
                          ],
                          "description": "Who is paying for it, once someone has started: a shopper at its link, or the user's agent."
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer",
                                "description": "Names the line in this cart."
                              },
                              "catalog_item_id": {
                                "type": "integer"
                              },
                              "name": {
                                "type": "string"
                              },
                              "description": {
                                "type": "string"
                              },
                              "merchant": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Who sells it."
                              },
                              "unit_price": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The price of one, to three decimals: today's while the cart is open, the one it was checked out at once it is."
                              },
                              "quantity": {
                                "type": "integer"
                              },
                              "subtotal": {
                                "type": "string"
                              },
                              "arguments": {
                                "type": "object",
                                "additionalProperties": true
                              },
                              "available": {
                                "type": "boolean",
                                "description": "False when the item has been withdrawn from sale since it was added."
                              },
                              "units_paid": {
                                "type": "integer",
                                "description": "Once checked out: how many of this line the shopper has paid for."
                              }
                            }
                          }
                        },
                        "item_count": {
                          "type": "integer",
                          "description": "Every unit in the cart, all lines together."
                        },
                        "total": {
                          "type": "string",
                          "description": "What the cart costs at today's prices, to three decimals."
                        },
                        "currency": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "dq": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The digital quantity every line is priced in."
                        },
                        "url": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uri",
                          "description": "The checkout page for this cart, once checked out. Send the shopper here. It carries the cart's own random hash, not its `id`, and is the same every time the cart is checked out."
                        },
                        "units_total": {
                          "type": "integer",
                          "description": "Once checked out: the units to pay."
                        },
                        "units_paid": {
                          "type": "integer",
                          "description": "Once checked out: the units paid."
                        },
                        "amount_paid": {
                          "type": "string",
                          "description": "Once checked out: what has been paid."
                        },
                        "outstanding": {
                          "type": "string",
                          "description": "Once checked out: what is left to pay. Zero once closed."
                        },
                        "approval_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The approval the user's agent is paying it under."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "checked_out_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "completed_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "id",
                        "status",
                        "lines",
                        "item_count",
                        "total",
                        "url"
                      ]
                    },
                    "reopened": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Present when this change reopened a checked-out cart. Its link shows the cart as not ready until it is checked out again; the link itself does not change."
                    },
                    "url": {
                      "type": "string",
                      "format": "uri",
                      "description": "The checkout page for this cart."
                    }
                  },
                  "required": [
                    "cart"
                  ]
                },
                "example": {
                  "cart": {
                    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
                    "status": "checkout",
                    "revision": 0,
                    "payer": null,
                    "lines": [
                      {
                        "id": 41,
                        "catalog_item_id": 128,
                        "name": "Carbonara kit",
                        "description": "Everything for carbonara, serves 4.",
                        "merchant": "La Dispensa",
                        "unit_price": "24.000",
                        "quantity": 2,
                        "subtotal": "48.000",
                        "arguments": {
                          "postcode": "M5V 2T6"
                        },
                        "available": true
                      }
                    ],
                    "item_count": 2,
                    "total": "48.000",
                    "currency": "USD-TDN",
                    "dq": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73",
                    "url": "https://checkout.m.todaq.net/cart/d1f8a3c2-6b4e-4f7a-9c0d-2e5b8a1f3c64",
                    "approval_id": null,
                    "created_at": "2026-10-08T14:03:11.482Z",
                    "updated_at": "2026-10-08T14:05:40.019Z",
                    "checked_out_at": null,
                    "completed_at": null
                  },
                  "url": "https://checkout.m.todaq.net/cart/d1f8a3c2-6b4e-4f7a-9c0d-2e5b8a1f3c64"
                }
              }
            }
          },
          "400": {
            "description": "The cart is empty, or holds more than it may.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Add something to the cart before checking it out",
                  "code": "cart_empty",
                  "status": 400
                }
              }
            }
          },
          "403": {
            "description": "No bearer token was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Forbidden",
                  "status": 403
                }
              }
            }
          },
          "404": {
            "description": "No cart of the caller's has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Cart not found",
                  "code": "cart_not_found",
                  "status": 404
                }
              }
            }
          },
          "409": {
            "description": "A line's item is no longer for sale (`items_unavailable`, naming `unavailable_lines`), a line's inputs no longer fit its item (`invalid_arguments`, naming `invalid_lines`), or the cart is paid or discarded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Some items in this cart are no longer for sale. Remove them and check out again.",
                  "code": "items_unavailable",
                  "status": 409,
                  "unavailable_lines": [
                    41
                  ]
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The cart's id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
          }
        ]
      }
    },
    "/v4/carts/{id}/lines": {
      "post": {
        "tags": [
          "Carts"
        ],
        "summary": "Add an item to a cart",
        "description": "Adds a line. The item must be on sale and priced, its inputs are checked as a checkout link's are, and it must be priced in the same currency as the rest of the cart. The same item with the same inputs adds to that line's quantity instead of making a second line.\n\nChanging a cart that has been checked out reopens it, so the shopper's link shows the cart as it now is once it is checked out again. That is refused once the shopper has started paying.",
        "operationId": "postV4CartsByIdLines",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "catalog_item_id": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "The catalog item. It must be active and priced."
                  },
                  "quantity": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 20,
                    "default": 1,
                    "description": "How many. Each one is a separate purchase: a separate payment and a separate call to the merchant."
                  },
                  "arguments": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "The item's inputs (its `service_schema`), as for a checkout link. Every required input must be present; keys the schema does not declare are dropped; a value of the wrong type is refused. Adding the same item with the same inputs adds to that line's quantity."
                  }
                },
                "required": [
                  "catalog_item_id"
                ]
              },
              "example": {
                "catalog_item_id": 128,
                "quantity": 2,
                "arguments": {
                  "postcode": "M5V 2T6"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The line was added, or its quantity grew.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "cart": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Names the cart here and in its checkout link."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "open",
                            "checkout",
                            "completed",
                            "closed",
                            "abandoned"
                          ],
                          "description": "`open`: lines can change. `checkout`: locked for the shopper, who can pay at `url`. `completed`: every item is paid for. `closed`: finished with part of it paid; the rest was cancelled. `abandoned`: discarded."
                        },
                        "revision": {
                          "type": "integer",
                          "description": "Bumped every time the cart is checked out, and when a seller's price change re-prices it. A payment is for one revision: the shopper confirms the one they pay, and an agent's approval is for one."
                        },
                        "payer": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "link",
                            "agent",
                            null
                          ],
                          "description": "Who is paying for it, once someone has started: a shopper at its link, or the user's agent."
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer",
                                "description": "Names the line in this cart."
                              },
                              "catalog_item_id": {
                                "type": "integer"
                              },
                              "name": {
                                "type": "string"
                              },
                              "description": {
                                "type": "string"
                              },
                              "merchant": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Who sells it."
                              },
                              "unit_price": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The price of one, to three decimals: today's while the cart is open, the one it was checked out at once it is."
                              },
                              "quantity": {
                                "type": "integer"
                              },
                              "subtotal": {
                                "type": "string"
                              },
                              "arguments": {
                                "type": "object",
                                "additionalProperties": true
                              },
                              "available": {
                                "type": "boolean",
                                "description": "False when the item has been withdrawn from sale since it was added."
                              },
                              "units_paid": {
                                "type": "integer",
                                "description": "Once checked out: how many of this line the shopper has paid for."
                              }
                            }
                          }
                        },
                        "item_count": {
                          "type": "integer",
                          "description": "Every unit in the cart, all lines together."
                        },
                        "total": {
                          "type": "string",
                          "description": "What the cart costs at today's prices, to three decimals."
                        },
                        "currency": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "dq": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The digital quantity every line is priced in."
                        },
                        "url": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uri",
                          "description": "The checkout page for this cart, once checked out. Send the shopper here. It carries the cart's own random hash, not its `id`, and is the same every time the cart is checked out."
                        },
                        "units_total": {
                          "type": "integer",
                          "description": "Once checked out: the units to pay."
                        },
                        "units_paid": {
                          "type": "integer",
                          "description": "Once checked out: the units paid."
                        },
                        "amount_paid": {
                          "type": "string",
                          "description": "Once checked out: what has been paid."
                        },
                        "outstanding": {
                          "type": "string",
                          "description": "Once checked out: what is left to pay. Zero once closed."
                        },
                        "approval_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The approval the user's agent is paying it under."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "checked_out_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "completed_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "id",
                        "status",
                        "lines",
                        "item_count",
                        "total",
                        "url"
                      ]
                    },
                    "reopened": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Present when this change reopened a checked-out cart. Its link shows the cart as not ready until it is checked out again; the link itself does not change."
                    },
                    "line_id": {
                      "type": "integer",
                      "description": "The line the item is on."
                    }
                  },
                  "required": [
                    "cart"
                  ]
                },
                "example": {
                  "cart": {
                    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
                    "status": "open",
                    "revision": 0,
                    "payer": null,
                    "lines": [
                      {
                        "id": 41,
                        "catalog_item_id": 128,
                        "name": "Carbonara kit",
                        "description": "Everything for carbonara, serves 4.",
                        "merchant": "La Dispensa",
                        "unit_price": "24.000",
                        "quantity": 2,
                        "subtotal": "48.000",
                        "arguments": {
                          "postcode": "M5V 2T6"
                        },
                        "available": true
                      }
                    ],
                    "item_count": 2,
                    "total": "48.000",
                    "currency": "USD-TDN",
                    "dq": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73",
                    "url": null,
                    "approval_id": null,
                    "created_at": "2026-10-08T14:03:11.482Z",
                    "updated_at": "2026-10-08T14:05:40.019Z",
                    "checked_out_at": null,
                    "completed_at": null
                  },
                  "line_id": 41
                }
              }
            }
          },
          "400": {
            "description": "The line cannot be added: the item is unknown, inactive or unpriced, the inputs do not fit it, it is priced in another currency than the cart, or a limit would be passed (25 lines, 20 of one line, 50 units in all).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingArguments": {
                    "summary": "A required input is missing",
                    "value": {
                      "error": "Missing required details: postcode",
                      "code": "invalid_arguments",
                      "status": 400,
                      "catalog_item_id": 128,
                      "missing_arguments": [
                        "postcode"
                      ]
                    }
                  },
                  "inactive": {
                    "summary": "Unknown or inactive item",
                    "value": {
                      "error": "Catalog item not found or inactive",
                      "code": "item_unavailable",
                      "status": 400,
                      "catalog_item_id": 128
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "No bearer token was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Forbidden",
                  "status": 403
                }
              }
            }
          },
          "404": {
            "description": "No cart of the caller's has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Cart not found",
                  "code": "cart_not_found",
                  "status": 404
                }
              }
            }
          },
          "409": {
            "description": "The cart cannot change: the shopper has started paying (`payment_started`), it is paid (`cart_completed`), or it was discarded (`cart_abandoned`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The shopper has started paying for this cart, so it can no longer change.",
                  "code": "payment_started",
                  "status": 409
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The cart's id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
          }
        ]
      }
    },
    "/v4/carts/{id}/lines/{lineId}": {
      "delete": {
        "tags": [
          "Carts"
        ],
        "summary": "Remove a line",
        "description": "Removes the line.\n\nChanging a cart that has been checked out reopens it, so the shopper's link shows the cart as it now is once it is checked out again. That is refused once the shopper has started paying.",
        "operationId": "deleteV4CartsByIdLinesByLineId",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The cart as it now is.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "cart": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Names the cart here and in its checkout link."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "open",
                            "checkout",
                            "completed",
                            "closed",
                            "abandoned"
                          ],
                          "description": "`open`: lines can change. `checkout`: locked for the shopper, who can pay at `url`. `completed`: every item is paid for. `closed`: finished with part of it paid; the rest was cancelled. `abandoned`: discarded."
                        },
                        "revision": {
                          "type": "integer",
                          "description": "Bumped every time the cart is checked out, and when a seller's price change re-prices it. A payment is for one revision: the shopper confirms the one they pay, and an agent's approval is for one."
                        },
                        "payer": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "link",
                            "agent",
                            null
                          ],
                          "description": "Who is paying for it, once someone has started: a shopper at its link, or the user's agent."
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer",
                                "description": "Names the line in this cart."
                              },
                              "catalog_item_id": {
                                "type": "integer"
                              },
                              "name": {
                                "type": "string"
                              },
                              "description": {
                                "type": "string"
                              },
                              "merchant": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Who sells it."
                              },
                              "unit_price": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The price of one, to three decimals: today's while the cart is open, the one it was checked out at once it is."
                              },
                              "quantity": {
                                "type": "integer"
                              },
                              "subtotal": {
                                "type": "string"
                              },
                              "arguments": {
                                "type": "object",
                                "additionalProperties": true
                              },
                              "available": {
                                "type": "boolean",
                                "description": "False when the item has been withdrawn from sale since it was added."
                              },
                              "units_paid": {
                                "type": "integer",
                                "description": "Once checked out: how many of this line the shopper has paid for."
                              }
                            }
                          }
                        },
                        "item_count": {
                          "type": "integer",
                          "description": "Every unit in the cart, all lines together."
                        },
                        "total": {
                          "type": "string",
                          "description": "What the cart costs at today's prices, to three decimals."
                        },
                        "currency": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "dq": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The digital quantity every line is priced in."
                        },
                        "url": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uri",
                          "description": "The checkout page for this cart, once checked out. Send the shopper here. It carries the cart's own random hash, not its `id`, and is the same every time the cart is checked out."
                        },
                        "units_total": {
                          "type": "integer",
                          "description": "Once checked out: the units to pay."
                        },
                        "units_paid": {
                          "type": "integer",
                          "description": "Once checked out: the units paid."
                        },
                        "amount_paid": {
                          "type": "string",
                          "description": "Once checked out: what has been paid."
                        },
                        "outstanding": {
                          "type": "string",
                          "description": "Once checked out: what is left to pay. Zero once closed."
                        },
                        "approval_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The approval the user's agent is paying it under."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "checked_out_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "completed_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "id",
                        "status",
                        "lines",
                        "item_count",
                        "total",
                        "url"
                      ]
                    },
                    "reopened": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Present when this change reopened a checked-out cart. Its link shows the cart as not ready until it is checked out again; the link itself does not change."
                    }
                  },
                  "required": [
                    "cart"
                  ]
                },
                "example": {
                  "cart": {
                    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
                    "status": "open",
                    "revision": 0,
                    "payer": null,
                    "lines": [],
                    "item_count": 0,
                    "total": "0.000",
                    "currency": "USD-TDN",
                    "dq": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73",
                    "url": null,
                    "approval_id": null,
                    "created_at": "2026-10-08T14:03:11.482Z",
                    "updated_at": "2026-10-08T14:05:40.019Z",
                    "checked_out_at": null,
                    "completed_at": null
                  }
                }
              }
            }
          },
          "403": {
            "description": "No bearer token was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Forbidden",
                  "status": 403
                }
              }
            }
          },
          "404": {
            "description": "No cart of the caller's has that id, or it has no such line.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Cart not found",
                  "code": "cart_not_found",
                  "status": 404
                }
              }
            }
          },
          "409": {
            "description": "The cart cannot change: the shopper has started paying (`payment_started`), it is paid (`cart_completed`), or it was discarded (`cart_abandoned`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The shopper has started paying for this cart, so it can no longer change.",
                  "code": "payment_started",
                  "status": 409
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The cart's id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
          },
          {
            "name": "lineId",
            "in": "path",
            "required": true,
            "description": "The line, as `lines[].id`.",
            "schema": {
              "type": "integer"
            },
            "example": 41
          }
        ]
      },
      "patch": {
        "tags": [
          "Carts"
        ],
        "summary": "Change a line",
        "description": "Sets the line's quantity, replaces its inputs, or both. A quantity of `0` removes the line. New inputs are checked as when it was added.\n\nChanging a cart that has been checked out reopens it, so the shopper's link shows the cart as it now is once it is checked out again. That is refused once the shopper has started paying.",
        "operationId": "patchV4CartsByIdLinesByLineId",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "quantity": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 20
                  },
                  "arguments": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "The item's inputs (its `service_schema`), as for a checkout link. Every required input must be present; keys the schema does not declare are dropped; a value of the wrong type is refused. Adding the same item with the same inputs adds to that line's quantity."
                  }
                }
              },
              "example": {
                "quantity": 3
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The cart as it now is.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "cart": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Names the cart here and in its checkout link."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "open",
                            "checkout",
                            "completed",
                            "closed",
                            "abandoned"
                          ],
                          "description": "`open`: lines can change. `checkout`: locked for the shopper, who can pay at `url`. `completed`: every item is paid for. `closed`: finished with part of it paid; the rest was cancelled. `abandoned`: discarded."
                        },
                        "revision": {
                          "type": "integer",
                          "description": "Bumped every time the cart is checked out, and when a seller's price change re-prices it. A payment is for one revision: the shopper confirms the one they pay, and an agent's approval is for one."
                        },
                        "payer": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "link",
                            "agent",
                            null
                          ],
                          "description": "Who is paying for it, once someone has started: a shopper at its link, or the user's agent."
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer",
                                "description": "Names the line in this cart."
                              },
                              "catalog_item_id": {
                                "type": "integer"
                              },
                              "name": {
                                "type": "string"
                              },
                              "description": {
                                "type": "string"
                              },
                              "merchant": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Who sells it."
                              },
                              "unit_price": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The price of one, to three decimals: today's while the cart is open, the one it was checked out at once it is."
                              },
                              "quantity": {
                                "type": "integer"
                              },
                              "subtotal": {
                                "type": "string"
                              },
                              "arguments": {
                                "type": "object",
                                "additionalProperties": true
                              },
                              "available": {
                                "type": "boolean",
                                "description": "False when the item has been withdrawn from sale since it was added."
                              },
                              "units_paid": {
                                "type": "integer",
                                "description": "Once checked out: how many of this line the shopper has paid for."
                              }
                            }
                          }
                        },
                        "item_count": {
                          "type": "integer",
                          "description": "Every unit in the cart, all lines together."
                        },
                        "total": {
                          "type": "string",
                          "description": "What the cart costs at today's prices, to three decimals."
                        },
                        "currency": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "dq": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The digital quantity every line is priced in."
                        },
                        "url": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uri",
                          "description": "The checkout page for this cart, once checked out. Send the shopper here. It carries the cart's own random hash, not its `id`, and is the same every time the cart is checked out."
                        },
                        "units_total": {
                          "type": "integer",
                          "description": "Once checked out: the units to pay."
                        },
                        "units_paid": {
                          "type": "integer",
                          "description": "Once checked out: the units paid."
                        },
                        "amount_paid": {
                          "type": "string",
                          "description": "Once checked out: what has been paid."
                        },
                        "outstanding": {
                          "type": "string",
                          "description": "Once checked out: what is left to pay. Zero once closed."
                        },
                        "approval_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The approval the user's agent is paying it under."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "checked_out_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "completed_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "id",
                        "status",
                        "lines",
                        "item_count",
                        "total",
                        "url"
                      ]
                    },
                    "reopened": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Present when this change reopened a checked-out cart. Its link shows the cart as not ready until it is checked out again; the link itself does not change."
                    }
                  },
                  "required": [
                    "cart"
                  ]
                },
                "example": {
                  "cart": {
                    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
                    "status": "open",
                    "revision": 0,
                    "payer": null,
                    "lines": [
                      {
                        "id": 41,
                        "catalog_item_id": 128,
                        "name": "Carbonara kit",
                        "description": "Everything for carbonara, serves 4.",
                        "merchant": "La Dispensa",
                        "unit_price": "24.000",
                        "quantity": 2,
                        "subtotal": "48.000",
                        "arguments": {
                          "postcode": "M5V 2T6"
                        },
                        "available": true
                      }
                    ],
                    "item_count": 2,
                    "total": "48.000",
                    "currency": "USD-TDN",
                    "dq": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73",
                    "url": null,
                    "approval_id": null,
                    "created_at": "2026-10-08T14:03:11.482Z",
                    "updated_at": "2026-10-08T14:05:40.019Z",
                    "checked_out_at": null,
                    "completed_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "The line cannot be added: the item is unknown, inactive or unpriced, the inputs do not fit it, it is priced in another currency than the cart, or a limit would be passed (25 lines, 20 of one line, 50 units in all).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingArguments": {
                    "summary": "A required input is missing",
                    "value": {
                      "error": "Missing required details: postcode",
                      "code": "invalid_arguments",
                      "status": 400,
                      "catalog_item_id": 128,
                      "missing_arguments": [
                        "postcode"
                      ]
                    }
                  },
                  "inactive": {
                    "summary": "Unknown or inactive item",
                    "value": {
                      "error": "Catalog item not found or inactive",
                      "code": "item_unavailable",
                      "status": 400,
                      "catalog_item_id": 128
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "No bearer token was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Forbidden",
                  "status": 403
                }
              }
            }
          },
          "404": {
            "description": "No cart of the caller's has that id, or it has no such line.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Cart not found",
                  "code": "cart_not_found",
                  "status": 404
                }
              }
            }
          },
          "409": {
            "description": "The cart cannot change: the shopper has started paying (`payment_started`), it is paid (`cart_completed`), or it was discarded (`cart_abandoned`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The shopper has started paying for this cart, so it can no longer change.",
                  "code": "payment_started",
                  "status": 409
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The cart's id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
          },
          {
            "name": "lineId",
            "in": "path",
            "required": true,
            "description": "The line, as `lines[].id`.",
            "schema": {
              "type": "integer"
            },
            "example": 41
          }
        ]
      }
    },
    "/v4/carts/{id}/pay": {
      "post": {
        "tags": [
          "Carts"
        ],
        "summary": "Pay for a cart from the agent wallet",
        "description": "For an AI agent the user connected: pays for what is left of one of the user's checked-out carts from the user's agent wallet, item by item, as the checkout page would, once the user has approved it. Raise the approval with `POST /v4/agent/approvals` `{ kind: \"cart\", cart_id }` and pass its id here.\n\nThe approval is for the cart as it was when asked. If a seller has changed a price since, nothing is paid: the answer is `409` `cart_changed`, and the user is asked again for the cart as it now is. The first payment spends the approval; the cart then holds it, so call again with the same `approval_id` to carry on.\n\n`outcome` says how far it got: `completed` (everything is paid, and the receipt is emailed), `partial` (this call ran out of time; call again), `processing` (an item's payment is still settling; call again after `Retry-After`), or `stopped` (an item could not be bought, said in `failure`, such as a wallet without enough in it). After `stopped`, call again once it is put right, or close the cart with what is paid (`DELETE /v4/carts/{id}`).\n\nEach item bought is listed in `paid` with what its merchant answered, as `result`. That answer is not stored: it is in this response only.",
        "operationId": "postV4CartsByIdPay",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "approval_id": {
                    "type": "string",
                    "description": "The user's approval of this cart: `aar_` and a UUID."
                  }
                }
              },
              "example": {
                "approval_id": "aar_3f0e9b0c-6a43-4c0e-9d0a-2a8b7d1f4e21"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How far the payment got.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "outcome": {
                      "type": "string",
                      "enum": [
                        "completed",
                        "partial",
                        "processing",
                        "stopped"
                      ]
                    },
                    "paid": {
                      "type": "array",
                      "description": "The items this call bought.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "line_id": {
                            "type": "integer"
                          },
                          "name": {
                            "type": "string"
                          },
                          "transaction_id": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "amount_charged": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "delivery": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "result": {
                            "description": "What the merchant answered, when it was delivered."
                          }
                        }
                      }
                    },
                    "failure": {
                      "type": "object",
                      "description": "When `stopped`: the item that could not be bought, and why.",
                      "properties": {
                        "line_id": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "name": {
                          "type": "string"
                        },
                        "status": {
                          "type": "integer"
                        },
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string"
                        }
                      }
                    },
                    "cart": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Names the cart here and in its checkout link."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "open",
                            "checkout",
                            "completed",
                            "closed",
                            "abandoned"
                          ],
                          "description": "`open`: lines can change. `checkout`: locked for the shopper, who can pay at `url`. `completed`: every item is paid for. `closed`: finished with part of it paid; the rest was cancelled. `abandoned`: discarded."
                        },
                        "revision": {
                          "type": "integer",
                          "description": "Bumped every time the cart is checked out, and when a seller's price change re-prices it. A payment is for one revision: the shopper confirms the one they pay, and an agent's approval is for one."
                        },
                        "payer": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "link",
                            "agent",
                            null
                          ],
                          "description": "Who is paying for it, once someone has started: a shopper at its link, or the user's agent."
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer",
                                "description": "Names the line in this cart."
                              },
                              "catalog_item_id": {
                                "type": "integer"
                              },
                              "name": {
                                "type": "string"
                              },
                              "description": {
                                "type": "string"
                              },
                              "merchant": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Who sells it."
                              },
                              "unit_price": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The price of one, to three decimals: today's while the cart is open, the one it was checked out at once it is."
                              },
                              "quantity": {
                                "type": "integer"
                              },
                              "subtotal": {
                                "type": "string"
                              },
                              "arguments": {
                                "type": "object",
                                "additionalProperties": true
                              },
                              "available": {
                                "type": "boolean",
                                "description": "False when the item has been withdrawn from sale since it was added."
                              },
                              "units_paid": {
                                "type": "integer",
                                "description": "Once checked out: how many of this line the shopper has paid for."
                              }
                            }
                          }
                        },
                        "item_count": {
                          "type": "integer",
                          "description": "Every unit in the cart, all lines together."
                        },
                        "total": {
                          "type": "string",
                          "description": "What the cart costs at today's prices, to three decimals."
                        },
                        "currency": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "dq": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The digital quantity every line is priced in."
                        },
                        "url": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uri",
                          "description": "The checkout page for this cart, once checked out. Send the shopper here. It carries the cart's own random hash, not its `id`, and is the same every time the cart is checked out."
                        },
                        "units_total": {
                          "type": "integer",
                          "description": "Once checked out: the units to pay."
                        },
                        "units_paid": {
                          "type": "integer",
                          "description": "Once checked out: the units paid."
                        },
                        "amount_paid": {
                          "type": "string",
                          "description": "Once checked out: what has been paid."
                        },
                        "outstanding": {
                          "type": "string",
                          "description": "Once checked out: what is left to pay. Zero once closed."
                        },
                        "approval_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The approval the user's agent is paying it under."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "checked_out_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "completed_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "id",
                        "status",
                        "lines",
                        "item_count",
                        "total",
                        "url"
                      ]
                    }
                  },
                  "required": [
                    "outcome",
                    "paid",
                    "cart"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Not the token of an app the user connected, or no approval of the cart as it now is (`approval_required`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No cart of the caller's has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Cart not found",
                  "code": "cart_not_found",
                  "status": 404
                }
              }
            }
          },
          "409": {
            "description": "The cart is not checked out (`cart_not_ready`), is finished, is being paid at its link (`cart_taken`), was re-priced since it was approved (`cart_changed`, with the cart), or is being paid under another approval (`approval_mismatch`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The cart's id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
          }
        ]
      }
    },
    "/v4/catalog/items": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "List catalog items",
        "description": "Lists catalog items, oldest first. Anonymous callers see public items only; an authenticated caller also sees the private items they may manage. Pass `mine=true` to list only the items you may manage, public or private: items on your own twins, or, for a user, on twins of the account they belong to.\n\nResults are paged with `limit` and `offset`; `total` counts every matching item, not just the returned page.\n\n`external_endpoint` is returned only on the items the caller may manage.",
        "operationId": "getV4CatalogItems",
        "security": [],
        "responses": {
          "200": {
            "description": "A page of catalog items.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "description": "A catalog item, joined with the commodity that prices it.",
                        "properties": {
                          "id": {
                            "type": "integer",
                            "description": "Identifies the catalog item."
                          },
                          "commodity_id": {
                            "type": "integer",
                            "description": "The commodity this item sells."
                          },
                          "service_method": {
                            "type": "string",
                            "enum": [
                              "GET",
                              "POST",
                              "PUT"
                            ],
                            "description": "HTTP method used to call `external_endpoint` once a purchase has settled. With `GET` the buyer's arguments are sent as query parameters; with `POST` or `PUT` they are sent as a JSON body. Either way, an argument that fills a `{input.NAME}` placeholder in `external_endpoint` is sent there instead."
                          },
                          "service_schema": {
                            "type": "object",
                            "additionalProperties": true,
                            "description": "JSON Schema describing the arguments a buyer supplies with a purchase. Only arguments declared here are forwarded to `external_endpoint`, and purchases missing a `required` argument are not dispatched. MCP clients also see it as the tool's input schema."
                          },
                          "external_endpoint": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uri-template",
                            "description": "The URL called to fulfil a purchase after payment settles. `null` for an item bought purely for what the payment represents (access, a licence, an entitlement), where nothing needs to be called afterwards. It may carry `{input.NAME}` and `{secret.NAME}` placeholders, filled in when a purchase is dispatched: see the request body of `POST /v4/catalog/items`. Returned only to callers who may manage the item; for anyone else, anonymous callers included, the key is absent. Anyone holding the URL could call it directly, without paying."
                          },
                          "provider_twin_id": {
                            "type": "integer",
                            "description": "The twin that receives payment for this item: always the commodity's twin."
                          },
                          "commodities_on_twin": {
                            "type": "integer",
                            "description": "How many commodities share `provider_twin_id`. A twin holds a single paywall configuration, so when this is greater than 1 only one of the items on that twin can be sold through the paywall at a time."
                          },
                          "active": {
                            "type": "boolean",
                            "description": "Whether the item is on sale. Inactive items are left out of default listings and are not offered by MCP servers."
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "commodity_hash": {
                            "type": "string",
                            "description": "Hash of the commodity. The item's name, descriptor, description and price live on the commodity; change them with `PUT /v4/commodity/{hash}`."
                          },
                          "commodity_name": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Display name of the item."
                          },
                          "commodity_description": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Description of the item."
                          },
                          "commodity_descriptor": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The line item on the receipt, the checkout confirmation and the card statement."
                          },
                          "commodity_cost": {
                            "type": "number",
                            "description": "Price per purchase, in units of the commodity's digital quantity."
                          },
                          "commodity_private": {
                            "type": "boolean",
                            "description": "When true the item is private: it is only visible to callers who may manage it, and is offered only on the seller's own MCP server."
                          },
                          "featured": {
                            "type": "boolean",
                            "description": "Whether the item is registered as a first-class tool on the selling account's managed MCP server, so agents can call it directly. Items that are not featured can still be found and bought through catalog search."
                          },
                          "seller": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Who sells the item, for display to buyers: the selling account's business name, else its name."
                          }
                        }
                      }
                    },
                    "total": {
                      "type": "integer",
                      "description": "Number of items matching the filters, across all pages."
                    },
                    "limit": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "The page size applied; `null` for an unpaged `mine` listing."
                    },
                    "offset": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "items",
                    "total",
                    "limit",
                    "offset"
                  ]
                },
                "examples": {
                  "public": {
                    "summary": "Anonymous, or not the seller",
                    "value": {
                      "items": [
                        {
                          "id": 42,
                          "commodity_id": 118,
                          "service_method": "POST",
                          "service_schema": {
                            "type": "object",
                            "properties": {
                              "city": {
                                "type": "string",
                                "description": "City to forecast."
                              },
                              "days": {
                                "type": "integer",
                                "minimum": 1,
                                "maximum": 14
                              }
                            },
                            "required": [
                              "city"
                            ]
                          },
                          "provider_twin_id": 3539,
                          "commodities_on_twin": 1,
                          "active": true,
                          "created_at": "2026-08-14T15:02:11.482Z",
                          "updated_at": "2026-08-14T15:02:11.482Z",
                          "commodity_hash": "9f2c4e1a7b3d5f60819a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
                          "commodity_name": "14-day weather forecast",
                          "commodity_description": "Daily forecast for any city, up to 14 days ahead.",
                          "commodity_descriptor": "14-day forecast",
                          "commodity_cost": 0.25,
                          "commodity_private": false,
                          "featured": true,
                          "seller": "Acme Weather Inc."
                        }
                      ],
                      "total": 1,
                      "limit": 50,
                      "offset": 0
                    }
                  },
                  "manager": {
                    "summary": "The seller, with mine=true",
                    "value": {
                      "items": [
                        {
                          "id": 42,
                          "commodity_id": 118,
                          "service_method": "POST",
                          "service_schema": {
                            "type": "object",
                            "properties": {
                              "city": {
                                "type": "string",
                                "description": "City to forecast."
                              },
                              "days": {
                                "type": "integer",
                                "minimum": 1,
                                "maximum": 14
                              }
                            },
                            "required": [
                              "city"
                            ]
                          },
                          "external_endpoint": "https://api.example.com/forecast",
                          "provider_twin_id": 3539,
                          "commodities_on_twin": 1,
                          "active": true,
                          "created_at": "2026-08-14T15:02:11.482Z",
                          "updated_at": "2026-08-14T15:02:11.482Z",
                          "commodity_hash": "9f2c4e1a7b3d5f60819a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
                          "commodity_name": "14-day weather forecast",
                          "commodity_description": "Daily forecast for any city, up to 14 days ahead.",
                          "commodity_descriptor": "14-day forecast",
                          "commodity_cost": 0.25,
                          "commodity_private": false,
                          "featured": true,
                          "seller": "Acme Weather Inc."
                        }
                      ],
                      "total": 1,
                      "limit": null,
                      "offset": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A query parameter was malformed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "limit must be a positive integer",
                  "status": 400
                }
              }
            }
          },
          "401": {
            "description": "`mine=true` was requested without authenticating.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Authentication required",
                  "status": 401
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "commodity_id",
            "in": "query",
            "description": "Only the item for this commodity.",
            "schema": {
              "type": "integer"
            },
            "example": 118
          },
          {
            "name": "active",
            "in": "query",
            "description": "`true` (the default) lists active items, `false` inactive ones, `all` both.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false",
                "all"
              ],
              "default": "true"
            }
          },
          {
            "name": "mine",
            "in": "query",
            "description": "Restrict the listing to items the caller may manage. Requires a bearer token. A `mine` listing is unpaged unless `limit` is given.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size. Defaults to 50; values above 200 are capped at 200.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Number of items to skip.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "created_after",
            "in": "query",
            "description": "Only items created after this instant (ISO 8601).",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "example": "2026-08-01T00:00:00Z"
          }
        ]
      },
      "post": {
        "tags": [
          "Catalog"
        ],
        "summary": "Create a catalog item",
        "description": "Puts one of your commodities up for sale. You must be able to manage the commodity: it is on one of your twins or, for a user, on a twin of the account you belong to. An account's own token is refused with `400` when the account has no user associated with it; `POST /v4/user` with that token gives it one.\n\nCreating the item configures the paywall on the commodity's twin with the commodity's price and a fulfilment URL for this item. If the twin cannot be reached or refuses the configuration, the item is not created and the call answers `502`. The commodity must have a positive price for this to succeed.\n\nA twin holds a single paywall configuration, so give each priced item a commodity on a twin of its own. When several active priced items share a twin, whichever was configured last sets the price and fulfilment URL for all of them. Creating one there still succeeds, but the response carries a `warning` naming the others.\n\nSend `featured: true` to also register the item as a tool on your account's managed MCP server. Subscribers to catalog changes are notified of the new item.\n\nSend `dispatch_params` to give the item its dispatch parameters in the same step, such as the key your endpoint checks. They are written with the item and its fulfilment secret in one transaction, so the item is never on sale without them; if any entry is refused, nothing is created. The answer then carries them as `GET /v4/catalog/items/{id}/dispatch-params` returns them, with no sensitive value.\n\nAn `external_endpoint` with placeholders is checked against the `service_schema` and `dispatch_params` sent here: see `external_endpoint` below.",
        "operationId": "postV4CatalogItems",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "commodity_id": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "The commodity to sell. It supplies the item's name, description, price and receiving twin."
                  },
                  "service_method": {
                    "type": "string",
                    "enum": [
                      "GET",
                      "POST",
                      "PUT"
                    ],
                    "description": "HTTP method used to call `external_endpoint` once a purchase has settled. With `GET` the buyer's arguments are sent as query parameters; with `POST` or `PUT` they are sent as a JSON body. Either way, an argument that fills a `{input.NAME}` placeholder in `external_endpoint` is sent there instead.",
                    "default": "GET"
                  },
                  "service_schema": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "JSON Schema describing the arguments a buyer supplies with a purchase. Only arguments declared here are forwarded to `external_endpoint`, and purchases missing a `required` argument are not dispatched. MCP clients also see it as the tool's input schema.",
                    "default": {}
                  },
                  "external_endpoint": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uri-template",
                    "description": "The URL called to fulfil a purchase after payment settles. `null` for an item bought purely for what the payment represents (access, a licence, an entitlement), where nothing needs to be called afterwards.\n\nIt may carry placeholders, filled in when the purchase is dispatched: `{input.NAME}` with the buyer's argument NAME, and `{secret.NAME}` with the item's dispatch parameter of kind `url` named NAME, e.g. `https://api.example.com/points/{input.lat},{input.lon}/days` or `https://api.example.com/accounts/{secret.account}/orders`. NAME is 1-64 letters, digits, `_` or `-`.\n\n- Placeholders may appear in the path and the query string only, never in the scheme, userinfo, host, port or fragment: nothing before the first `/` after `://`, and no `#` before one. Any other `{` or `}` is refused.\n- Each `{input.NAME}` must be a property of `service_schema`, listed in its `required`, with a `type` of `string`, `number`, `integer` or `boolean` (or none, which means string). These are checked whenever the endpoint or the schema is written.\n- Each `{secret.NAME}` must name a URL value of the item: on create, one in the same request's `dispatch_params`.\n- A query parameter that a `{secret.NAME}` fills, such as `key` in `?key={secret.key}`, may not share its name with an input in `service_schema` unless a placeholder carries that input: on a `GET` the buyer's argument would be sent under the same name.\n- Every value is percent-encoded whole, so `/`, `?`, `#`, `&` and spaces stay inside it. A buyer value that is empty, `.` or `..` is refused, and the purchase records a failed dispatch. An input that fills a placeholder is sent there only, not again as a query parameter or in the JSON body."
                  },
                  "active": {
                    "type": "boolean",
                    "default": true,
                    "description": "Whether the item is on sale immediately."
                  },
                  "featured": {
                    "type": "boolean",
                    "description": "Whether the item is registered as a first-class tool on the selling account's managed MCP server, so agents can call it directly. Items that are not featured can still be found and bought through catalog search.",
                    "default": false
                  },
                  "dispatch_params": {
                    "type": "array",
                    "maxItems": 20,
                    "description": "Headers and query parameters payment adds when it calls `external_endpoint` to fulfil a purchase, such as the credential your endpoint checks, and the URL values its `{secret.NAME}` placeholders name. Written with the item, so it is never on sale without them. The rules are those for a new parameter in `PUT /v4/catalog/items/{id}/dispatch-params`, with query names checked against the `service_schema` and `external_endpoint` sent here. An entry may not carry an `id`, and every entry needs its `value`.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "kind": {
                          "type": "string",
                          "enum": [
                            "header",
                            "query",
                            "url"
                          ],
                          "description": "An HTTP header; a query parameter added to the endpoint URL; or a URL value, which is sent only where `external_endpoint` says `{secret.NAME}`, percent-encoded."
                        },
                        "name": {
                          "type": "string",
                          "description": "The header or query parameter name, or for a URL value the NAME in `{secret.NAME}`: 1-64 letters, digits, `_` or `-`, compared exactly."
                        },
                        "sensitive": {
                          "type": "boolean",
                          "description": "Whether the value is a secret. A sensitive value is never returned once saved."
                        },
                        "value": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 4096,
                          "description": "The value, used exactly as sent. A sensitive one is never returned."
                        }
                      },
                      "required": [
                        "kind",
                        "name",
                        "sensitive",
                        "value"
                      ]
                    }
                  }
                },
                "required": [
                  "commodity_id"
                ]
              },
              "examples": {
                "plain": {
                  "summary": "An item",
                  "value": {
                    "commodity_id": 118,
                    "service_method": "POST",
                    "service_schema": {
                      "type": "object",
                      "properties": {
                        "city": {
                          "type": "string",
                          "description": "City to forecast."
                        },
                        "days": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 14
                        }
                      },
                      "required": [
                        "city"
                      ]
                    },
                    "external_endpoint": "https://api.example.com/forecast",
                    "featured": true
                  }
                },
                "withKey": {
                  "summary": "An item with the key its endpoint checks",
                  "value": {
                    "commodity_id": 118,
                    "service_method": "POST",
                    "service_schema": {
                      "type": "object",
                      "properties": {
                        "city": {
                          "type": "string",
                          "description": "City to forecast."
                        },
                        "days": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 14
                        }
                      },
                      "required": [
                        "city"
                      ]
                    },
                    "external_endpoint": "https://api.example.com/forecast",
                    "featured": true,
                    "dispatch_params": [
                      {
                        "kind": "header",
                        "name": "X-API-KEY",
                        "sensitive": true,
                        "value": "<your secret>"
                      },
                      {
                        "kind": "query",
                        "name": "source",
                        "sensitive": false,
                        "value": "qatom"
                      }
                    ]
                  }
                },
                "templated": {
                  "summary": "An endpoint taking an input and a secret in its path",
                  "value": {
                    "commodity_id": 118,
                    "service_method": "GET",
                    "service_schema": {
                      "type": "object",
                      "properties": {
                        "lat": {
                          "type": "number"
                        },
                        "lon": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "lat",
                        "lon"
                      ]
                    },
                    "external_endpoint": "https://api.example.com/v2/accounts/{secret.account}/points/{input.lat},{input.lon}/days",
                    "dispatch_params": [
                      {
                        "kind": "url",
                        "name": "account",
                        "sensitive": true,
                        "value": "<your account id>"
                      },
                      {
                        "kind": "header",
                        "name": "X-API-KEY",
                        "sensitive": true,
                        "value": "<your secret>"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The catalog item was created and its twin configured.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "item": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "commodity_id": {
                          "type": "integer"
                        },
                        "service_method": {
                          "type": "string",
                          "enum": [
                            "GET",
                            "POST",
                            "PUT"
                          ],
                          "description": "HTTP method used to call `external_endpoint` once a purchase has settled. With `GET` the buyer's arguments are sent as query parameters; with `POST` or `PUT` they are sent as a JSON body. Either way, an argument that fills a `{input.NAME}` placeholder in `external_endpoint` is sent there instead."
                        },
                        "service_schema": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "JSON Schema describing the arguments a buyer supplies with a purchase. Only arguments declared here are forwarded to `external_endpoint`, and purchases missing a `required` argument are not dispatched. MCP clients also see it as the tool's input schema."
                        },
                        "external_endpoint": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uri-template",
                          "description": "The URL called to fulfil a purchase after payment settles. `null` for an item bought purely for what the payment represents (access, a licence, an entitlement), where nothing needs to be called afterwards. It may carry `{input.NAME}` and `{secret.NAME}` placeholders, filled in when a purchase is dispatched: see the request body of `POST /v4/catalog/items`."
                        },
                        "active": {
                          "type": "boolean"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "featured": {
                          "type": "boolean",
                          "description": "Present only when `featured` was sent in the request."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    },
                    "dispatch_params": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DispatchParam"
                      },
                      "description": "Present only when `dispatch_params` was sent: the item's parameters as stored, headers first, then by name. A sensitive value is `null`."
                    },
                    "warning": {
                      "type": "string",
                      "description": "Present when other active priced items share this item's twin, so they share one paywall configuration."
                    }
                  },
                  "required": [
                    "item"
                  ]
                },
                "examples": {
                  "plain": {
                    "summary": "An item",
                    "value": {
                      "item": {
                        "id": 42,
                        "commodity_id": 118,
                        "service_method": "POST",
                        "service_schema": {
                          "type": "object",
                          "properties": {
                            "city": {
                              "type": "string",
                              "description": "City to forecast."
                            },
                            "days": {
                              "type": "integer",
                              "minimum": 1,
                              "maximum": 14
                            }
                          },
                          "required": [
                            "city"
                          ]
                        },
                        "external_endpoint": "https://api.example.com/forecast",
                        "active": true,
                        "created_at": "2026-08-14T15:02:11.482Z",
                        "updated_at": "2026-08-14T15:02:11.482Z",
                        "featured": true
                      }
                    }
                  },
                  "withKey": {
                    "summary": "An item with the key its endpoint checks",
                    "value": {
                      "item": {
                        "id": 42,
                        "commodity_id": 118,
                        "service_method": "POST",
                        "service_schema": {
                          "type": "object",
                          "properties": {
                            "city": {
                              "type": "string",
                              "description": "City to forecast."
                            },
                            "days": {
                              "type": "integer",
                              "minimum": 1,
                              "maximum": 14
                            }
                          },
                          "required": [
                            "city"
                          ]
                        },
                        "external_endpoint": "https://api.example.com/forecast",
                        "active": true,
                        "created_at": "2026-08-14T15:02:11.482Z",
                        "updated_at": "2026-08-14T15:02:11.482Z",
                        "featured": true
                      },
                      "dispatch_params": [
                        {
                          "id": 7,
                          "kind": "header",
                          "name": "X-API-KEY",
                          "sensitive": true,
                          "value": null,
                          "value_set": true,
                          "updated_at": "2026-08-14T15:02:11.482Z"
                        },
                        {
                          "id": 8,
                          "kind": "query",
                          "name": "source",
                          "sensitive": false,
                          "value": "qatom",
                          "value_set": true,
                          "updated_at": "2026-08-14T15:02:11.482Z"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The caller is an account with no user associated with it, the body is invalid, or `commodity_id` does not exist or is not a commodity you may manage (the two are not distinguished). Nothing was created. When an entry of `dispatch_params` breaks a rule, `field` names it, e.g. `dispatch_params[1].name`; when the endpoint's placeholders do, `field` is `external_endpoint` and the message names the placeholder. The message never repeats a value.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "field": {
                      "type": "string",
                      "description": "Where the problem is: `external_endpoint`, or an entry of `dispatch_params`, e.g. `dispatch_params[1].value`."
                    },
                    "status": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "error"
                  ]
                },
                "examples": {
                  "noUser": {
                    "summary": "An account with no user",
                    "value": {
                      "error": "This account has no user associated with it. Create one with POST /v4/user before creating catalog items.",
                      "status": 400
                    }
                  },
                  "missing": {
                    "summary": "Missing commodity",
                    "value": {
                      "error": "commodity_id is required",
                      "status": 400
                    }
                  },
                  "notManaged": {
                    "summary": "Commodity not yours",
                    "value": {
                      "error": "commodity_id 118 does not exist or is not managed by the caller",
                      "status": 400
                    }
                  },
                  "badMethod": {
                    "summary": "Bad method",
                    "value": {
                      "error": "service_method must be GET, POST, or PUT",
                      "status": 400
                    }
                  },
                  "reservedHeader": {
                    "summary": "A header payment sets itself",
                    "value": {
                      "error": "dispatch_params[0].name is a header payment sets itself or never forwards",
                      "field": "dispatch_params[0].name",
                      "status": 400
                    }
                  },
                  "noValue": {
                    "summary": "A parameter without its value",
                    "value": {
                      "error": "dispatch_params[1].value is required for a new parameter",
                      "field": "dispatch_params[1].value",
                      "status": 400
                    }
                  },
                  "inputNotRequired": {
                    "summary": "A placeholder input the schema does not require",
                    "value": {
                      "error": "external_endpoint uses {input.lat}, so service_schema must list lat in required",
                      "field": "external_endpoint",
                      "status": 400
                    }
                  },
                  "unknownSecret": {
                    "summary": "A secret placeholder with no URL value",
                    "value": {
                      "error": "external_endpoint uses {secret.account}, but the item has no dispatch parameter of kind \"url\" named account",
                      "field": "external_endpoint",
                      "status": 400
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The request did not carry a valid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Forbidden",
                  "status": 403
                }
              }
            }
          },
          "409": {
            "description": "`featured: true` was requested but the selling account has no managed MCP server to register the tool on. Nothing was changed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "This account has no MCP server to feature the item on. Create one first.",
                  "status": 409
                }
              }
            }
          },
          "500": {
            "description": "The item could not be created. This is currently also the response when the commodity already has a catalog item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The provider twin could not be reached, or refused the paywall configuration (price and fulfilment URL) for this item. The write was undone.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Could not configure the paywall for this item: Twin 00000000000000000000000000000000.micro.biz.todaq.net rejected the paywall config: Bad Request",
                  "status": 502
                }
              }
            }
          },
          "503": {
            "description": "`dispatch_params` was not empty, and this server has no encryption key for dispatch parameters. Nothing was created. An item without them can still be created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Dispatch parameters are not configured on this server",
                  "status": 503
                }
              }
            }
          }
        }
      }
    },
    "/v4/catalog/items/{id}": {
      "delete": {
        "tags": [
          "Catalog"
        ],
        "summary": "Deactivate a catalog item",
        "description": "Takes the item off sale by marking it inactive. The item is not destroyed: it still appears with `active=false` or `active=all`, and can be put back on sale with `PUT` and `active: true`. Subscribers to catalog changes are notified.",
        "operationId": "deleteV4CatalogItemsById",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "204": {
            "description": "The item is now inactive."
          },
          "403": {
            "description": "The request did not carry a valid bearer token, or the caller has no authority over the catalog item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "unauthenticated": {
                    "summary": "No bearer token",
                    "value": {
                      "error": "Forbidden",
                      "status": 403
                    }
                  },
                  "notOwner": {
                    "summary": "Not the seller",
                    "value": {
                      "error": "Forbidden: you do not have authority over this catalog item",
                      "status": 403
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No catalog item with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Catalog item not found",
                  "status": 404
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Identifies the catalog item.",
            "schema": {
              "type": "integer"
            },
            "example": 42
          }
        ]
      },
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "Get a catalog item",
        "description": "Returns one catalog item, inactive ones included. A private item is returned only to callers who may manage it, and so is `external_endpoint`.",
        "operationId": "getV4CatalogItemsById",
        "security": [],
        "responses": {
          "200": {
            "description": "The catalog item.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "item": {
                      "type": "object",
                      "description": "A catalog item, joined with the commodity that prices it.",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "description": "Identifies the catalog item."
                        },
                        "commodity_id": {
                          "type": "integer",
                          "description": "The commodity this item sells."
                        },
                        "service_method": {
                          "type": "string",
                          "enum": [
                            "GET",
                            "POST",
                            "PUT"
                          ],
                          "description": "HTTP method used to call `external_endpoint` once a purchase has settled. With `GET` the buyer's arguments are sent as query parameters; with `POST` or `PUT` they are sent as a JSON body. Either way, an argument that fills a `{input.NAME}` placeholder in `external_endpoint` is sent there instead."
                        },
                        "service_schema": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "JSON Schema describing the arguments a buyer supplies with a purchase. Only arguments declared here are forwarded to `external_endpoint`, and purchases missing a `required` argument are not dispatched. MCP clients also see it as the tool's input schema."
                        },
                        "external_endpoint": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uri-template",
                          "description": "The URL called to fulfil a purchase after payment settles. `null` for an item bought purely for what the payment represents (access, a licence, an entitlement), where nothing needs to be called afterwards. It may carry `{input.NAME}` and `{secret.NAME}` placeholders, filled in when a purchase is dispatched: see the request body of `POST /v4/catalog/items`. Returned only to callers who may manage the item; for anyone else, anonymous callers included, the key is absent. Anyone holding the URL could call it directly, without paying."
                        },
                        "provider_twin_id": {
                          "type": "integer",
                          "description": "The twin that receives payment for this item: always the commodity's twin."
                        },
                        "commodities_on_twin": {
                          "type": "integer",
                          "description": "How many commodities share `provider_twin_id`. A twin holds a single paywall configuration, so when this is greater than 1 only one of the items on that twin can be sold through the paywall at a time."
                        },
                        "active": {
                          "type": "boolean",
                          "description": "Whether the item is on sale. Inactive items are left out of default listings and are not offered by MCP servers."
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "commodity_hash": {
                          "type": "string",
                          "description": "Hash of the commodity. The item's name, descriptor, description and price live on the commodity; change them with `PUT /v4/commodity/{hash}`."
                        },
                        "commodity_name": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Display name of the item."
                        },
                        "commodity_description": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Description of the item."
                        },
                        "commodity_descriptor": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The line item on the receipt, the checkout confirmation and the card statement."
                        },
                        "commodity_cost": {
                          "type": "number",
                          "description": "Price per purchase, in units of the commodity's digital quantity."
                        },
                        "commodity_private": {
                          "type": "boolean",
                          "description": "When true the item is private: it is only visible to callers who may manage it, and is offered only on the seller's own MCP server."
                        },
                        "featured": {
                          "type": "boolean",
                          "description": "Whether the item is registered as a first-class tool on the selling account's managed MCP server, so agents can call it directly. Items that are not featured can still be found and bought through catalog search."
                        },
                        "seller": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Who sells the item, for display to buyers: the selling account's business name, else its name."
                        }
                      }
                    }
                  },
                  "required": [
                    "item"
                  ]
                },
                "examples": {
                  "public": {
                    "summary": "Anonymous, or not the seller",
                    "value": {
                      "item": {
                        "id": 42,
                        "commodity_id": 118,
                        "service_method": "POST",
                        "service_schema": {
                          "type": "object",
                          "properties": {
                            "city": {
                              "type": "string",
                              "description": "City to forecast."
                            },
                            "days": {
                              "type": "integer",
                              "minimum": 1,
                              "maximum": 14
                            }
                          },
                          "required": [
                            "city"
                          ]
                        },
                        "provider_twin_id": 3539,
                        "commodities_on_twin": 1,
                        "active": true,
                        "created_at": "2026-08-14T15:02:11.482Z",
                        "updated_at": "2026-08-14T15:02:11.482Z",
                        "commodity_hash": "9f2c4e1a7b3d5f60819a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
                        "commodity_name": "14-day weather forecast",
                        "commodity_description": "Daily forecast for any city, up to 14 days ahead.",
                        "commodity_descriptor": "14-day forecast",
                        "commodity_cost": 0.25,
                        "commodity_private": false,
                        "featured": true,
                        "seller": "Acme Weather Inc."
                      }
                    }
                  },
                  "manager": {
                    "summary": "The seller",
                    "value": {
                      "item": {
                        "id": 42,
                        "commodity_id": 118,
                        "service_method": "POST",
                        "service_schema": {
                          "type": "object",
                          "properties": {
                            "city": {
                              "type": "string",
                              "description": "City to forecast."
                            },
                            "days": {
                              "type": "integer",
                              "minimum": 1,
                              "maximum": 14
                            }
                          },
                          "required": [
                            "city"
                          ]
                        },
                        "external_endpoint": "https://api.example.com/forecast",
                        "provider_twin_id": 3539,
                        "commodities_on_twin": 1,
                        "active": true,
                        "created_at": "2026-08-14T15:02:11.482Z",
                        "updated_at": "2026-08-14T15:02:11.482Z",
                        "commodity_hash": "9f2c4e1a7b3d5f60819a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
                        "commodity_name": "14-day weather forecast",
                        "commodity_description": "Daily forecast for any city, up to 14 days ahead.",
                        "commodity_descriptor": "14-day forecast",
                        "commodity_cost": 0.25,
                        "commodity_private": false,
                        "featured": true,
                        "seller": "Acme Weather Inc."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No catalog item with that id. A private item the caller may not manage is reported the same way, so its existence is not disclosed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Catalog item not found",
                  "status": 404
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Identifies the catalog item.",
            "schema": {
              "type": "integer"
            },
            "example": 42
          }
        ]
      },
      "put": {
        "tags": [
          "Catalog"
        ],
        "summary": "Update a catalog item",
        "description": "Changes the fields you send and leaves the rest alone. To change the item's name, description or price, update its commodity instead.\n\nMoving the item to a different commodity, or reactivating an inactive item, reconfigures the paywall on the (new) commodity's twin. If the twin cannot be reached or refuses, the commodity and active state are restored and the call answers `502`; other fields sent in the same request stay applied.\n\nSend `featured` to register the item as a tool on your account's managed MCP server (`true`) or remove it from the tool list (`false`); a request carrying only `featured` is valid. Subscribers to catalog changes are notified.\n\nWriting `external_endpoint` or `service_schema` checks the endpoint's placeholders against both, each as it will be after the request (see `external_endpoint` below). A `{secret.NAME}` must name one of the item's URL values: add it with `PUT /v4/catalog/items/{id}/dispatch-params` first.",
        "operationId": "putV4CatalogItemsById",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "commodity_id": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Move the item to another commodity you may manage."
                  },
                  "service_method": {
                    "type": "string",
                    "enum": [
                      "GET",
                      "POST",
                      "PUT"
                    ],
                    "description": "HTTP method used to call `external_endpoint` once a purchase has settled. With `GET` the buyer's arguments are sent as query parameters; with `POST` or `PUT` they are sent as a JSON body. Either way, an argument that fills a `{input.NAME}` placeholder in `external_endpoint` is sent there instead."
                  },
                  "service_schema": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "JSON Schema describing the arguments a buyer supplies with a purchase. Only arguments declared here are forwarded to `external_endpoint`, and purchases missing a `required` argument are not dispatched. MCP clients also see it as the tool's input schema."
                  },
                  "external_endpoint": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uri-template",
                    "description": "The URL called to fulfil a purchase after payment settles. `null` for an item bought purely for what the payment represents (access, a licence, an entitlement), where nothing needs to be called afterwards.\n\nIt may carry placeholders, filled in when the purchase is dispatched: `{input.NAME}` with the buyer's argument NAME, and `{secret.NAME}` with the item's dispatch parameter of kind `url` named NAME, e.g. `https://api.example.com/points/{input.lat},{input.lon}/days` or `https://api.example.com/accounts/{secret.account}/orders`. NAME is 1-64 letters, digits, `_` or `-`.\n\n- Placeholders may appear in the path and the query string only, never in the scheme, userinfo, host, port or fragment: nothing before the first `/` after `://`, and no `#` before one. Any other `{` or `}` is refused.\n- Each `{input.NAME}` must be a property of `service_schema`, listed in its `required`, with a `type` of `string`, `number`, `integer` or `boolean` (or none, which means string). These are checked whenever the endpoint or the schema is written.\n- Each `{secret.NAME}` must name a URL value of the item: on create, one in the same request's `dispatch_params`.\n- A query parameter that a `{secret.NAME}` fills, such as `key` in `?key={secret.key}`, may not share its name with an input in `service_schema` unless a placeholder carries that input: on a `GET` the buyer's argument would be sent under the same name.\n- Every value is percent-encoded whole, so `/`, `?`, `#`, `&` and spaces stay inside it. A buyer value that is empty, `.` or `..` is refused, and the purchase records a failed dispatch. An input that fills a placeholder is sent there only, not again as a query parameter or in the JSON body."
                  },
                  "active": {
                    "type": "boolean",
                    "description": "Take the item off sale (`false`) or put it back on sale (`true`)."
                  },
                  "featured": {
                    "type": "boolean",
                    "description": "Whether the item is registered as a first-class tool on the selling account's managed MCP server, so agents can call it directly. Items that are not featured can still be found and bought through catalog search."
                  }
                },
                "minProperties": 1
              },
              "examples": {
                "changeEndpoint": {
                  "summary": "Point fulfilment at a new URL",
                  "value": {
                    "external_endpoint": "https://api.example.com/v2/forecast"
                  }
                },
                "feature": {
                  "summary": "Promote to an MCP tool",
                  "value": {
                    "featured": true
                  }
                },
                "templated": {
                  "summary": "Take an input and a URL value in the path",
                  "value": {
                    "external_endpoint": "https://api.example.com/v2/accounts/{secret.account}/orders/{input.order}"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated catalog item.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "item": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "commodity_id": {
                          "type": "integer"
                        },
                        "service_method": {
                          "type": "string",
                          "enum": [
                            "GET",
                            "POST",
                            "PUT"
                          ],
                          "description": "HTTP method used to call `external_endpoint` once a purchase has settled. With `GET` the buyer's arguments are sent as query parameters; with `POST` or `PUT` they are sent as a JSON body. Either way, an argument that fills a `{input.NAME}` placeholder in `external_endpoint` is sent there instead."
                        },
                        "service_schema": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "JSON Schema describing the arguments a buyer supplies with a purchase. Only arguments declared here are forwarded to `external_endpoint`, and purchases missing a `required` argument are not dispatched. MCP clients also see it as the tool's input schema."
                        },
                        "external_endpoint": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uri-template",
                          "description": "The URL called to fulfil a purchase after payment settles. `null` for an item bought purely for what the payment represents (access, a licence, an entitlement), where nothing needs to be called afterwards. It may carry `{input.NAME}` and `{secret.NAME}` placeholders, filled in when a purchase is dispatched: see the request body of `POST /v4/catalog/items`."
                        },
                        "active": {
                          "type": "boolean"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "featured": {
                          "type": "boolean",
                          "description": "Present only when `featured` was sent in the request."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "item": {
                    "id": 42,
                    "commodity_id": 118,
                    "service_method": "POST",
                    "service_schema": {
                      "type": "object",
                      "properties": {
                        "city": {
                          "type": "string",
                          "description": "City to forecast."
                        },
                        "days": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 14
                        }
                      },
                      "required": [
                        "city"
                      ]
                    },
                    "external_endpoint": "https://api.example.com/v2/forecast",
                    "active": true,
                    "updated_at": "2026-09-02T09:41:57.103Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is invalid, has no updatable field, or names a `commodity_id` that does not exist or is not a commodity you may manage. Nothing was changed. When the endpoint's placeholders are at fault, `field` is `external_endpoint` and the message names the placeholder.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "field": {
                      "type": "string",
                      "description": "`external_endpoint`, when its placeholders are at fault."
                    },
                    "status": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "error"
                  ]
                },
                "examples": {
                  "noFields": {
                    "summary": "Nothing to change",
                    "value": {
                      "error": "No updatable fields provided",
                      "status": 400
                    }
                  },
                  "undeclaredInput": {
                    "summary": "A placeholder input the schema does not declare",
                    "value": {
                      "error": "external_endpoint uses {input.order}, but service_schema declares no input named order",
                      "field": "external_endpoint",
                      "status": 400
                    }
                  },
                  "outsidePath": {
                    "summary": "A placeholder in the host",
                    "value": {
                      "error": "external_endpoint: {secret.shop} must be in the path or the query string, not the scheme, userinfo, host or port",
                      "field": "external_endpoint",
                      "status": 400
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The request did not carry a valid bearer token, or the caller has no authority over the catalog item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "unauthenticated": {
                    "summary": "No bearer token",
                    "value": {
                      "error": "Forbidden",
                      "status": 403
                    }
                  },
                  "notOwner": {
                    "summary": "Not the seller",
                    "value": {
                      "error": "Forbidden: you do not have authority over this catalog item",
                      "status": 403
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No catalog item with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Catalog item not found",
                  "status": 404
                }
              }
            }
          },
          "409": {
            "description": "`featured: true` was requested but the selling account has no managed MCP server to register the tool on. Nothing was changed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "This account has no MCP server to feature the item on. Create one first.",
                  "status": 409
                }
              }
            }
          },
          "500": {
            "description": "The update failed. This is currently also the response when moving the item to a commodity that already has a catalog item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The provider twin could not be reached, or refused the paywall configuration (price and fulfilment URL) for this item. The write was undone.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Could not configure the paywall for this item: Twin 00000000000000000000000000000000.micro.biz.todaq.net rejected the paywall config: Bad Request",
                  "status": 502
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Identifies the catalog item.",
            "schema": {
              "type": "integer"
            },
            "example": 42
          }
        ]
      }
    },
    "/v4/catalog/items/{id}/dispatch-params": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "List a catalog item's dispatch parameters",
        "description": "Dispatch parameters are the headers and query parameters payment adds when it calls the item's `external_endpoint` to fulfil a purchase. Check one of them, such as an `Authorization` header, in your endpoint to confirm a call came from Qatom, rather than putting a secret in the endpoint URL. A URL value (kind `url`) is for an API that wants its secret inside the URL: it is sent only where the endpoint says `{secret.NAME}`, percent-encoded, and never as a header or a query parameter.\n\nEvery value is stored encrypted. The value of a sensitive parameter is never returned, not even to you; a parameter that is not sensitive is returned with its value. Headers come first, then query parameters, then URL values, each ordered by name.\n\nOnly callers who may manage the item can read them.",
        "operationId": "getV4CatalogItemsByIdDispatchParams",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The item's dispatch parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "params": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DispatchParam"
                      }
                    }
                  },
                  "required": [
                    "params"
                  ]
                },
                "example": {
                  "params": [
                    {
                      "id": 7,
                      "kind": "header",
                      "name": "X-Store-Key",
                      "sensitive": true,
                      "value": null,
                      "value_set": true,
                      "updated_at": "2026-10-04T23:30:00.000Z"
                    },
                    {
                      "id": 8,
                      "kind": "header",
                      "name": "X-Source",
                      "sensitive": false,
                      "value": "qatom",
                      "value_set": true,
                      "updated_at": "2026-10-04T23:30:00.000Z"
                    },
                    {
                      "id": 9,
                      "kind": "query",
                      "name": "key",
                      "sensitive": true,
                      "value": null,
                      "value_set": true,
                      "updated_at": "2026-10-04T23:30:00.000Z"
                    },
                    {
                      "id": 10,
                      "kind": "query",
                      "name": "source",
                      "sensitive": false,
                      "value": "qatom",
                      "value_set": true,
                      "updated_at": "2026-10-04T23:30:00.000Z"
                    },
                    {
                      "id": 11,
                      "kind": "url",
                      "name": "account",
                      "sensitive": true,
                      "value": null,
                      "value_set": true,
                      "updated_at": "2026-10-04T23:30:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The request did not carry a valid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Authentication required",
                  "status": 401
                }
              }
            }
          },
          "404": {
            "description": "No catalog item with that id, or one the caller may not manage. The two are not distinguished, so the item's existence is not disclosed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Catalog item not found",
                  "status": 404
                }
              }
            }
          },
          "503": {
            "description": "This server has no encryption key for dispatch parameters, so it can neither read nor store them.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Dispatch parameters are not configured on this server",
                  "status": 503
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Identifies the catalog item.",
            "schema": {
              "type": "integer"
            },
            "example": 42
          }
        ]
      },
      "put": {
        "tags": [
          "Catalog"
        ],
        "summary": "Replace a catalog item's dispatch parameters",
        "description": "Replaces the item's whole set of dispatch parameters in one step; parameters left out of `params` are deleted, and `params: []` deletes them all.\n\n- An entry with an `id` and no `value` keeps that parameter's stored value. Its `name` and `sensitive` may change, its `kind` may not. Making a sensitive parameter not sensitive needs its value sent again.\n- An entry with a `value` sets that value.\n- An entry without an `id` is a new parameter and must carry a `value`.\n\nAn item may have up to 20 parameters. A value is 1-4096 characters with no line breaks, and is used exactly as sent, spaces included. A header value must be printable ASCII and must not start or end with a space or tab, which HTTP would drop; a query parameter value may be any other text, and so may a URL value, except `.` and `..`. Header names are unique ignoring case, and payment refuses headers it sets itself or never forwards (`Host`, `Content-Type`, `Content-Length`, `Connection`, `Transfer-Encoding`, `Proxy-Authorization`, any `X-Forwarded-*` or `X-Qatom-*`, and other hop-by-hop headers). A query parameter may not share its name with an argument in the item's `service_schema`, or with a parameter already in `external_endpoint`: move it out of the URL instead. A URL value's name is 1-64 letters, digits, `_` or `-`, unique exactly.\n\nA URL value the endpoint uses cannot be removed or renamed while it does; change the endpoint first. One it does not use is kept and sent nowhere.\n\nOnly callers who may manage the item can change them.",
        "operationId": "putV4CatalogItemsByIdDispatchParams",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "params": {
                    "type": "array",
                    "maxItems": 20,
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "description": "An existing parameter of this item, to keep."
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "header",
                            "query",
                            "url"
                          ],
                          "description": "An HTTP header; a query parameter added to the endpoint URL; or a URL value, which is sent only where `external_endpoint` says `{secret.NAME}`, percent-encoded."
                        },
                        "name": {
                          "type": "string",
                          "description": "The header or query parameter name, or for a URL value the NAME in `{secret.NAME}`: 1-64 letters, digits, `_` or `-`, compared exactly."
                        },
                        "sensitive": {
                          "type": "boolean",
                          "description": "Whether the value is a secret. A sensitive value is never returned once saved."
                        },
                        "value": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 4096,
                          "description": "The value to set. Leave it out to keep the stored value of `id`."
                        }
                      },
                      "required": [
                        "kind",
                        "name",
                        "sensitive"
                      ]
                    }
                  }
                },
                "required": [
                  "params"
                ]
              },
              "examples": {
                "addSecretHeader": {
                  "summary": "Keep a header, add a secret query parameter",
                  "value": {
                    "params": [
                      {
                        "id": 7,
                        "kind": "header",
                        "name": "X-Store-Key",
                        "sensitive": true
                      },
                      {
                        "kind": "query",
                        "name": "key",
                        "sensitive": true,
                        "value": "<your secret>"
                      }
                    ]
                  }
                },
                "addUrlValue": {
                  "summary": "Add the value an endpoint names as {secret.account}",
                  "value": {
                    "params": [
                      {
                        "id": 7,
                        "kind": "header",
                        "name": "X-Store-Key",
                        "sensitive": true
                      },
                      {
                        "kind": "url",
                        "name": "account",
                        "sensitive": true,
                        "value": "<your account id>"
                      }
                    ]
                  }
                },
                "removeAll": {
                  "summary": "Remove every parameter",
                  "value": {
                    "params": []
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The item's dispatch parameters, as now stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "params": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DispatchParam"
                      }
                    }
                  },
                  "required": [
                    "params"
                  ]
                },
                "example": {
                  "params": [
                    {
                      "id": 7,
                      "kind": "header",
                      "name": "X-Store-Key",
                      "sensitive": true,
                      "value": null,
                      "value_set": true,
                      "updated_at": "2026-10-04T23:30:00.000Z"
                    },
                    {
                      "id": 8,
                      "kind": "header",
                      "name": "X-Source",
                      "sensitive": false,
                      "value": "qatom",
                      "value_set": true,
                      "updated_at": "2026-10-04T23:30:00.000Z"
                    },
                    {
                      "id": 9,
                      "kind": "query",
                      "name": "key",
                      "sensitive": true,
                      "value": null,
                      "value_set": true,
                      "updated_at": "2026-10-04T23:30:00.000Z"
                    },
                    {
                      "id": 10,
                      "kind": "query",
                      "name": "source",
                      "sensitive": false,
                      "value": "qatom",
                      "value_set": true,
                      "updated_at": "2026-10-04T23:30:00.000Z"
                    },
                    {
                      "id": 11,
                      "kind": "url",
                      "name": "account",
                      "sensitive": true,
                      "value": null,
                      "value_set": true,
                      "updated_at": "2026-10-04T23:30:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The body breaks one of the rules above. Nothing was changed. `field` points at the entry at fault; the message never repeats a value.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "field": {
                      "type": "string",
                      "description": "Where the problem is, e.g. `params[1].name`."
                    },
                    "status": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "error"
                  ]
                },
                "examples": {
                  "notSensitive": {
                    "summary": "Made not sensitive without its value",
                    "value": {
                      "error": "Enter the value again to make it not sensitive.",
                      "field": "params[0].value",
                      "status": 400
                    }
                  },
                  "clash": {
                    "summary": "Query name in the endpoint URL",
                    "value": {
                      "error": "params[1].name \"key\" is already in this item's endpoint URL. Remove it from the URL; it is sent from here instead.",
                      "field": "params[1].name",
                      "status": 400
                    }
                  },
                  "inUse": {
                    "summary": "A URL value the endpoint uses, left out",
                    "value": {
                      "error": "The endpoint uses {secret.account}",
                      "field": "params",
                      "status": 400
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The request did not carry a valid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Authentication required",
                  "status": 401
                }
              }
            }
          },
          "404": {
            "description": "No catalog item with that id, or one the caller may not manage. The two are not distinguished, so the item's existence is not disclosed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Catalog item not found",
                  "status": 404
                }
              }
            }
          },
          "503": {
            "description": "This server has no encryption key for dispatch parameters, so it can neither read nor store them.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Dispatch parameters are not configured on this server",
                  "status": 503
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Identifies the catalog item.",
            "schema": {
              "type": "integer"
            },
            "example": 42
          }
        ]
      }
    },
    "/v4/catalog/purchases/{transaction_id}": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "Look up a catalog sale",
        "description": "Returns one settled sale of an item the caller may manage. Use it to check a dispatch before serving it: an unknown transaction id, or one for another item, means the call did not come from a Qatom sale. Keep the ids you have served, so no sale is served twice.",
        "operationId": "getV4CatalogPurchasesByTransactionId",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The sale.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "purchase": {
                      "type": "object",
                      "properties": {
                        "transaction_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "catalog_item_id": {
                          "type": "integer",
                          "description": "The item sold."
                        },
                        "amount": {
                          "type": "string",
                          "description": "What settled, three decimal places."
                        },
                        "dq": {
                          "type": "string",
                          "description": "The digital quantity it settled in."
                        },
                        "settled_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "dispatch_status": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "skipped",
                            "delivered",
                            "failed"
                          ],
                          "description": "Whether the sale has been delivered to the item's endpoint."
                        }
                      },
                      "required": [
                        "transaction_id",
                        "catalog_item_id",
                        "amount",
                        "dq",
                        "settled_at",
                        "dispatch_status"
                      ]
                    }
                  },
                  "required": [
                    "purchase"
                  ]
                },
                "example": {
                  "purchase": {
                    "transaction_id": "b2b0d2c6-1f4e-4a90-9a1e-2c9a4a7f0f11",
                    "catalog_item_id": 42,
                    "amount": "0.250",
                    "dq": "a1b2c3",
                    "settled_at": "2026-10-03T15:02:11.482Z",
                    "dispatch_status": "pending"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No authenticated principal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No settled sale with that id on an item the caller may manage.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "transaction_id",
            "in": "path",
            "required": true,
            "description": "The sale's transaction id, as sent in a dispatch's `X-Qatom-Transaction-Id` header.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ]
      }
    },
    "/v4/catalog/revenue": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "Total revenue across your catalog",
        "description": "Everything the caller’s catalog items have earned, all time, from the settled purchase records. Covers every item the caller may manage — the same set `GET /v4/catalog/items?mine=true` lists — or, with `catalog_item_id`, just that one. An agent's token reads only the business account the user granted it when they signed it in.",
        "operationId": "getV4CatalogRevenue",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The total and the number of sales it is made of.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revenue": {
                      "type": "string",
                      "description": "Three decimal places, e.g. `\"12.345\"`."
                    },
                    "currency": {
                      "type": "string",
                      "description": "Always `USD-TDN`."
                    },
                    "transactions": {
                      "type": "integer",
                      "description": "How many settled sales the total is made of."
                    }
                  },
                  "required": [
                    "revenue",
                    "currency",
                    "transactions"
                  ]
                },
                "example": {
                  "revenue": "12.345",
                  "currency": "USD-TDN",
                  "transactions": 7
                }
              }
            }
          },
          "400": {
            "description": "`catalog_item_id` is not an integer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No authenticated principal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`account_not_granted`: an agent's token, granted no account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`catalog_item_id` names an item the caller may not manage, or none at all.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "catalog_item_id",
            "in": "query",
            "description": "Only this item's sales. An item the caller may not manage answers `404`, as one that does not exist does.",
            "schema": {
              "type": "integer"
            },
            "example": 42
          }
        ]
      }
    },
    "/v4/commodity": {
      "post": {
        "tags": [
          "Commodities"
        ],
        "summary": "Create a commodity",
        "description": "Registers something for sale against one of your twins. Only account principals may create commodities. Pass `?embed` to receive a ready-made embed snippet alongside the commodity.",
        "operationId": "postV4Commodity",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "descriptor": {
                    "type": "string",
                    "description": "Human-readable name for the commodity."
                  },
                  "cost": {
                    "type": "number",
                    "minimum": 0,
                    "description": "Price, in the smallest unit of `dq`. Zero makes it free: nothing is transferred, and the sale is recorded and delivered all the same."
                  },
                  "dq": {
                    "$ref": "#/components/schemas/DigitalQuantityHash"
                  },
                  "twin_id": {
                    "type": "integer",
                    "description": "The twin that will be paid."
                  }
                },
                "required": [
                  "descriptor",
                  "cost",
                  "dq",
                  "twin_id"
                ]
              },
              "example": {
                "descriptor": "Premium article",
                "cost": 25,
                "dq": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73",
                "twin_id": 3539
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The commodity was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "hash": {
                      "type": "string"
                    },
                    "descriptor": {
                      "type": "string"
                    },
                    "dq": {
                      "$ref": "#/components/schemas/DigitalQuantityHash"
                    },
                    "cost": {
                      "type": "number"
                    },
                    "hostname": {
                      "type": "string",
                      "description": "Hostname of the owning twin."
                    },
                    "embed": {
                      "type": "string",
                      "description": "Embed snippet, when `?embed` was passed."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Only account principals may create commodities.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "embed",
            "in": "query",
            "description": "When present, the response includes an `embed` script snippet.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/commodity/{hash}": {
      "get": {
        "tags": [
          "Commodities"
        ],
        "summary": "Get a commodity",
        "description": "Returns the commodity, the twin that sells it, and an embed snippet that renders a payment button for it.",
        "operationId": "getV4CommodityByHash",
        "security": [],
        "responses": {
          "200": {
            "description": "The commodity.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "hash": {
                      "type": "string"
                    },
                    "descriptor": {
                      "type": "string"
                    },
                    "dq": {
                      "$ref": "#/components/schemas/DigitalQuantityHash"
                    },
                    "cost": {
                      "type": "number"
                    },
                    "hostname": {
                      "type": "string",
                      "description": "Hostname of the owning twin."
                    },
                    "embed": {
                      "type": "string",
                      "description": "Script snippet embedding a payment button."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No commodity with that hash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the commodity.",
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "put": {
        "tags": [
          "Commodities"
        ],
        "summary": "Update a commodity",
        "description": "Updates the provided fields of a commodity you manage. Fields you leave out keep their current value.\n\nChanging `cost` also changes what the commodity's catalog items charge. The provider twin enforces the price itself, so the new price is pushed to the twin of every active catalog item selling the commodity. If a twin refuses it, the old price is put back and the call answers `502`: the price is never left changed here while the twin still charges the old one.",
        "operationId": "putV4CommodityByHash",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "descriptor": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "The line item shown on the receipt, the checkout confirmation and the card statement."
                  },
                  "cost": {
                    "type": "number",
                    "minimum": 0,
                    "description": "Price, in the smallest unit of `dq`."
                  },
                  "mcp_private": {
                    "type": "boolean",
                    "description": "Hide the commodity's catalog items from public MCP discovery."
                  }
                }
              },
              "example": {
                "cost": 50
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The commodity was updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "commodity": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "hash": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "description": {
                          "type": "string"
                        },
                        "descriptor": {
                          "type": "string"
                        },
                        "cost": {
                          "type": "number"
                        },
                        "mcp_private": {
                          "type": "boolean"
                        },
                        "twin_id": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A field is invalid, or none was provided.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The principal may not update commodities.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No commodity with that hash that you manage.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "A provider twin refused the new price. The price was not changed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the commodity.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/commodity/{hash}/transactions": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "List a commodity’s transactions",
        "description": "Reconstructs the sales history of one commodity from the owning twin’s binder, and totals what it has earned. Only the twin’s owner may ask; an agent's token, only for the business account the user granted it.",
        "operationId": "getV4CommodityByHashTransactions",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The commodity and its sales.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "commodity": {
                      "type": "object",
                      "properties": {
                        "hash": {
                          "type": "string"
                        },
                        "descriptor": {
                          "type": "string"
                        },
                        "cost": {
                          "type": "number"
                        },
                        "dq": {
                          "$ref": "#/components/schemas/DigitalQuantityHash"
                        },
                        "hostname": {
                          "type": "string",
                          "description": "The owning twin."
                        }
                      }
                    },
                    "calculation": {
                      "type": "object",
                      "properties": {
                        "totalAmount": {
                          "type": "number",
                          "description": "Everything this commodity has earned."
                        },
                        "transactionCount": {
                          "type": "integer"
                        },
                        "transactions": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "timestamp": {
                                "type": "string",
                                "format": "date-time"
                              },
                              "twistId": {
                                "type": "string",
                                "description": "Identifies the entry in the twin’s binder."
                              },
                              "operation": {
                                "type": "string"
                              },
                              "amount": {
                                "type": "number"
                              },
                              "fromAddress": {
                                "type": "string",
                                "description": "The paying twin."
                              },
                              "destinationAddress": {
                                "type": "string",
                                "description": "The receiving twin."
                              },
                              "fileType": {
                                "type": "string"
                              },
                              "commodityHash": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller does not own the twin that sells this commodity, or an agent's token reads one outside the account the user granted it (`account_not_granted` when it was granted none).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No commodity with that hash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The transactions could not be calculated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The twin’s balance or binder could not be fetched.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Hash of the commodity to report on.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/dq": {
      "get": {
        "tags": [
          "Digital Quantities"
        ],
        "summary": "List digital quantities",
        "description": "Returns every digital quantity the platform transacts in, together with the metadata describing how each should be displayed. Identical to `GET /v4/twin/dq`.",
        "operationId": "getV4Dq",
        "security": [],
        "responses": {
          "200": {
            "description": "The available digital quantities.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "digital_quantities": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DigitalQuantityHash"
                      }
                    },
                    "metadata": {
                      "type": "object",
                      "description": "Display metadata for each quantity, keyed by DQ hash.",
                      "additionalProperties": {
                        "type": "object"
                      }
                    }
                  },
                  "required": [
                    "digital_quantities"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v4/embed/{hash}": {
      "get": {
        "tags": [
          "Embeds"
        ],
        "summary": "Render a payment embed",
        "description": "Returns the HTML for the standard payment button iframe. The frame talks to its host page by `postMessage`, emitting `_TQMResize` when its size changes and `_TQMSuccess` once a purchase completes — the embed snippet turns the latter into a `purchase` event on `document`.",
        "operationId": "getV4EmbedByHash",
        "security": [],
        "responses": {
          "200": {
            "description": "The embed document.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "`theme` is not `light` or `dark`, or `style` is not a JSON object of CSS properties."
          },
          "404": {
            "description": "No commodity with that hash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Hash of the commodity the embed is for.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "theme",
            "in": "query",
            "description": "Colour theme to render with.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "style",
            "in": "query",
            "description": "JSON-encoded CSS properties applied to the embed.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "leading",
            "in": "query",
            "description": "Text shown before the amount.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "middle",
            "in": "query",
            "description": "Text shown between the amount and the label.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "trailing",
            "in": "query",
            "description": "Text shown after the amount.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "decimalPoints",
            "in": "query",
            "description": "How many decimal places to show the amount to.",
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v4/events": {
      "post": {
        "tags": [
          "Events"
        ],
        "summary": "Record an event",
        "description": "Appends an entry to the audit trail, attributed to the authenticated principal and the current session.",
        "operationId": "postV4Events",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "actionType": {
                    "type": "string",
                    "description": "What happened, e.g. `login` or `transfer`."
                  },
                  "requestData": {
                    "type": "object",
                    "description": "What the action was asked to do."
                  },
                  "responseData": {
                    "type": "object",
                    "description": "What came back."
                  },
                  "status": {
                    "type": "string",
                    "description": "How it ended.",
                    "enum": [
                      "success",
                      "failure",
                      "pending"
                    ]
                  }
                },
                "required": [
                  "actionType"
                ]
              },
              "example": {
                "actionType": "login",
                "status": "success",
                "requestData": {
                  "method": "embed"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The event was recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "An empty object."
                },
                "example": {}
              }
            }
          },
          "400": {
            "description": "`actionType` was not supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller may not record events.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/mcp/servers/{hostname}/agent": {
      "delete": {
        "tags": [
          "MCP Tools"
        ],
        "summary": "Detach the managed agent",
        "description": "Revokes the agent's credential and detaches it from the server. Tokens already issued to it stop acting as the account immediately, although they may still introspect as active until they expire. A new agent can be attached afterwards.",
        "operationId": "deleteV4McpServersByHostnameAgent",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The agent was revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revoked": {
                      "type": "boolean",
                      "const": true
                    },
                    "client_id": {
                      "type": "string",
                      "description": "The revoked credential's client id."
                    }
                  },
                  "required": [
                    "revoked",
                    "client_id"
                  ]
                },
                "example": {
                  "revoked": true,
                  "client_id": "agent_3f9a1c7e2b5d8046a1e9c3b7"
                }
              }
            }
          },
          "401": {
            "description": "The request is not authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Authentication required",
                  "status": 401
                }
              }
            }
          },
          "403": {
            "description": "The caller is neither the account that owns the MCP server nor a user of that account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Forbidden",
                  "status": 403
                }
              }
            }
          },
          "404": {
            "description": "No MCP server with that hostname, or the server has no managed agent attached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "noServer": {
                    "summary": "Unknown server",
                    "value": {
                      "error": "MCP server not found",
                      "status": 404
                    }
                  },
                  "noAgent": {
                    "summary": "No agent attached",
                    "value": {
                      "error": "This server has no managed agent",
                      "status": 404
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "description": "The managed MCP server's hostname: the subdomain it answers on.",
            "schema": {
              "type": "string"
            },
            "example": "acme-weather"
          }
        ]
      },
      "get": {
        "tags": [
          "MCP Tools"
        ],
        "summary": "Get the managed agent",
        "description": "Returns the hosted agent attached to this MCP server, with the connection details it uses, or `agent: null` when none is attached. The client secret is never returned here. Callable by the account that owns the server or any user of that account.",
        "operationId": "getV4McpServersByHostnameAgent",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The attached agent, or `null`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agent": {
                      "oneOf": [
                        {
                          "type": "object",
                          "description": "The managed agent attached to an MCP server. Never includes the client secret.",
                          "properties": {
                            "id": {
                              "type": "integer"
                            },
                            "mcp_id": {
                              "type": "integer",
                              "description": "The MCP server the agent is bound to."
                            },
                            "client_id": {
                              "type": "string",
                              "description": "OAuth client id of the agent's credential. Always starts with `agent_`."
                            },
                            "channel": {
                              "type": "object",
                              "additionalProperties": true,
                              "description": "Free-form settings the agent host needs to reach buyers, such as the channel `type` and a phone `number` or allowed origins. Stored and returned as given."
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "pending",
                                "connected",
                                "failed",
                                "disabled"
                              ],
                              "description": "Connection state as last reported by the agent host. `pending` until it reports."
                            },
                            "status_detail": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Free-text detail accompanying `status`, e.g. an error."
                            },
                            "external_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "The agent host's own identifier for this agent."
                            },
                            "created_at": {
                              "type": "string",
                              "pattern": "^[0-9]+$",
                              "description": "Unix time in seconds, serialized as a decimal string."
                            },
                            "updated_at": {
                              "type": "string",
                              "pattern": "^[0-9]+$",
                              "description": "Unix time in seconds, serialized as a decimal string."
                            },
                            "connection": {
                              "type": "object",
                              "description": "Everything the agent host needs to connect, apart from the client secret.",
                              "properties": {
                                "client_id": {
                                  "type": "string"
                                },
                                "token_url": {
                                  "type": "string",
                                  "format": "uri",
                                  "description": "Token endpoint. Exchange the client id and secret here (client-credentials grant, HTTP Basic)."
                                },
                                "resource": {
                                  "type": "string",
                                  "format": "uri",
                                  "description": "Resource indicator to request the token for: the MCP server's origin."
                                },
                                "mcp_url": {
                                  "type": "string",
                                  "format": "uri",
                                  "description": "Where the MCP server answers."
                                }
                              }
                            }
                          }
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "agent"
                  ]
                },
                "examples": {
                  "attached": {
                    "summary": "Agent attached",
                    "value": {
                      "agent": {
                        "id": 7,
                        "mcp_id": 12,
                        "client_id": "agent_3f9a1c7e2b5d8046a1e9c3b7",
                        "channel": {
                          "type": "whatsapp",
                          "number": "+14165550123"
                        },
                        "status": "connected",
                        "status_detail": null,
                        "external_id": "wa-agent-81234",
                        "created_at": "1758290400",
                        "updated_at": "1758294012",
                        "connection": {
                          "client_id": "agent_3f9a1c7e2b5d8046a1e9c3b7",
                          "token_url": "https://pay.m.todaq.net/v4/oidc/token",
                          "resource": "https://acme-weather.mcp.m.todaq.net",
                          "mcp_url": "https://acme-weather.mcp.m.todaq.net"
                        }
                      }
                    }
                  },
                  "none": {
                    "summary": "No agent",
                    "value": {
                      "agent": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The request is not authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Authentication required",
                  "status": 401
                }
              }
            }
          },
          "403": {
            "description": "The caller is neither the account that owns the MCP server nor a user of that account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Forbidden",
                  "status": 403
                }
              }
            }
          },
          "404": {
            "description": "No MCP server with that hostname.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "MCP server not found",
                  "status": 404
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "description": "The managed MCP server's hostname: the subdomain it answers on.",
            "schema": {
              "type": "string"
            },
            "example": "acme-weather"
          }
        ]
      },
      "post": {
        "tags": [
          "MCP Tools"
        ],
        "summary": "Attach a managed agent",
        "description": "Mints an OAuth client-credentials credential bound to this MCP server and its account, and attaches it as the server's managed agent. Hand the returned connection details, `client_secret` and `callback_token` to the agent host.\n\nA token obtained with this credential acts as the account on the payment API and is accepted only by this MCP server.\n\n**`client_secret` and `callback_token` are returned only in this response.** A server has at most one agent; detach the current one before attaching another.",
        "operationId": "postV4McpServersByHostnameAgent",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "channel": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Free-form settings the agent host needs to reach buyers, such as the channel `type` and a phone `number` or allowed origins. Stored and returned as given.",
                    "default": {}
                  }
                }
              },
              "example": {
                "channel": {
                  "type": "whatsapp",
                  "number": "+14165550123"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The agent was attached. Store the secret now; it cannot be retrieved later.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agent": {
                      "type": "object",
                      "description": "The managed agent attached to an MCP server. Never includes the client secret.",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "mcp_id": {
                          "type": "integer",
                          "description": "The MCP server the agent is bound to."
                        },
                        "client_id": {
                          "type": "string",
                          "description": "OAuth client id of the agent's credential. Always starts with `agent_`."
                        },
                        "channel": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "Free-form settings the agent host needs to reach buyers, such as the channel `type` and a phone `number` or allowed origins. Stored and returned as given."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "connected",
                            "failed",
                            "disabled"
                          ],
                          "description": "Connection state as last reported by the agent host. `pending` until it reports."
                        },
                        "status_detail": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Free-text detail accompanying `status`, e.g. an error."
                        },
                        "external_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The agent host's own identifier for this agent."
                        },
                        "created_at": {
                          "type": "string",
                          "pattern": "^[0-9]+$",
                          "description": "Unix time in seconds, serialized as a decimal string."
                        },
                        "updated_at": {
                          "type": "string",
                          "pattern": "^[0-9]+$",
                          "description": "Unix time in seconds, serialized as a decimal string."
                        },
                        "connection": {
                          "type": "object",
                          "description": "Everything the agent host needs to connect, apart from the client secret.",
                          "properties": {
                            "client_id": {
                              "type": "string"
                            },
                            "token_url": {
                              "type": "string",
                              "format": "uri",
                              "description": "Token endpoint. Exchange the client id and secret here (client-credentials grant, HTTP Basic)."
                            },
                            "resource": {
                              "type": "string",
                              "format": "uri",
                              "description": "Resource indicator to request the token for: the MCP server's origin."
                            },
                            "mcp_url": {
                              "type": "string",
                              "format": "uri",
                              "description": "Where the MCP server answers."
                            }
                          }
                        }
                      }
                    },
                    "client_secret": {
                      "type": "string",
                      "description": "The credential's client secret. Returned only once."
                    },
                    "callback_token": {
                      "type": "string",
                      "description": "Opaque token issued to the agent host with the credential. Returned only once."
                    }
                  },
                  "required": [
                    "agent",
                    "client_secret",
                    "callback_token"
                  ]
                },
                "example": {
                  "agent": {
                    "id": 7,
                    "mcp_id": 12,
                    "client_id": "agent_3f9a1c7e2b5d8046a1e9c3b7",
                    "channel": {
                      "type": "whatsapp",
                      "number": "+14165550123"
                    },
                    "status": "pending",
                    "status_detail": null,
                    "external_id": null,
                    "created_at": "1758290400",
                    "updated_at": "1758290400",
                    "connection": {
                      "client_id": "agent_3f9a1c7e2b5d8046a1e9c3b7",
                      "token_url": "https://pay.m.todaq.net/v4/oidc/token",
                      "resource": "https://acme-weather.mcp.m.todaq.net",
                      "mcp_url": "https://acme-weather.mcp.m.todaq.net"
                    }
                  },
                  "client_secret": "Qm9vdHN0cmFwLXNlY3JldC1leGFtcGxlLW5vdC1yZWFsLTEyMzQ1Ng",
                  "callback_token": "c2FtcGxlLWNhbGxiYWNrLXRva2VuLTk4NzY1"
                }
              }
            }
          },
          "400": {
            "description": "`channel` is not an object.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "channel must be an object",
                  "status": 400
                }
              }
            }
          },
          "401": {
            "description": "The request is not authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Authentication required",
                  "status": 401
                }
              }
            }
          },
          "403": {
            "description": "The caller is neither the account that owns the MCP server nor a user of that account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Forbidden",
                  "status": 403
                }
              }
            }
          },
          "404": {
            "description": "No MCP server with that hostname.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "MCP server not found",
                  "status": 404
                }
              }
            }
          },
          "409": {
            "description": "The server already has a managed agent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "This server already has a managed agent; detach it first",
                  "status": 409
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "description": "The managed MCP server's hostname: the subdomain it answers on.",
            "schema": {
              "type": "string"
            },
            "example": "acme-weather"
          }
        ]
      },
      "put": {
        "tags": [
          "MCP Tools"
        ],
        "summary": "Update the managed agent",
        "description": "Changes the agent's channel settings, or records the connection state the agent host reports. Only the fields sent are changed. Setting `status` does not revoke the credential; detach the agent to do that.",
        "operationId": "putV4McpServersByHostnameAgent",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "channel": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Free-form settings the agent host needs to reach buyers, such as the channel `type` and a phone `number` or allowed origins. Stored and returned as given. Replaces the stored value."
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "pending",
                      "connected",
                      "failed",
                      "disabled"
                    ]
                  },
                  "status_detail": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Send `null` to clear."
                  },
                  "external_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Send `null` to clear."
                  }
                }
              },
              "examples": {
                "connected": {
                  "summary": "Agent host reports success",
                  "value": {
                    "status": "connected",
                    "external_id": "wa-agent-81234",
                    "status_detail": null
                  }
                },
                "failed": {
                  "summary": "Agent host reports a failure",
                  "value": {
                    "status": "failed",
                    "status_detail": "WhatsApp number is not verified"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated agent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agent": {
                      "type": "object",
                      "description": "The managed agent attached to an MCP server. Never includes the client secret.",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "mcp_id": {
                          "type": "integer",
                          "description": "The MCP server the agent is bound to."
                        },
                        "client_id": {
                          "type": "string",
                          "description": "OAuth client id of the agent's credential. Always starts with `agent_`."
                        },
                        "channel": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "Free-form settings the agent host needs to reach buyers, such as the channel `type` and a phone `number` or allowed origins. Stored and returned as given."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "connected",
                            "failed",
                            "disabled"
                          ],
                          "description": "Connection state as last reported by the agent host. `pending` until it reports."
                        },
                        "status_detail": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Free-text detail accompanying `status`, e.g. an error."
                        },
                        "external_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The agent host's own identifier for this agent."
                        },
                        "created_at": {
                          "type": "string",
                          "pattern": "^[0-9]+$",
                          "description": "Unix time in seconds, serialized as a decimal string."
                        },
                        "updated_at": {
                          "type": "string",
                          "pattern": "^[0-9]+$",
                          "description": "Unix time in seconds, serialized as a decimal string."
                        },
                        "connection": {
                          "type": "object",
                          "description": "Everything the agent host needs to connect, apart from the client secret.",
                          "properties": {
                            "client_id": {
                              "type": "string"
                            },
                            "token_url": {
                              "type": "string",
                              "format": "uri",
                              "description": "Token endpoint. Exchange the client id and secret here (client-credentials grant, HTTP Basic)."
                            },
                            "resource": {
                              "type": "string",
                              "format": "uri",
                              "description": "Resource indicator to request the token for: the MCP server's origin."
                            },
                            "mcp_url": {
                              "type": "string",
                              "format": "uri",
                              "description": "Where the MCP server answers."
                            }
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "agent"
                  ]
                },
                "example": {
                  "agent": {
                    "id": 7,
                    "mcp_id": 12,
                    "client_id": "agent_3f9a1c7e2b5d8046a1e9c3b7",
                    "channel": {
                      "type": "whatsapp",
                      "number": "+14165550123"
                    },
                    "status": "connected",
                    "status_detail": null,
                    "external_id": "wa-agent-81234",
                    "created_at": "1758290400",
                    "updated_at": "1758294012",
                    "connection": {
                      "client_id": "agent_3f9a1c7e2b5d8046a1e9c3b7",
                      "token_url": "https://pay.m.todaq.net/v4/oidc/token",
                      "resource": "https://acme-weather.mcp.m.todaq.net",
                      "mcp_url": "https://acme-weather.mcp.m.todaq.net"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`channel` is not an object, or `status` is not one of the allowed values.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "status must be one of pending, connected, failed, disabled",
                  "status": 400
                }
              }
            }
          },
          "401": {
            "description": "The request is not authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Authentication required",
                  "status": 401
                }
              }
            }
          },
          "403": {
            "description": "The caller is neither the account that owns the MCP server nor a user of that account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Forbidden",
                  "status": 403
                }
              }
            }
          },
          "404": {
            "description": "No MCP server with that hostname, or the server has no managed agent attached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "noServer": {
                    "summary": "Unknown server",
                    "value": {
                      "error": "MCP server not found",
                      "status": 404
                    }
                  },
                  "noAgent": {
                    "summary": "No agent attached",
                    "value": {
                      "error": "This server has no managed agent",
                      "status": 404
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "description": "The managed MCP server's hostname: the subdomain it answers on.",
            "schema": {
              "type": "string"
            },
            "example": "acme-weather"
          }
        ]
      }
    },
    "/v4/oidc/auth": {
      "get": {
        "tags": [
          "OIDC"
        ],
        "summary": "Start an authorization request",
        "description": "The OAuth 2.0 authorization endpoint. Send the user here to begin the flow; they are redirected onward to sign in and, once they have, back to your `redirect_uri` with an authorization code.\n\nThe resume route `/v4/oidc/auth/{uid}` is where the provider returns after an interaction completes — clients do not call it directly.",
        "operationId": "getV4OidcAuth",
        "security": [],
        "responses": {
          "303": {
            "description": "Redirect onward to sign in, or back to `redirect_uri`.",
            "headers": {
              "Location": {
                "description": "Where to go next.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "`client_id` or `redirect_uri` is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "invalid_request"
                    },
                    "error_description": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "The interaction could not be started."
          }
        },
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "description": "Your registered client.",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "description": "Where to return the user.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uri"
            }
          },
          {
            "name": "response_type",
            "in": "query",
            "description": "Typically `code`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "description": "Space-separated scopes requested.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "description": "Opaque value returned unchanged, to defend against CSRF.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "resource",
            "in": "query",
            "description": "RFC 8707 resource indicator the token should be bound to.",
            "schema": {
              "type": "string",
              "format": "uri"
            }
          }
        ]
      }
    },
    "/v4/oidc/auth/{uid}": {
      "get": {
        "tags": [
          "OIDC"
        ],
        "summary": "Start an authorization request",
        "description": "The OAuth 2.0 authorization endpoint. Send the user here to begin the flow; they are redirected onward to sign in and, once they have, back to your `redirect_uri` with an authorization code.\n\nThe resume route `/v4/oidc/auth/{uid}` is where the provider returns after an interaction completes — clients do not call it directly.",
        "operationId": "getV4OidcAuthByUid",
        "security": [],
        "responses": {
          "303": {
            "description": "Redirect onward to sign in, or back to `redirect_uri`.",
            "headers": {
              "Location": {
                "description": "Where to go next.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "`client_id` or `redirect_uri` is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "invalid_request"
                    },
                    "error_description": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "The interaction could not be started."
          }
        },
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "client_id",
            "in": "query",
            "description": "Your registered client.",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "description": "Where to return the user.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uri"
            }
          },
          {
            "name": "response_type",
            "in": "query",
            "description": "Typically `code`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "description": "Space-separated scopes requested.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "description": "Opaque value returned unchanged, to defend against CSRF.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "resource",
            "in": "query",
            "description": "RFC 8707 resource indicator the token should be bound to.",
            "schema": {
              "type": "string",
              "format": "uri"
            }
          }
        ]
      }
    },
    "/v4/oidc/complete": {
      "post": {
        "tags": [
          "OIDC"
        ],
        "summary": "Complete an interaction (retired)",
        "description": "Retired: the sign-in page now completes the interaction at `POST /v4/oidc/interaction/{uid}`, with the cookies the provider set in the browser that started it. This route answers 410.",
        "operationId": "postV4OidcComplete",
        "deprecated": true,
        "security": [],
        "responses": {
          "410": {
            "description": "Retired. Reload the sign-in page and try again."
          }
        }
      }
    },
    "/v4/oidc/nonce": {
      "post": {
        "tags": [
          "OIDC"
        ],
        "summary": "Send a sign-in code",
        "description": "Emails a one-time code to `email` as part of an OIDC sign-in, and returns the `tonce` that identifies this attempt. Exchange the code with `POST /v4/oidc/nonce/verify`.",
        "operationId": "postV4OidcNonce",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Where to send the code."
                  },
                  "client_id": {
                    "type": "string",
                    "description": "The client the sign-in is for."
                  }
                },
                "required": [
                  "email"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The code was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "created_at": {
                      "type": "string",
                      "description": "The `tonce` identifying this attempt."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`email` is missing or the code could not be issued."
          },
          "500": {
            "description": "The code could not be sent."
          }
        }
      }
    },
    "/v4/oidc/nonce/verify": {
      "post": {
        "tags": [
          "OIDC"
        ],
        "summary": "Redeem a sign-in code",
        "description": "Exchanges the code the user received for their identity, and sets the authentication cookies the rest of the OIDC flow depends on. The user is provisioned on first sign-in.",
        "operationId": "postV4OidcNonceVerify",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "The address the code was sent to."
                  },
                  "code": {
                    "type": "string",
                    "description": "The code the user received."
                  },
                  "tonce": {
                    "type": "string",
                    "description": "The value returned when the code was sent."
                  },
                  "client_id": {
                    "type": "string",
                    "description": "The client the sign-in is for."
                  }
                },
                "required": [
                  "email",
                  "code",
                  "tonce"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The code was accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/User"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "secrets": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          }
                        },
                        "passkey_offer_token": {
                          "type": "string",
                          "description": "Present when the user should be offered a passkey, so they can skip the code next time. It authorizes `POST /v4/oidc/passkey/register/options`, `…/register/verify` and `POST /v4/oidc/passkey/decline` for this user for 15 minutes. Absent once they have a passkey, and between offers after they decline one."
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A field is missing, or the code is wrong or expired."
          },
          "500": {
            "description": "The sign-in could not be completed."
          }
        }
      }
    },
    "/v4/oidc/passkey/decline": {
      "post": {
        "tags": [
          "OIDC"
        ],
        "summary": "Decline the passkey offer",
        "description": "Records the user saying \"not now\" and spends the offer token. They are offered again 15 days later, then 30, 60, 120 and 240 days after each further decline, and after that once a year on the date of their latest decline. Answering the same offer twice counts once.",
        "operationId": "postV4OidcPasskeyDecline",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "offer_token": {
                    "type": "string",
                    "description": "The `passkey_offer_token` from `POST /v4/oidc/nonce/verify`."
                  }
                },
                "required": [
                  "offer_token"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "next_offer_at": {
                      "type": "string",
                      "format": "date-time",
                      "description": "The first moment a sign-in will offer a passkey again."
                    }
                  },
                  "required": [
                    "next_offer_at"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The offer token is missing, spent or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "A machine-readable code."
                    },
                    "error_description": {
                      "type": "string",
                      "description": "A message that can be shown to the user."
                    }
                  },
                  "required": [
                    "error",
                    "error_description"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v4/oidc/passkey/options": {
      "post": {
        "tags": [
          "OIDC"
        ],
        "summary": "Start a passkey sign-in",
        "description": "Returns the options to pass to `navigator.credentials.get()` (as `PublicKeyCredentialRequestOptionsJSON`). They carry a single-use challenge that expires after ten minutes, and no account: the browser offers whichever of the user's passkeys it has. Send the result to `POST /v4/oidc/passkey/verify`.",
        "operationId": "postV4OidcPasskeyOptions",
        "security": [],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "WebAuthn request options.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "challenge": {
                      "type": "string"
                    },
                    "rpId": {
                      "type": "string"
                    },
                    "timeout": {
                      "type": "integer"
                    },
                    "userVerification": {
                      "type": "string",
                      "enum": [
                        "required"
                      ]
                    }
                  },
                  "required": [
                    "challenge",
                    "rpId"
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Passkeys are not configured on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "A machine-readable code."
                    },
                    "error_description": {
                      "type": "string",
                      "description": "A message that can be shown to the user."
                    }
                  },
                  "required": [
                    "error",
                    "error_description"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v4/oidc/passkey/register/options": {
      "post": {
        "tags": [
          "OIDC"
        ],
        "summary": "Start setting up a passkey",
        "description": "Returns the options to pass to `navigator.credentials.create()` (as `PublicKeyCredentialCreationOptionsJSON`) for the user the offer token was issued to. The passkey must be discoverable and user-verifying, because it replaces the emailed code. Passkeys the user already has are listed in `excludeCredentials`, so an authenticator holding one says so instead of making a second.",
        "operationId": "postV4OidcPasskeyRegisterOptions",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "offer_token": {
                    "type": "string",
                    "description": "The `passkey_offer_token` from `POST /v4/oidc/nonce/verify`."
                  }
                },
                "required": [
                  "offer_token"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "WebAuthn creation options.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "The offer token is missing, spent or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "A machine-readable code."
                    },
                    "error_description": {
                      "type": "string",
                      "description": "A message that can be shown to the user."
                    }
                  },
                  "required": [
                    "error",
                    "error_description"
                  ]
                }
              }
            }
          },
          "409": {
            "description": "The user already has as many passkeys as they may.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "A machine-readable code."
                    },
                    "error_description": {
                      "type": "string",
                      "description": "A message that can be shown to the user."
                    }
                  },
                  "required": [
                    "error",
                    "error_description"
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Passkeys are not configured on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "A machine-readable code."
                    },
                    "error_description": {
                      "type": "string",
                      "description": "A message that can be shown to the user."
                    }
                  },
                  "required": [
                    "error",
                    "error_description"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v4/oidc/passkey/register/verify": {
      "post": {
        "tags": [
          "OIDC"
        ],
        "summary": "Finish setting up a passkey",
        "description": "Verifies what `navigator.credentials.create()` returned (as `RegistrationResponseJSON`) and saves the passkey. The offer token is spent once a passkey is saved; until then a failed attempt can be retried with it.",
        "operationId": "postV4OidcPasskeyRegisterVerify",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "offer_token": {
                    "type": "string",
                    "description": "The `passkey_offer_token` from `POST /v4/oidc/nonce/verify`."
                  },
                  "response": {
                    "type": "object",
                    "description": "The credential, as `RegistrationResponseJSON`."
                  }
                },
                "required": [
                  "offer_token",
                  "response"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The passkey was saved.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "passkey": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "rp_id": {
                          "type": "string",
                          "description": "The domain the passkey works on."
                        },
                        "aaguid": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Identifies the kind of authenticator (a password manager, a security key model). All zeros when it does not say."
                        },
                        "device_type": {
                          "type": "string",
                          "enum": [
                            "singleDevice",
                            "multiDevice"
                          ],
                          "description": "`multiDevice` passkeys sync between the user's devices; `singleDevice` ones live on one authenticator."
                        },
                        "backed_up": {
                          "type": "boolean",
                          "description": "Whether the passkey is currently synced or backed up."
                        },
                        "transports": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "last_used_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "id",
                        "rp_id",
                        "device_type",
                        "backed_up",
                        "created_at"
                      ]
                    }
                  },
                  "required": [
                    "passkey"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The challenge is spent or expired (`challenge_expired`), or the credential did not verify (`verification_failed`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "A machine-readable code."
                    },
                    "error_description": {
                      "type": "string",
                      "description": "A message that can be shown to the user."
                    }
                  },
                  "required": [
                    "error",
                    "error_description"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The offer token is missing, spent or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "A machine-readable code."
                    },
                    "error_description": {
                      "type": "string",
                      "description": "A message that can be shown to the user."
                    }
                  },
                  "required": [
                    "error",
                    "error_description"
                  ]
                }
              }
            }
          },
          "409": {
            "description": "That passkey is already registered (`already_registered`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "A machine-readable code."
                    },
                    "error_description": {
                      "type": "string",
                      "description": "A message that can be shown to the user."
                    }
                  },
                  "required": [
                    "error",
                    "error_description"
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Passkeys are not configured on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "A machine-readable code."
                    },
                    "error_description": {
                      "type": "string",
                      "description": "A message that can be shown to the user."
                    }
                  },
                  "required": [
                    "error",
                    "error_description"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v4/oidc/passkey/verify": {
      "post": {
        "tags": [
          "OIDC"
        ],
        "summary": "Finish a passkey sign-in",
        "description": "Verifies what `navigator.credentials.get()` returned (as `AuthenticationResponseJSON`) and answers exactly as `POST /v4/oidc/nonce/verify` does for a correct code: the user and their OAuth client credentials. The user must have verified themselves on the authenticator (biometric or screen lock).\n\nEach challenge works once. `challenge_expired` means start again with `POST /v4/oidc/passkey/options`; `unknown_credential` means the passkey is not, or no longer, registered, and the page may tell the authenticator to forget it.",
        "operationId": "postV4OidcPasskeyVerify",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "client_id": {
                    "type": "string",
                    "description": "The client the sign-in is for."
                  },
                  "response": {
                    "type": "object",
                    "description": "The credential, as `AuthenticationResponseJSON`."
                  }
                },
                "required": [
                  "client_id",
                  "response"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in. The user, with their OAuth client credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "email": {
                      "type": "string",
                      "format": "email"
                    },
                    "secrets": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  },
                  "required": [
                    "id",
                    "email",
                    "secrets"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A field is missing, the challenge is spent or expired (`challenge_expired`), the passkey is not registered (`unknown_credential`), it did not verify (`verification_failed`), or the account may not sign in to this client.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "A machine-readable code."
                    },
                    "error_description": {
                      "type": "string",
                      "description": "A message that can be shown to the user."
                    }
                  },
                  "required": [
                    "error",
                    "error_description"
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Passkeys are not configured on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "A machine-readable code."
                    },
                    "error_description": {
                      "type": "string",
                      "description": "A message that can be shown to the user."
                    }
                  },
                  "required": [
                    "error",
                    "error_description"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v4/openapi.json": {
      "get": {
        "tags": [
          "Meta"
        ],
        "summary": "Get the OpenAPI document",
        "description": "Returns the OpenAPI 3.1 description of the v4 API, generated from the running route table. Point an API client, a code generator or a reference renderer at this URL to stay in step with the deployed version.",
        "operationId": "getV4Openapi.json",
        "security": [],
        "responses": {
          "200": {
            "description": "The OpenAPI document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/v4/pay/notifications/register": {
      "delete": {
        "tags": [
          "Notifications"
        ],
        "summary": "Unregister a device",
        "description": "Clears the stored FCM token so the device stops receiving payment approval prompts. Call it on sign-out, with the device’s own token: the registration is cleared only while that token is still the registered one, so a device signing out never unregisters the device that replaced it. Without `fcmToken`, whatever is registered is cleared. Either way the answer is `200`. The same credential as registering is required.",
        "operationId": "deleteV4PayNotificationsRegister",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fcmToken": {
                    "type": "string",
                    "description": "The token to clear, if it is still the registered one. Without it, the registered one is cleared."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The device was unregistered.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    },
                    "unregisteredAt": {
                      "type": "integer",
                      "description": "Unix time of removal, in milliseconds."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not a user signed in to their own TODAQ app.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The registration could not be cleared.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Notifications"
        ],
        "summary": "Register a device for notifications",
        "description": "Stores the device’s FCM token against the signed-in user so payment approvals can be pushed to it. FCM tokens are reissued by the platform from time to time, so call this whenever the app starts rather than only on first run.\n\nOnly the user’s own TODAQ app may register its device: the device registered here receives the user’s payment and sign-in approval requests, so an account token, an account’s public secret and any OIDC token, the dashboard’s included, are refused. A user has one registered device; registering another replaces it.\n\nThe endpoint is mounted at both `/v4/pay/notifications/register` and `/v4/pay/notifications/unregister`; it is the HTTP method that decides what happens, so register with `POST` and unregister with `DELETE`.",
        "operationId": "postV4PayNotificationsRegister",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fcmToken": {
                    "type": "string",
                    "description": "The device’s Firebase Cloud Messaging token."
                  },
                  "platform": {
                    "type": "string",
                    "default": "mobile",
                    "description": "The device platform."
                  },
                  "deviceId": {
                    "type": "string",
                    "description": "Identifies the device. Derived from the token when omitted."
                  }
                },
                "required": [
                  "fcmToken"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The device was registered.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    },
                    "deviceId": {
                      "type": "string"
                    },
                    "platform": {
                      "type": "string"
                    },
                    "registeredAt": {
                      "type": "integer",
                      "description": "Unix time of registration, in milliseconds."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`fcmToken` is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not a user signed in to their own TODAQ app.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The registration could not be stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/pay/notifications/unregister": {
      "delete": {
        "tags": [
          "Notifications"
        ],
        "summary": "Unregister a device",
        "description": "Clears the stored FCM token so the device stops receiving payment approval prompts. Call it on sign-out, with the device’s own token: the registration is cleared only while that token is still the registered one, so a device signing out never unregisters the device that replaced it. Without `fcmToken`, whatever is registered is cleared. Either way the answer is `200`. The same credential as registering is required.",
        "operationId": "deleteV4PayNotificationsUnregister",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fcmToken": {
                    "type": "string",
                    "description": "The token to clear, if it is still the registered one. Without it, the registered one is cleared."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The device was unregistered.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    },
                    "unregisteredAt": {
                      "type": "integer",
                      "description": "Unix time of removal, in milliseconds."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not a user signed in to their own TODAQ app.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The registration could not be cleared.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Notifications"
        ],
        "summary": "Register a device for notifications",
        "description": "Stores the device’s FCM token against the signed-in user so payment approvals can be pushed to it. FCM tokens are reissued by the platform from time to time, so call this whenever the app starts rather than only on first run.\n\nOnly the user’s own TODAQ app may register its device: the device registered here receives the user’s payment and sign-in approval requests, so an account token, an account’s public secret and any OIDC token, the dashboard’s included, are refused. A user has one registered device; registering another replaces it.\n\nThe endpoint is mounted at both `/v4/pay/notifications/register` and `/v4/pay/notifications/unregister`; it is the HTTP method that decides what happens, so register with `POST` and unregister with `DELETE`.",
        "operationId": "postV4PayNotificationsUnregister",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fcmToken": {
                    "type": "string",
                    "description": "The device’s Firebase Cloud Messaging token."
                  },
                  "platform": {
                    "type": "string",
                    "default": "mobile",
                    "description": "The device platform."
                  },
                  "deviceId": {
                    "type": "string",
                    "description": "Identifies the device. Derived from the token when omitted."
                  }
                },
                "required": [
                  "fcmToken"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The device was registered.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    },
                    "deviceId": {
                      "type": "string"
                    },
                    "platform": {
                      "type": "string"
                    },
                    "registeredAt": {
                      "type": "integer",
                      "description": "Unix time of registration, in milliseconds."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`fcmToken` is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not a user signed in to their own TODAQ app.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The registration could not be stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/payback/button/{hash}": {
      "get": {
        "tags": [
          "Embeds"
        ],
        "summary": "Render a payback button",
        "description": "Paybacks are not currently offered; this endpoint always reports the service as unavailable.",
        "operationId": "getV4PaybackButtonByHash",
        "deprecated": true,
        "security": [],
        "responses": {
          "503": {
            "description": "Paybacks are not available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the payback.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/payback/embed/{hash}": {
      "get": {
        "tags": [
          "Embeds"
        ],
        "summary": "Render a payback embed",
        "description": "Paybacks are not currently offered; this endpoint always reports the service as unavailable.",
        "operationId": "getV4PaybackEmbedByHash",
        "deprecated": true,
        "security": [],
        "responses": {
          "503": {
            "description": "Paybacks are not available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the payback.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/payback/{hash}": {
      "get": {
        "tags": [
          "Transfers"
        ],
        "summary": "Get a payback",
        "description": "Paybacks are not currently offered; this endpoint always reports the service as unavailable.",
        "operationId": "getV4PaybackByHash",
        "deprecated": true,
        "security": [],
        "responses": {
          "503": {
            "description": "Paybacks are not available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the payback.",
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "post": {
        "tags": [
          "Transfers"
        ],
        "summary": "Create a payback",
        "description": "Paybacks are not currently offered; this endpoint always reports the service as unavailable.",
        "operationId": "postV4PaybackByHash",
        "deprecated": true,
        "security": [],
        "responses": {
          "503": {
            "description": "Paybacks are not available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the payback.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/payment/button/{hash}": {
      "get": {
        "tags": [
          "Embeds"
        ],
        "summary": "Render a payment button",
        "description": "Returns the HTML for the payment button on its own, without the surrounding embed chrome. Use it when the host page already provides the layout and only needs the control.",
        "operationId": "getV4PaymentButtonByHash",
        "security": [],
        "responses": {
          "200": {
            "description": "The button document.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "`style` is not a JSON object of CSS properties."
          },
          "404": {
            "description": "No commodity with that hash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Hash of the commodity the button is for.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "style",
            "in": "query",
            "description": "JSON-encoded CSS properties applied to the button.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fixedPoints",
            "in": "query",
            "description": "How many decimal places to show the amount to.",
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v4/payment/cart/{hash}": {
      "get": {
        "tags": [
          "Carts"
        ],
        "summary": "Get a cart to pay",
        "description": "What the hosted checkout page loads for a cart: its items, what it costs and what is left to pay. No authentication is required: the hash in the cart's link is the capability, as a checkout's is, so share it only with the shopper. The cart's `id` does not open it, and the items' inputs are never shown.",
        "operationId": "getV4PaymentCartByHash",
        "security": [],
        "responses": {
          "200": {
            "description": "The cart can be paid, or is partly paid.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "hash": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The cart's link: what it is loaded and paid by."
                    },
                    "revision": {
                      "type": "integer",
                      "description": "Which version of the cart this is. Send it back with every payment: a cart checked out again, or re-priced by a seller, is a new revision, and paying for any other is refused (`cart_changed`)."
                    },
                    "payer": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "link",
                        "agent",
                        null
                      ],
                      "description": "Who is paying for it, once someone has started: a shopper at this link, or the account holder's AI agent."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "partial",
                        "processing",
                        "completed",
                        "closed"
                      ],
                      "description": "`pending`: nothing paid yet. `partial`: some items are paid and the rest can be. `processing`: an item's payment is underway; paying again returns its outcome. `completed`: everything is paid. `closed`: finished with part of it paid; the rest was cancelled."
                    },
                    "lines": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "catalog_item_id": {
                            "type": "integer"
                          },
                          "name": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string"
                          },
                          "descriptor": {
                            "type": "string",
                            "description": "Short label for the item, as shown on receipts."
                          },
                          "merchant": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "unit_price": {
                            "type": "string"
                          },
                          "quantity": {
                            "type": "integer"
                          },
                          "subtotal": {
                            "type": "string"
                          },
                          "units_paid": {
                            "type": "integer"
                          },
                          "receipts": {
                            "type": "array",
                            "description": "Only in answers to the shopper who paid: one per unit of this line they paid for.",
                            "items": {
                              "type": "object",
                              "properties": {
                                "transaction_id": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "format": "uuid"
                                },
                                "amount_charged": {
                                  "type": "string"
                                },
                                "delivery": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "enum": [
                                    "delivered",
                                    "failed",
                                    "skipped",
                                    "pending",
                                    null
                                  ]
                                },
                                "completed_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "format": "date-time"
                                }
                              }
                            }
                          }
                        }
                      }
                    },
                    "item_count": {
                      "type": "integer",
                      "description": "Every unit in the cart: each is paid, and delivered, on its own."
                    },
                    "units_paid": {
                      "type": "integer"
                    },
                    "total": {
                      "type": "string",
                      "description": "What the whole cart costs, to three decimals."
                    },
                    "amount_paid": {
                      "type": "string",
                      "description": "What has been paid so far."
                    },
                    "outstanding": {
                      "type": "string",
                      "description": "What is left to pay, at the prices shown. Zero once closed."
                    },
                    "currency": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "dq": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The digital quantity every item is priced in: what to top up."
                    },
                    "public_secret": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "A selling merchant's publishable key, for the page's sign-in."
                    },
                    "commodity_hash": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass it when funding a wallet by card for this cart. Present only when one merchant sells everything in it, whom it names on the card statement."
                    }
                  }
                },
                "example": {
                  "hash": "d1f8a3c2-6b4e-4f7a-9c0d-2e5b8a1f3c64",
                  "revision": 1,
                  "payer": null,
                  "status": "pending",
                  "lines": [
                    {
                      "id": 41,
                      "catalog_item_id": 128,
                      "name": "Carbonara kit",
                      "description": "Everything for carbonara, serves 4.",
                      "descriptor": "Carbonara kit",
                      "merchant": "La Dispensa",
                      "unit_price": "24.000",
                      "quantity": 2,
                      "subtotal": "48.000",
                      "units_paid": 0
                    }
                  ],
                  "item_count": 2,
                  "units_paid": 0,
                  "total": "48.000",
                  "amount_paid": "0.000",
                  "outstanding": "48.000",
                  "currency": "USD-TDN",
                  "dq": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73",
                  "public_secret": "pub_00000000000000000000000000000000",
                  "commodity_hash": "9d2e6f4a1b7c3e5d8f0a2b4c6d8e0f1a"
                }
              }
            }
          },
          "404": {
            "description": "No cart has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not found",
                  "status": 404
                }
              }
            }
          },
          "409": {
            "description": "The cart is being changed and has not been checked out (again) yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "This cart is not ready to pay yet. Try again in a moment, or ask for a new link.",
                  "code": "cart_not_ready",
                  "state": "open",
                  "status": 409
                }
              }
            }
          },
          "410": {
            "description": "The cart has been paid, was discarded, or was finished part paid (`cart_closed`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Cart already paid",
                  "code": "cart_completed",
                  "status": 410
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the cart's checkout page: the last part of its `url`. Not the cart's `id`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "d1f8a3c2-6b4e-4f7a-9c0d-2e5b8a1f3c64"
          }
        ]
      },
      "post": {
        "tags": [
          "Carts"
        ],
        "summary": "Pay for the next item in a cart",
        "description": "Pays for the next unpaid item in the cart from one of the signed-in shopper's wallets, exactly as `POST /v4/payment/checkout/{hash}` pays a checkout, and answers with that item's receipt as `unit` and the cart as it now stands. **Call it again until `status` is `completed`.** The bearer token must belong to a user (the shopper).\n\nThe body is the checkout's, `twin_hostname`, `email` and `payment_intent_id`, all optional, and the cart's `revision` as the shopper was shown it. If the cart has changed since (checked out again, or a seller changed a price), nothing is paid: the answer is `409` `cart_changed` with the cart as it now is, to show the shopper before they pay again. When the last item is paid, one receipt for the whole cart is emailed; a shopper who stops part way can finish with what is paid (`POST /v4/payment/cart/{hash}/finish`).\n\nEvery answer a checkout gives, this gives for the item being paid, with `line_id` and `cart` added. In particular, while an item's payment is underway the answer is `409` (or `504`) with `state: \"processing\"` and `Retry-After`: wait and send the same request; nothing is paid twice. A wallet that refuses (insufficient funds) is passed through, and the cart stays payable from that item on.\n\nOnce one shopper has paid for an item, the rest of the cart is theirs to pay; anyone else is refused.",
        "operationId": "postV4PaymentCartByHash",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "revision"
                ],
                "properties": {
                  "revision": {
                    "type": "integer",
                    "description": "The cart's `revision` the shopper was shown and agreed to."
                  },
                  "twin_hostname": {
                    "type": "string",
                    "description": "The wallet to pay from. Defaults to the shopper's primary wallet."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Where to send the receipt. Defaults to the shopper's account email."
                  },
                  "payment_intent_id": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "The card payment that funded the wallet for this cart, if any."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An item was paid (`unit`), or there was nothing left to pay. `status` says whether the cart is now `completed`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "partial",
                        "completed"
                      ]
                    },
                    "unit": {
                      "type": "object",
                      "description": "The item just paid: `POST /v4/payment/checkout/{hash}`'s answer, with `line_id`. Absent when nothing was left to pay."
                    },
                    "cart": {
                      "type": "object",
                      "properties": {
                        "hash": {
                          "type": "string",
                          "format": "uuid",
                          "description": "The cart's link: what it is loaded and paid by."
                        },
                        "revision": {
                          "type": "integer",
                          "description": "Which version of the cart this is. Send it back with every payment: a cart checked out again, or re-priced by a seller, is a new revision, and paying for any other is refused (`cart_changed`)."
                        },
                        "payer": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "link",
                            "agent",
                            null
                          ],
                          "description": "Who is paying for it, once someone has started: a shopper at this link, or the account holder's AI agent."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "partial",
                            "processing",
                            "completed",
                            "closed"
                          ],
                          "description": "`pending`: nothing paid yet. `partial`: some items are paid and the rest can be. `processing`: an item's payment is underway; paying again returns its outcome. `completed`: everything is paid. `closed`: finished with part of it paid; the rest was cancelled."
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer"
                              },
                              "catalog_item_id": {
                                "type": "integer"
                              },
                              "name": {
                                "type": "string"
                              },
                              "description": {
                                "type": "string"
                              },
                              "descriptor": {
                                "type": "string",
                                "description": "Short label for the item, as shown on receipts."
                              },
                              "merchant": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "unit_price": {
                                "type": "string"
                              },
                              "quantity": {
                                "type": "integer"
                              },
                              "subtotal": {
                                "type": "string"
                              },
                              "units_paid": {
                                "type": "integer"
                              },
                              "receipts": {
                                "type": "array",
                                "description": "Only in answers to the shopper who paid: one per unit of this line they paid for.",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "transaction_id": {
                                      "type": [
                                        "string",
                                        "null"
                                      ],
                                      "format": "uuid"
                                    },
                                    "amount_charged": {
                                      "type": "string"
                                    },
                                    "delivery": {
                                      "type": [
                                        "string",
                                        "null"
                                      ],
                                      "enum": [
                                        "delivered",
                                        "failed",
                                        "skipped",
                                        "pending",
                                        null
                                      ]
                                    },
                                    "completed_at": {
                                      "type": [
                                        "string",
                                        "null"
                                      ],
                                      "format": "date-time"
                                    }
                                  }
                                }
                              }
                            }
                          }
                        },
                        "item_count": {
                          "type": "integer",
                          "description": "Every unit in the cart: each is paid, and delivered, on its own."
                        },
                        "units_paid": {
                          "type": "integer"
                        },
                        "total": {
                          "type": "string",
                          "description": "What the whole cart costs, to three decimals."
                        },
                        "amount_paid": {
                          "type": "string",
                          "description": "What has been paid so far."
                        },
                        "outstanding": {
                          "type": "string",
                          "description": "What is left to pay, at the prices shown. Zero once closed."
                        },
                        "currency": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "dq": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The digital quantity every item is priced in: what to top up."
                        }
                      }
                    }
                  },
                  "required": [
                    "success",
                    "status",
                    "cart"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "No `revision` was sent (`revision_required`), the shopper has no wallet to pay from, or their wallet refused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "No bearer token was sent, or it does not belong to a user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No cart has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "An item's payment is underway (retry after `Retry-After`), someone else is paying for this cart (`cart_taken`), it is not checked out (`cart_not_ready`), it is not the revision the shopper saw (`cart_changed`, with the cart as it now is as `cart`), or an item can no longer be bought.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Another shopper paid for the cart, it was discarded, or it was finished part paid (`cart_closed`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The shopper's wallet could not be reached, or the seller would not confirm the price. Nothing was charged for the item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "The item's payment did not settle while this request waited. Retry after `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the cart's checkout page: the last part of its `url`. Not the cart's `id`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "d1f8a3c2-6b4e-4f7a-9c0d-2e5b8a1f3c64"
          }
        ]
      }
    },
    "/v4/payment/cart/{hash}/finish": {
      "post": {
        "tags": [
          "Carts"
        ],
        "summary": "Finish a cart with part of it paid",
        "description": "For a shopper who has paid for some of a cart and cannot, or will not, pay for the rest: the items not yet paid for are cancelled, nothing more is charged, and one receipt for what was bought is emailed. Money added to the wallet for the rest stays in the wallet. The bearer token must belong to the shopper who paid. Finishing a cart twice is not an error.",
        "operationId": "postV4PaymentCartByHashFinish",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The cart is `closed` (or was already), or every item turned out to be paid (`completed`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "closed",
                        "completed"
                      ]
                    },
                    "cart": {
                      "type": "object",
                      "properties": {
                        "hash": {
                          "type": "string",
                          "format": "uuid",
                          "description": "The cart's link: what it is loaded and paid by."
                        },
                        "revision": {
                          "type": "integer",
                          "description": "Which version of the cart this is. Send it back with every payment: a cart checked out again, or re-priced by a seller, is a new revision, and paying for any other is refused (`cart_changed`)."
                        },
                        "payer": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "link",
                            "agent",
                            null
                          ],
                          "description": "Who is paying for it, once someone has started: a shopper at this link, or the account holder's AI agent."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "partial",
                            "processing",
                            "completed",
                            "closed"
                          ],
                          "description": "`pending`: nothing paid yet. `partial`: some items are paid and the rest can be. `processing`: an item's payment is underway; paying again returns its outcome. `completed`: everything is paid. `closed`: finished with part of it paid; the rest was cancelled."
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer"
                              },
                              "catalog_item_id": {
                                "type": "integer"
                              },
                              "name": {
                                "type": "string"
                              },
                              "description": {
                                "type": "string"
                              },
                              "descriptor": {
                                "type": "string",
                                "description": "Short label for the item, as shown on receipts."
                              },
                              "merchant": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "unit_price": {
                                "type": "string"
                              },
                              "quantity": {
                                "type": "integer"
                              },
                              "subtotal": {
                                "type": "string"
                              },
                              "units_paid": {
                                "type": "integer"
                              },
                              "receipts": {
                                "type": "array",
                                "description": "Only in answers to the shopper who paid: one per unit of this line they paid for.",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "transaction_id": {
                                      "type": [
                                        "string",
                                        "null"
                                      ],
                                      "format": "uuid"
                                    },
                                    "amount_charged": {
                                      "type": "string"
                                    },
                                    "delivery": {
                                      "type": [
                                        "string",
                                        "null"
                                      ],
                                      "enum": [
                                        "delivered",
                                        "failed",
                                        "skipped",
                                        "pending",
                                        null
                                      ]
                                    },
                                    "completed_at": {
                                      "type": [
                                        "string",
                                        "null"
                                      ],
                                      "format": "date-time"
                                    }
                                  }
                                }
                              }
                            }
                          }
                        },
                        "item_count": {
                          "type": "integer",
                          "description": "Every unit in the cart: each is paid, and delivered, on its own."
                        },
                        "units_paid": {
                          "type": "integer"
                        },
                        "total": {
                          "type": "string",
                          "description": "What the whole cart costs, to three decimals."
                        },
                        "amount_paid": {
                          "type": "string",
                          "description": "What has been paid so far."
                        },
                        "outstanding": {
                          "type": "string",
                          "description": "What is left to pay, at the prices shown. Zero once closed."
                        },
                        "currency": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "dq": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The digital quantity every item is priced in: what to top up."
                        }
                      }
                    }
                  },
                  "required": [
                    "success",
                    "status",
                    "cart"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "No bearer token was sent, or it does not belong to a user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No cart has that hash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Nothing of it has been paid by this shopper (`nothing_paid`), someone else is paying for it (`cart_taken`), an item's payment is still going through (`payment_in_progress`), or it is not checked out (`cart_not_ready`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "The cart was discarded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the cart's checkout page: the last part of its `url`. Not the cart's `id`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "d1f8a3c2-6b4e-4f7a-9c0d-2e5b8a1f3c64"
          }
        ]
      }
    },
    "/v4/payment/checkout": {
      "post": {
        "tags": [
          "Checkout"
        ],
        "summary": "Create a checkout",
        "description": "Creates a checkout for one active catalog item and returns a link to the hosted checkout page. Send the shopper to `url`; nothing is charged until they complete the purchase there.\n\nEach checkout is paid at most once. Create a new checkout for every purchase rather than reusing a link.",
        "operationId": "postV4PaymentCheckout",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "catalog_item_id": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "The catalog item being sold. It must be active."
                  },
                  "arguments": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "The item's inputs (its `service_schema`), keyed by property name, as collected from the shopper before the link is made — the checkout page does not ask for them. Every required input must be present. When the checkout is paid, they are sent to the item's `external_endpoint`. Keys the schema does not declare are dropped. A value of the wrong type is refused."
                  }
                },
                "required": [
                  "catalog_item_id"
                ]
              },
              "example": {
                "catalog_item_id": 128,
                "arguments": {
                  "city": "Toronto"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The checkout was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "hash": {
                      "type": "string",
                      "format": "uuid",
                      "description": "Identifies the checkout in the other checkout endpoints."
                    },
                    "url": {
                      "type": "string",
                      "format": "uri",
                      "description": "The hosted checkout page for this checkout. Send the shopper here."
                    }
                  },
                  "required": [
                    "hash",
                    "url"
                  ]
                },
                "example": {
                  "hash": "5f0c7c1e-8a4b-4d2f-9c61-3b7e2a9d4f10",
                  "url": "https://checkout.m.todaq.net/checkout/5f0c7c1e-8a4b-4d2f-9c61-3b7e2a9d4f10"
                }
              }
            }
          },
          "400": {
            "description": "`catalog_item_id` is missing, not a positive integer, or not an active catalog item; or `arguments` is not an object, lacks a required input, or has a value of the wrong type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingArguments": {
                    "summary": "A required input is missing",
                    "value": {
                      "error": "Missing required details: city",
                      "status": 400,
                      "missing_arguments": [
                        "city"
                      ]
                    }
                  },
                  "missing": {
                    "summary": "Missing item",
                    "value": {
                      "error": "catalog_item_id is required",
                      "status": 400
                    }
                  },
                  "invalid": {
                    "summary": "Not a positive integer",
                    "value": {
                      "error": "catalog_item_id must be a positive integer",
                      "status": 400
                    }
                  },
                  "inactive": {
                    "summary": "Unknown or inactive item",
                    "value": {
                      "error": "Catalog item not found or inactive",
                      "status": 400
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "No valid bearer token was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Forbidden",
                  "status": 403
                }
              }
            }
          }
        }
      }
    },
    "/v4/payment/checkout/{hash}": {
      "get": {
        "tags": [
          "Checkout"
        ],
        "summary": "Get a checkout",
        "description": "Returns what the checkout sells, for rendering a checkout page. This is what the hosted checkout page loads; call it yourself only if you build your own checkout UI. No authentication is required: the checkout hash is the capability, so share it only with the shopper.\n\nA checkout whose payment is underway still loads, with `status` `processing`, so a page reloaded mid-payment can pick up where it left off. Once a checkout has been paid it answers `410`.",
        "operationId": "getV4PaymentCheckoutByHash",
        "security": [],
        "responses": {
          "200": {
            "description": "The checkout is open.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "cost": {
                      "type": "number",
                      "description": "Price of the item, denominated in `dq`."
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Name of the item."
                    },
                    "description": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Longer description of the item."
                    },
                    "descriptor": {
                      "type": "string",
                      "description": "Short label for the item, as shown on receipts."
                    },
                    "dq": {
                      "$ref": "#/components/schemas/DigitalQuantityHash"
                    },
                    "commodity_hash": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Identifies the commodity behind the catalog item. Pass it when funding a wallet by card for this purchase, so the charge is linked to what was bought."
                    },
                    "public_secret": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The selling merchant's publishable key, for client-side calls made on the merchant's behalf. It is designed to be exposed in a browser and grants no access to the merchant account."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "processing"
                      ],
                      "description": "`pending`: awaiting payment. `processing`: a payment is underway; completing the checkout again returns its outcome."
                    }
                  },
                  "required": [
                    "cost",
                    "descriptor",
                    "dq",
                    "status"
                  ]
                },
                "example": {
                  "cost": 2.5,
                  "name": "Premium article",
                  "description": "Unlimited access to one premium article.",
                  "descriptor": "Premium article",
                  "dq": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73",
                  "commodity_hash": "9d2e6f4a1b7c3e5d8f0a2b4c6d8e0f1a",
                  "public_secret": "pub_00000000000000000000000000000000",
                  "status": "pending"
                }
              }
            }
          },
          "404": {
            "description": "No checkout has that hash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not found",
                  "status": 404
                }
              }
            }
          },
          "410": {
            "description": "The checkout has already been paid, or has expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Checkout no longer available",
                  "status": 410
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the checkout. Returned as `hash` when the checkout is created.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "5f0c7c1e-8a4b-4d2f-9c61-3b7e2a9d4f10"
          }
        ]
      },
      "post": {
        "tags": [
          "Checkout"
        ],
        "summary": "Complete a checkout",
        "description": "Pays for the checkout from one of the signed-in shopper's wallets and returns a receipt. The hosted checkout page calls this once the shopper has signed in and, if needed, topped up their wallet. The bearer token must belong to a **user** (the shopper); an account token is refused.\n\nThe wallet paid from is `twin_hostname` when given, otherwise the shopper's primary wallet. A receipt is emailed to `email` when given, otherwise to the address on the shopper's account.\n\n**Retries are safe.** Completing a checkout waits for the payment to settle, which can take a while. If the request times out or the connection drops, send the same request again:\n\n- While the payment is still underway the answer is `409` (or `504` if this request itself waited too long) with `state: \"processing\"` and a `Retry-After` header. Wait that many seconds and retry.\n- Once it has settled, the shopper who paid gets `200` with the original receipt, however many times they ask. No second payment is made and no second receipt is emailed.\n- Anyone else gets `410`.\n\nIf the payment is refused by the shopper's wallet (for example, insufficient funds), that error is passed through with the wallet's status code, usually `400`, and the checkout stays open so it can be paid once the problem is fixed.",
        "operationId": "postV4PaymentCheckoutByHash",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "twin_hostname": {
                    "type": "string",
                    "description": "The wallet to pay from. It must belong to the shopper and cannot be an agent wallet. Defaults to the shopper's primary wallet."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Where to send the receipt. Defaults to the shopper's account email."
                  },
                  "payment_intent_id": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "The card payment that funded the wallet for this purchase, if any. Recorded on the checkout for reconciliation."
                  }
                }
              },
              "example": {
                "twin_hostname": "00000000000000000000000000000000.micro.biz.todaq.net",
                "email": "shopper@example.com",
                "payment_intent_id": "pi_0000000000000000000000"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The checkout is paid. Returned both for the payment itself and for any retry by the shopper who paid.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "completed"
                      ]
                    },
                    "transaction_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid",
                      "description": "Identifies the payment. Quote it when reconciling or contacting support."
                    },
                    "amount_charged": {
                      "type": "string",
                      "description": "What was charged, as a decimal string with three places."
                    },
                    "currency": {
                      "type": "string",
                      "description": "The currency `amount_charged` is in."
                    },
                    "completed_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time",
                      "description": "When the checkout was paid."
                    },
                    "receipt": {
                      "type": "object",
                      "properties": {
                        "amount": {
                          "type": "number",
                          "description": "What was charged."
                        },
                        "descriptor": {
                          "type": "string",
                          "description": "Short label for the item."
                        },
                        "name": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Name of the item."
                        },
                        "merchant": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Name of the merchant paid."
                        },
                        "transaction_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        }
                      }
                    },
                    "delivery": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "delivered",
                        "failed",
                        "skipped",
                        "pending",
                        null
                      ],
                      "description": "Whether the merchant's endpoint was called for this sale. `failed` means the payment settled but the merchant did not answer successfully."
                    },
                    "result": {
                      "description": "What the merchant's endpoint returned: what the shopper paid for. Present only on the response to the request that paid, and only when the merchant answered successfully."
                    }
                  },
                  "required": [
                    "success",
                    "status",
                    "amount_charged",
                    "currency",
                    "receipt"
                  ]
                },
                "example": {
                  "success": true,
                  "status": "completed",
                  "transaction_id": "b6a4d0e2-1f3c-4e8a-9d7b-2c5f8e1a0b93",
                  "amount_charged": "2.500",
                  "currency": "USD-TDN",
                  "completed_at": "2026-09-24T14:03:11.482Z",
                  "receipt": {
                    "amount": 2.5,
                    "descriptor": "Premium article",
                    "name": "Premium article",
                    "merchant": "Example News",
                    "transaction_id": "b6a4d0e2-1f3c-4e8a-9d7b-2c5f8e1a0b93"
                  }
                }
              }
            }
          },
          "400": {
            "description": "The shopper has no wallet to pay from, `twin_hostname` is not one of their wallets, or their wallet refused the payment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "noWallet": {
                    "summary": "Shopper has no primary wallet",
                    "value": {
                      "error": "No payment wallet found",
                      "status": 400
                    }
                  },
                  "wrongWallet": {
                    "summary": "`twin_hostname` is not the shopper's",
                    "value": {
                      "error": "Payment wallet not found",
                      "status": 400
                    }
                  },
                  "refused": {
                    "summary": "The wallet refused the payment",
                    "value": {
                      "error": "Payment failed",
                      "status": 400
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "No bearer token was sent, or it does not belong to a user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Forbidden",
                  "status": 403
                }
              }
            }
          },
          "404": {
            "description": "No checkout has that hash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not found",
                  "status": 404
                }
              }
            }
          },
          "409": {
            "description": "A payment for this checkout is already underway (retry after `Retry-After` seconds), the item can no longer be bought, or the link lacks an input the item now requires or has one its endpoint can no longer carry.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying the same request.",
                "schema": {
                  "type": "integer",
                  "example": 5
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "status": {
                      "type": "integer"
                    },
                    "state": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ],
                      "description": "Present when the payment is underway. Retry the same request; it will not pay twice."
                    }
                  },
                  "required": [
                    "error",
                    "status"
                  ]
                },
                "examples": {
                  "processing": {
                    "summary": "Payment underway: retry later",
                    "description": "Sent with `Retry-After`. Retry the same request; it will not pay twice.",
                    "value": {
                      "error": "This payment is still being processed. Try again shortly; you will not be charged twice.",
                      "status": 409,
                      "state": "processing"
                    }
                  },
                  "unavailable": {
                    "summary": "Item withdrawn since the checkout was created",
                    "value": {
                      "error": "This item is no longer available",
                      "status": 409
                    }
                  },
                  "inputsChanged": {
                    "summary": "The item's inputs changed since the checkout was created",
                    "description": "The link lacks an input the item now requires. Nothing was charged; create a new checkout with it.",
                    "value": {
                      "error": "This item now needs details the link does not have: city",
                      "status": 409,
                      "missing_arguments": [
                        "city"
                      ]
                    }
                  },
                  "inputsUnusable": {
                    "summary": "The item's endpoint changed since the checkout was created",
                    "description": "The item's endpoint now carries an input in its path, and the link's value cannot go there (empty, `.` or `..`). Nothing was charged; create a new checkout with another value.",
                    "value": {
                      "error": "This item can no longer use details the link has: city",
                      "status": 409,
                      "unusable_arguments": [
                        "city"
                      ]
                    }
                  }
                }
              }
            }
          },
          "410": {
            "description": "The checkout was paid by someone else (or the request is not authenticated as the shopper who paid), or it has expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "completed": {
                    "summary": "Already paid",
                    "value": {
                      "error": "Checkout already completed",
                      "status": 410
                    }
                  },
                  "expired": {
                    "summary": "Expired",
                    "value": {
                      "error": "Checkout has expired",
                      "status": 410
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "The shopper's wallet could not be reached. Nothing was charged and the checkout is still open.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Payment service unreachable",
                  "status": 502
                }
              }
            }
          },
          "504": {
            "description": "The payment did not settle while this request waited. It may still complete: retry the same request after `Retry-After` seconds to get the outcome. The shopper will not be charged twice.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying the same request.",
                "schema": {
                  "type": "integer",
                  "example": 5
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "status": {
                      "type": "integer"
                    },
                    "state": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ],
                      "description": "Present when the payment is underway. Retry the same request; it will not pay twice."
                    }
                  },
                  "required": [
                    "error",
                    "status"
                  ]
                },
                "example": {
                  "error": "The payment is taking longer than expected. Try again shortly; you will not be charged twice.",
                  "status": 504,
                  "state": "processing"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the checkout. Returned as `hash` when the checkout is created.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "5f0c7c1e-8a4b-4d2f-9c61-3b7e2a9d4f10"
          }
        ]
      }
    },
    "/v4/payment/embed/{hash}": {
      "get": {
        "tags": [
          "Embeds"
        ],
        "summary": "Render a payment embed",
        "description": "Returns the HTML for the payment iframe for this commodity.",
        "operationId": "getV4PaymentEmbedByHash",
        "security": [],
        "responses": {
          "200": {
            "description": "The embed document.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "`theme` is not `light` or `dark`."
          },
          "404": {
            "description": "No commodity with that hash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Hash of the commodity the embed is for.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "theme",
            "in": "query",
            "description": "Colour theme to render with.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/payment/{hash}": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Pay for a commodity",
        "description": "Charges the commodity’s price to the paying twin and transfers it to the twin that owns the commodity.\n\nThe call checks the payer’s balance first and refuses with `402` if it will not cover the cost. On success a receipt is emailed to the payer and a single-use validation nonce is returned: hand it to the seller, who confirms it through `POST /v4/payment/{hash}/validate` before releasing the goods.",
        "operationId": "postV4PaymentByHash",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hostname": {
                    "type": "string",
                    "description": "Hostname of the twin paying for the commodity.",
                    "example": "00000000000000000000000000000000.micro.biz.todaq.net"
                  }
                },
                "required": [
                  "hostname"
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The transfer was accepted. The nonce and timestamp together identify this purchase for validation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "nonce": {
                      "type": "string",
                      "format": "uuid",
                      "description": "Single-use proof of purchase."
                    },
                    "timestamp": {
                      "type": "integer",
                      "description": "Unix time the validation was created."
                    }
                  },
                  "required": [
                    "nonce",
                    "timestamp"
                  ]
                },
                "example": {
                  "nonce": "b2b0d2c6-1f4e-4a90-9a1e-2c9a4a7f0f11",
                  "timestamp": 1718467200
                }
              }
            }
          },
          "402": {
            "description": "The paying twin’s balance does not cover the cost.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "balance": {
                      "type": "number",
                      "description": "What the twin actually holds."
                    },
                    "cost": {
                      "type": "number",
                      "description": "What the commodity costs."
                    },
                    "status": {
                      "type": "integer"
                    }
                  }
                },
                "example": {
                  "error": "Insufficient balance",
                  "balance": 10,
                  "cost": 25,
                  "status": 402
                }
              }
            }
          },
          "403": {
            "description": "The caller may not spend from this twin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The balance could not be read, or the transfer failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Hash of the commodity being bought.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/payment/{hash}/validate": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Look up a purchase validation",
        "description": "Reads the validation created by a payment without consuming it. Use this to check whether a nonce is genuine and whether it has already been redeemed — `used_at` is set once it has.",
        "operationId": "getV4PaymentByHashValidate",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The validation record.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "nonce": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "created_at": {
                      "type": "integer",
                      "description": "Unix time the purchase was made."
                    },
                    "expires_at": {
                      "type": "integer",
                      "description": "Unix time the nonce stops being redeemable."
                    },
                    "used_at": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "Unix time the nonce was redeemed, or null if it is still unused."
                    },
                    "twin_id": {
                      "type": "integer"
                    },
                    "cost": {
                      "type": "number",
                      "description": "What was paid."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not read this validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No validation matches that nonce, timestamp and commodity.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Hash of the commodity the purchase was for.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "nonce",
            "in": "query",
            "description": "The nonce returned by the payment.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "timestamp",
            "in": "query",
            "description": "The timestamp returned alongside the nonce.",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      },
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Redeem a purchase validation",
        "description": "Consumes the nonce, marking the purchase as fulfilled. A nonce can be redeemed once; a second attempt returns `400`. Call this at the point you hand over what was bought.",
        "operationId": "postV4PaymentByHashValidate",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nonce": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The nonce returned by the payment."
                  },
                  "timestamp": {
                    "type": "integer",
                    "description": "The timestamp returned alongside the nonce."
                  }
                },
                "required": [
                  "nonce",
                  "timestamp"
                ]
              },
              "example": {
                "nonce": "b2b0d2c6-1f4e-4a90-9a1e-2c9a4a7f0f11",
                "timestamp": 1718467200
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The nonce was redeemed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "An empty object."
                },
                "example": {}
              }
            }
          },
          "400": {
            "description": "No unused validation matches that nonce, timestamp and commodity.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller may not redeem this validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The validation could not be marked as used.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Hash of the commodity the purchase was for.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/paywall": {
      "get": {
        "tags": [
          "Paywall"
        ],
        "summary": "Pay for and call a service (query form)",
        "description": "The same flow as `POST /v4/paywall`, with the parameters in the query string instead of a body, and the upstream always called with `GET`. Use it where a request body is awkward — an image or link that has to be fetchable by URL alone.",
        "operationId": "getV4Paywall",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionId": []
          }
        ],
        "responses": {
          "200": {
            "description": "The upstream service’s response, passed through unchanged.",
            "content": {
              "application/json": {
                "schema": {
                  "description": "Whatever the upstream service returned."
                }
              }
            }
          },
          "400": {
            "description": "`amount` or `serviceUrl` is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No usable authentication.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Nothing was charged. The credential acts for an account, or is an OIDC token, which buys only a catalog item by POST with the user’s approval.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The twin node or the upstream service failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "amount",
            "in": "query",
            "description": "What to charge, in the smallest unit of the payer’s digital quantity.",
            "required": true,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "serviceUrl",
            "in": "query",
            "description": "The upstream endpoint being paid for.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uri"
            }
          }
        ]
      },
      "post": {
        "tags": [
          "Paywall"
        ],
        "summary": "Pay for and call a service",
        "description": "Charges `amount` and forwards the request to `serviceUrl`, returning the upstream response.\n\nHow the payment is authorized depends on how the caller arrived:\n\n- **The user’s own TODAQ app** (its bearer token) — charged   immediately from the user’s agent wallet, with no second factor.\n- **An app the user connected** (an OIDC token) — only a   `catalog_item_id`, and only with the user’s `approval_id` for that   purchase (`POST /v4/agent/approvals`); otherwise `403 approval_required`.\n- **A session** (`public_secret` plus `X-Session-ID`) — deducted from   the remaining budget set by `POST /v4/paywall/preauthorization`, or,   without one, a push notification is sent to the user’s device and   the call blocks until they approve or deny it.\n\nAn account’s bearer token, or a managed agent’s, pays as no user and is refused with `403`.\n\nAny properties beyond `amount`, `serviceUrl` and `serviceMethod` are passed through to the upstream service untouched.\n\n**Buying a catalog item.** Name it with `catalog_item_id` instead of stating `amount` and `serviceUrl`: the price and destination come from the catalog. The other properties are the arguments for the item, flat or wrapped in `additionalParams`, and are checked against its input schema before anything is charged: arguments it does not declare are dropped, and a missing required input, a value of the wrong type, one outside its `enum`, or one its endpoint carries in its path that is empty, `.` or `..` is refused with a `400` carrying `charged: false` and a `problems` list. Nothing is authorised or recorded, and no wallet is touched.",
        "operationId": "postV4Paywall",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionId": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "catalog_item_id": {
                    "type": "integer",
                    "description": "The catalog item being bought. When given, `amount` and `serviceUrl` are ignored and the item’s own price and destination are used."
                  },
                  "additionalParams": {
                    "type": "object",
                    "description": "The item’s arguments, wrapped. When absent, the other top-level properties are the arguments."
                  },
                  "amount": {
                    "type": "number",
                    "description": "What to charge, in the smallest unit of the payer’s digital quantity."
                  },
                  "serviceUrl": {
                    "type": "string",
                    "format": "uri",
                    "description": "The upstream endpoint being paid for."
                  },
                  "serviceMethod": {
                    "type": "string",
                    "enum": [
                      "GET",
                      "POST"
                    ],
                    "default": "POST",
                    "description": "How the twin should call the upstream service."
                  }
                },
                "additionalProperties": {
                  "description": "Forwarded to the upstream service as-is; for a catalog item, an argument checked against the item’s input schema."
                }
              },
              "examples": {
                "catalogItem": {
                  "summary": "Buy a catalog item",
                  "value": {
                    "catalog_item_id": 128,
                    "latitude": 43.65,
                    "unitScale": "METRIC"
                  }
                },
                "legacy": {
                  "summary": "State an amount and destination (deprecated)",
                  "value": {
                    "amount": 0.05,
                    "serviceUrl": "https://api.example.com/summarize",
                    "serviceMethod": "POST",
                    "text": "The quick brown fox…"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The payment settled and the upstream service responded. The body is that service’s response, passed through unchanged; what was charged is reported in headers.",
            "headers": {
              "X-Transaction-Id": {
                "description": "The id this sale is recorded under. For a `catalog_item_id` call it is the id on the merchant’s purchase record and the checkout receipt.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Amount-Charged": {
                "description": "What was charged, as a decimal string with three places (`\"1.000\"`).",
                "schema": {
                  "type": "string"
                }
              },
              "X-Currency": {
                "description": "The currency charged in. Always `USD-TDN`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "description": "Whatever the upstream service returned."
                }
              }
            }
          },
          "400": {
            "description": "Nothing was charged. For a catalog item: `catalog_item_id` is invalid or the item cannot be sold, or the arguments do not satisfy its input schema or cannot fill its endpoint, in which case `problems` names each argument at fault and `charged` is `false`. For the legacy shape: `amount` or `serviceUrl` is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable description of what went wrong."
                    },
                    "status": {
                      "type": "integer",
                      "description": "The HTTP status code, repeated in the body."
                    },
                    "problems": {
                      "type": "array",
                      "description": "Each argument the item refuses: one its input schema does not allow, or one its endpoint could not carry in its path. Names the argument, never the value supplied. Present when the arguments were refused.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "argument": {
                            "type": "string",
                            "description": "The argument’s name."
                          },
                          "problem": {
                            "type": "string",
                            "description": "What is wrong with it, reading on from its name."
                          }
                        },
                        "required": [
                          "argument",
                          "problem"
                        ]
                      }
                    },
                    "charged": {
                      "type": "boolean",
                      "description": "`false`: the arguments were refused before any payment was authorised. Present when the arguments were refused."
                    }
                  },
                  "required": [
                    "error"
                  ]
                },
                "examples": {
                  "invalidArguments": {
                    "summary": "The arguments do not fit the item",
                    "value": {
                      "error": "Invalid arguments: latitude is required; unitScale must be one of: METRIC, IMPERIAL",
                      "problems": [
                        {
                          "argument": "latitude",
                          "problem": "is required"
                        },
                        {
                          "argument": "unitScale",
                          "problem": "must be one of: METRIC, IMPERIAL"
                        }
                      ],
                      "charged": false,
                      "status": 400
                    }
                  },
                  "unusableArgument": {
                    "summary": "An input the item’s endpoint cannot carry in its path",
                    "value": {
                      "error": "Invalid arguments: lat cannot be used in the endpoint",
                      "problems": [
                        {
                          "argument": "lat",
                          "problem": "cannot be used in the endpoint"
                        }
                      ],
                      "charged": false,
                      "status": 400
                    }
                  },
                  "inactive": {
                    "summary": "The item is not on sale",
                    "value": {
                      "error": "Catalog item is not active",
                      "status": 400
                    }
                  },
                  "missingAmount": {
                    "summary": "Legacy shape without an amount",
                    "value": {
                      "error": "Missing required parameter: amount",
                      "status": 400
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No usable authentication. The `WWW-Authenticate` header points at the OIDC authorization endpoint to use.",
            "headers": {
              "WWW-Authenticate": {
                "description": "Bearer challenge naming the authorization and resource-metadata URIs.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Nothing was charged. The credential acts for an account (an account or managed agent token), or a connected app named no approved purchase (`approval_required`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The payer has no twin to pay from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "No payment type was available for the payer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The twin node or the upstream service failed. When a catalog item’s payment had already settled and only the delivery behind it failed, the body says so with `charged: true` and the `purchase_id` the sale is recorded under, and the charge headers report what was charged, as on a 200: do not retry, which would be a second purchase.",
            "headers": {
              "X-Transaction-Id": {
                "description": "The id this sale is recorded under. Present only when `charged` is `true`.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Amount-Charged": {
                "description": "What was charged, as a decimal string with three places (`\"1.000\"`). Present only when `charged` is `true`.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Currency": {
                "description": "The currency charged in. Present only when `charged` is `true`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable description of what went wrong."
                    },
                    "status": {
                      "type": "integer",
                      "description": "The HTTP status code, repeated in the body."
                    },
                    "details": {
                      "description": "What the twin or fulfilment said."
                    },
                    "charged": {
                      "type": "boolean",
                      "description": "`true`: the payment settled. Present only when it did."
                    },
                    "purchase_id": {
                      "type": [
                        "integer",
                        "string",
                        "null"
                      ],
                      "description": "The settled sale's record, when the payment settled."
                    },
                    "dispatch_status": {
                      "type": "string",
                      "enum": [
                        "failed"
                      ],
                      "description": "Present when the payment settled and delivery failed."
                    }
                  },
                  "required": [
                    "error"
                  ]
                },
                "examples": {
                  "paidNotDelivered": {
                    "summary": "Paid, but delivery failed",
                    "value": {
                      "error": "Paid, but delivery failed: the payment settled and is recorded as purchase 4182, but the item was not delivered.",
                      "status": 502,
                      "charged": true,
                      "purchase_id": 4182,
                      "dispatch_status": "failed",
                      "details": "The endpoint for this item returned 500. The payment has settled and is recorded."
                    }
                  },
                  "twin": {
                    "summary": "The twin failed",
                    "value": {
                      "error": "Failed to process payment request",
                      "status": 502
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v4/paywall/authorizations": {
      "delete": {
        "tags": [
          "Paywall"
        ],
        "summary": "Revoke an authorization",
        "description": "Withdraws one of the user's own authorizations that is still pending or approved. Payments from that merchant will prompt for approval again. Only the user may revoke, from their own TODAQ app.",
        "operationId": "deleteV4PaywallAuthorizations",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionId": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "authorizationId": {
                    "type": "integer",
                    "description": "The authorization to revoke."
                  }
                },
                "required": [
                  "authorizationId"
                ]
              },
              "example": {
                "authorizationId": 481
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The authorization was revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "message": "Payment authorization revoked successfully"
                }
              }
            }
          },
          "400": {
            "description": "`authorizationId` is missing or not a positive integer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not a user signed in to their own TODAQ app.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The user has no pending or approved authorization with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The authorization could not be revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Paywall"
        ],
        "summary": "List payment authorizations",
        "description": "Returns the authorizations raised for the signed-in user: everything still awaiting a decision, plus those already approved. Denied and expired ones drop out of the list.\n\nCalled from the user's own TODAQ app, the list also carries the AI agent approval requests waiting on them (see `POST /v4/agent/approvals`), in the same shape, with an `aar_` id.",
        "operationId": "getV4PaywallAuthorizations",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionId": []
          }
        ],
        "responses": {
          "200": {
            "description": "The user’s payment authorizations, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "authorizations": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "merchant_url": {
                            "type": "string",
                            "format": "uri"
                          },
                          "merchant_name": {
                            "type": "string"
                          },
                          "payment_type_hash": {
                            "$ref": "#/components/schemas/DigitalQuantityHash"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "pending",
                              "approved",
                              "denied",
                              "revoked"
                            ]
                          },
                          "authorized_at": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "Unix time the user decided."
                          },
                          "expires_at": {
                            "type": "integer",
                            "description": "Unix time the request stops being answerable."
                          },
                          "created_at": {
                            "type": "integer"
                          },
                          "metadata": {
                            "type": "object",
                            "description": "Details of the purchase being authorized."
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "authorizations"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The caller is not a user signed in to their own TODAQ app.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The authorizations could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Paywall"
        ],
        "summary": "Approve or deny an authorization",
        "description": "Answers a pending authorization. Each authorization is one purchase: approving it lets that purchase go ahead, and nothing else. The user is notified of the outcome either way.\n\nAn authorization can only be answered once, and only before it expires.\n\nOnly the user may answer, from their own TODAQ app. A token from the OIDC provider, such as the one an MCP server holds while it asks for a purchase, is refused, as is an account token.",
        "operationId": "putV4PaywallAuthorizations",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionId": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "authorizationId": {
                    "description": "The authorization being answered, or an AI agent approval request's `aar_` id: the app answers both the same way, and the first answer to a request, here or at its approval link, stands.",
                    "oneOf": [
                      {
                        "type": "integer"
                      },
                      {
                        "type": "string"
                      }
                    ]
                  },
                  "approved": {
                    "type": "boolean",
                    "description": "True to approve, false to deny."
                  }
                },
                "required": [
                  "authorizationId",
                  "approved"
                ]
              },
              "example": {
                "authorizationId": 481,
                "approved": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The decision was recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "authorizationId": {
                      "type": "integer"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "approved",
                        "denied"
                      ]
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "authorizationId": 481,
                  "status": "approved",
                  "message": "Payment authorization approved."
                }
              }
            }
          },
          "400": {
            "description": "The payload is incomplete, or the authorization has already been answered or has expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Approving: the wallet holds too little to approve the purchase (`insufficient_funds`). The authorization stays pending, so it can still be denied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not a user signed in to their own TODAQ app.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such authorization for this user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The decision could not be recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/paywall/preauthorization": {
      "get": {
        "tags": [
          "Paywall"
        ],
        "summary": "List spending budgets",
        "description": "Returns the remaining preauthorized budget for each service the user has one for, most recently changed first.",
        "operationId": "getV4PaywallPreauthorization",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionId": []
          }
        ],
        "responses": {
          "200": {
            "description": "The user’s budgets.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "preauthorizations": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "service_url": {
                            "type": "string",
                            "format": "uri"
                          },
                          "remaining_amount": {
                            "type": "number",
                            "description": "What is left of the budget."
                          },
                          "twin_id": {
                            "type": "integer"
                          },
                          "twin_hostname": {
                            "type": "string",
                            "description": "The twin the budget draws from."
                          },
                          "created_at": {
                            "type": "integer"
                          },
                          "updated_at": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "preauthorizations"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated, or the session has expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller is an account, a managed agent or an OIDC token, which may not read budgets.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The budgets could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Paywall"
        ],
        "summary": "Set a spending budget for a service",
        "description": "Preauthorizes a budget for one service so that later paywall calls to it settle without prompting, drawing down the remaining amount until it runs out.\n\nFrom the user’s own TODAQ app (its bearer token) the budget is set straight away. With a session (`public_secret` plus `X-Session-ID`) the user must confirm the budget on their device: a push notification is sent and the request blocks until they answer. An account’s token and any OIDC token (a connected app’s or the dashboard’s) are refused with `403`. Setting a budget for a service that already has one replaces it.\n\nThe `serviceUrl` is reduced to its origin, so a budget covers every path on that host.",
        "operationId": "postV4PaywallPreauthorization",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionId": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount": {
                    "type": "number",
                    "minimum": 0,
                    "description": "The budget to allow. Must be positive."
                  },
                  "serviceUrl": {
                    "type": "string",
                    "format": "uri",
                    "description": "The service the budget applies to."
                  }
                },
                "required": [
                  "amount",
                  "serviceUrl"
                ]
              },
              "example": {
                "amount": 5,
                "serviceUrl": "https://api.example.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The budget was set.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "serviceUrl": {
                      "type": "string",
                      "format": "uri",
                      "description": "The origin the budget applies to."
                    },
                    "amount": {
                      "type": "number"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "serviceUrl": "https://api.example.com",
                  "amount": 5,
                  "message": "Preauthorization of $5.00 set for api.example.com"
                }
              }
            }
          },
          "400": {
            "description": "`amount` or `serviceUrl` is missing or invalid, or the user denied the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated, or the session has expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller is an account, a managed agent or an OIDC token, which may not set budgets.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "408": {
            "description": "The user did not answer the approval prompt in time.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "status": {
                      "type": "integer"
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "timeout",
                        "expired"
                      ]
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "The budget could not be stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/paywall/preauthorization/{id}": {
      "delete": {
        "tags": [
          "Paywall"
        ],
        "summary": "Revoke a spending budget",
        "description": "Removes one of the user's budgets, so later paywall calls to that service ask for approval again. Only the user's own budgets can be revoked; any other `id` answers `404`, as an unknown one does. Only the user's own TODAQ app can revoke one, with its bearer token. A browser session (`public_secret` plus `X-Session-ID`) does not authenticate here and answers `401`, and an account’s token or any OIDC token (a connected app’s or the dashboard’s) is refused with `403`.",
        "operationId": "deleteV4PaywallPreauthorizationById",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "204": {
            "description": "The budget was revoked."
          },
          "401": {
            "description": "Not authenticated: no user bearer token. A `public_secret` session is not accepted on this route.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller is an account, a managed agent or an OIDC token, which may not revoke budgets.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The user has no budget with this `id`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The budget could not be revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The budget's `id`, as `GET /v4/paywall/preauthorization` lists it.",
            "schema": {
              "type": "integer"
            },
            "example": 12
          }
        ]
      }
    },
    "/v4/persona": {
      "post": {
        "tags": [
          "Personas"
        ],
        "summary": "Create a persona",
        "description": "Anchors a new persona to a twin you manage. Takes your own first-party bearer token (a user's or an account's); an OIDC token, the dashboard's included, may not create personas.",
        "operationId": "postV4Persona",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hash": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "The persona hash to register."
                  },
                  "info": {
                    "type": "object",
                    "description": "Persona attributes."
                  },
                  "twin_id": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "The twin to anchor the persona to."
                  }
                },
                "required": [
                  "hash",
                  "twin_id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The persona was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`hash`, `twin_id` or `info` is missing or malformed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "An OIDC token may not create personas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No twin with that id that you manage.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A persona with that hash already exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The persona could not be stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/persona/delegate/{hash}": {
      "get": {
        "tags": [
          "Delegates"
        ],
        "summary": "Get a delegate",
        "description": "Returns the delegate’s attributes, read from the user’s primary twin.",
        "operationId": "getV4PersonaDelegateByHash",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The delegate.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "hash": {
                      "type": "string"
                    },
                    "info": {
                      "type": "object",
                      "description": "Delegate attributes: holder, type, issue number and expiry."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not a user: an account or its public secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The user has no primary twin, or it holds no such delegate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The user’s twin failed or could not be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the persona, or the delegate issued from it.",
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "post": {
        "tags": [
          "Delegates"
        ],
        "summary": "Issue a delegate",
        "description": "Issues delegated spending authority from this persona to the twin at `hostname`. Each issue increments the delegate’s issue number, so the most recent grant supersedes earlier ones.\n\nOnly a bearer that manages the persona’s twin may issue: the account that owns it, or a user of that account signed in to their own app. OIDC tokens are refused.\n\nWhen `expiry` is omitted the delegate is valid for one year; it may be at most five years out.",
        "operationId": "postV4PersonaDelegateByHash",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hostname": {
                    "type": "string",
                    "description": "Hostname of the twin receiving the authority.",
                    "example": "00000000000000000000000000000000.micro.biz.todaq.net"
                  },
                  "name": {
                    "type": "string",
                    "description": "Name recorded on the delegate."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Email recorded on the delegate."
                  },
                  "expiry": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the authority lapses. Defaults to one year out."
                  }
                },
                "required": [
                  "hostname"
                ]
              },
              "example": {
                "hostname": "00000000000000000000000000000000.micro.biz.todaq.net",
                "name": "Ada Lovelace",
                "email": "ada@example.com"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "The delegate was issued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "integer"
                    },
                    "message": {
                      "type": "string"
                    },
                    "hash": {
                      "type": "string",
                      "description": "The issued delegate file."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`hostname` is missing or names no twin, `expiry` is not a date in the next five years, or the hash cannot name a persona.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not a bearer, or is an OIDC token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No persona with that hash that the caller manages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "More than one persona the caller manages has that hash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The twin issued the delegate but it could not be recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "A twin failed, could not be reached, or refused the issue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the persona, or the delegate issued from it.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/persona/delegate/{hash}/transfer": {
      "post": {
        "tags": [
          "Delegates"
        ],
        "summary": "Transfer under a delegate",
        "description": "Sends value from the caller’s primary twin to `address`, carrying the delegate identified by `hash` so the recipient can see the authority the transfer was made under.\n\nAs with a plain transfer, `address` is either a twin URL such as `https://<name>.biz.todaq.net` or an email address, which invites the recipient to claim what was sent. Once it has left the caller’s wallet the answer is `202`, even if the email to the recipient could not be sent: do not retry it.",
        "operationId": "postV4PersonaDelegateByHashTransfer",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "address": {
                    "type": "string",
                    "description": "Where the value goes: a twin URL, or an email address to invite."
                  },
                  "note": {
                    "type": "string",
                    "description": "A message shown to the recipient. Trimmed, and cut to 280 characters; anything that is not a string is refused with `400`."
                  }
                },
                "required": [
                  "address"
                ]
              },
              "example": {
                "address": "https://00000000000000000000000000000000.biz.todaq.net",
                "note": "Delegated payment"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The transfer was accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "An empty object."
                },
                "example": {}
              }
            }
          },
          "400": {
            "description": "No body was sent, `address` is missing or unrecognised, or `note` is not a string.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller may not transfer from this twin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The sending or the destination twin could not be found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The transfer failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the delegate the value is sent under.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/persona/delegates": {
      "get": {
        "tags": [
          "Delegates"
        ],
        "summary": "List your delegates",
        "description": "Returns the delegates on the signed-in user’s twins, grouped by persona type: their primary twin’s, and those on any older twin they hold.",
        "operationId": "getV4PersonaDelegates",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Delegates, keyed by persona type.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Each key is a persona type; each value is the delegates of that type.",
                  "additionalProperties": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not a user: an account or its public secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The user holds no twin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The user’s twin failed or could not be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/persona/{hash}": {
      "get": {
        "tags": [
          "Personas"
        ],
        "summary": "Get a persona",
        "description": "Returns the persona and the twin it is anchored to.",
        "operationId": "getV4PersonaByHash",
        "security": [],
        "responses": {
          "200": {
            "description": "The persona.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "hash": {
                      "type": "string",
                      "description": "Identifies the persona."
                    },
                    "hostname": {
                      "type": "string",
                      "description": "Hostname of the twin the persona is anchored to."
                    },
                    "info": {
                      "type": "object",
                      "description": "Persona attributes."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No persona with that hash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the persona.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/persona/{hash}/delegate": {
      "get": {
        "tags": [
          "Delegates"
        ],
        "summary": "Get a delegate",
        "description": "Returns the delegate’s attributes, read from the user’s primary twin.",
        "operationId": "getV4PersonaByHashDelegate",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The delegate.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "hash": {
                      "type": "string"
                    },
                    "info": {
                      "type": "object",
                      "description": "Delegate attributes: holder, type, issue number and expiry."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not a user: an account or its public secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The user has no primary twin, or it holds no such delegate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The user’s twin failed or could not be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the persona, or the delegate issued from it.",
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "post": {
        "tags": [
          "Delegates"
        ],
        "summary": "Issue a delegate for a persona",
        "description": "Issues delegated spending authority from persona `hash` to the twin at `hostname`. Identical to `POST /v4/persona/delegate/{hash}`.\n\nEach issue increments the delegate’s issue number, so the most recent grant supersedes earlier ones. Only a bearer that manages the persona’s twin may issue; OIDC tokens are refused. When `expiry` is omitted the delegate is valid for one year; it may be at most five years out.",
        "operationId": "postV4PersonaByHashDelegate",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hostname": {
                    "type": "string",
                    "description": "Hostname of the twin receiving the authority."
                  },
                  "name": {
                    "type": "string",
                    "description": "Name recorded on the delegate."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Email recorded on the delegate."
                  },
                  "expiry": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the authority lapses. Defaults to one year out."
                  }
                },
                "required": [
                  "hostname"
                ]
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "The delegate was issued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "integer"
                    },
                    "message": {
                      "type": "string"
                    },
                    "hash": {
                      "type": "string",
                      "description": "The issued delegate file."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`hostname` is missing or names no twin, `expiry` is not a date in the next five years, or the hash cannot name a persona.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not a bearer, or is an OIDC token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No persona with that hash that the caller manages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "More than one persona the caller manages has that hash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The twin issued the delegate but it could not be recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "A twin failed, could not be reached, or refused the issue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the persona, or the delegate issued from it.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/persona/{hash}/verify": {
      "put": {
        "tags": [
          "Personas"
        ],
        "summary": "Verify a persona",
        "description": "Checks whether the signed-in user holds this persona: reads the user’s own primary twin and returns the instances found there. An empty result means the user holds no instance, which is answered with `204` rather than an error. A relying party holding the user’s OIDC token may verify that user’s pass; accounts may not.",
        "operationId": "putV4PersonaByHashVerify",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "twin_id": {
                    "type": "integer",
                    "description": "Ignored. The twin read is always the caller’s own primary twin.",
                    "deprecated": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The persona was found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "integer"
                    },
                    "message": {
                      "type": "string"
                    },
                    "hostname": {
                      "type": "string",
                      "description": "Hostname of the verifying twin."
                    },
                    "instances": {
                      "type": "array",
                      "description": "The persona instances the twin holds.",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "204": {
            "description": "The twin holds no instance of this persona.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "integer"
                    },
                    "message": {
                      "type": "string"
                    },
                    "hostname": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The hash cannot name a persona.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not a user: an account or its public secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No persona with that hash, or the user has no primary twin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The twin failed, could not be reached, or answered with something other than a list of instances.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Identifies the persona to verify.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/session": {
      "post": {
        "tags": [
          "Sessions"
        ],
        "summary": "Create a session",
        "description": "Issues a session token for the authenticated user, valid for one hour. Send it back as the `session_id` cookie or the `X-Session-ID` header on subsequent calls.\n\nSessions are issued only to the user's own TODAQ app. An account token, an account's public secret, and any token from the OIDC provider (a connected app's or an agent's) are refused.",
        "operationId": "postV4Session",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The session was created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "session": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The session token."
                    },
                    "expiresAt": {
                      "type": "integer",
                      "description": "Unix time the session expires."
                    },
                    "userId": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller is not a user signed in to their own TODAQ app: an account, a connected app or an agent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/transfer": {
      "post": {
        "tags": [
          "Transfers"
        ],
        "summary": "Transfer to a wallet or email address",
        "description": "Sends `amount` of digital quantity `type` from the caller’s primary twin to `address`.\n\nThe `address` decides how the transfer is delivered: a twin URL such as `https://<name>.biz.todaq.net` transfers wallet to wallet, while an email address sends the recipient an invitation to claim the amount. Anything else is rejected.\n\nThe balance is checked before the transfer is attempted, so an underfunded twin fails with `402` and nothing moves. Once the funds have moved the answer is `202`, even if the notification email could not be sent: do not retry it.\n\nA token from the OIDC provider (one held by an app the user connected, such as an MCP client) may only send to the caller’s own agent wallet; anything else is refused with `403`.",
        "operationId": "postV4Transfer",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "address": {
                    "type": "string",
                    "description": "Where the value goes: a twin URL (`https://` and a hostname under `biz.todaq.net`, with no path, port or trailing slash), or an email address to invite.",
                    "example": "https://00000000000000000000000000000000.biz.todaq.net"
                  },
                  "type": {
                    "$ref": "#/components/schemas/DigitalQuantityHash",
                    "description": "The digital quantity to send."
                  },
                  "amount": {
                    "type": "number",
                    "description": "How much to send, in the smallest unit of `type`."
                  },
                  "note": {
                    "type": "string",
                    "description": "A message shown to the recipient. Trimmed, and cut to 280 characters; anything that is not a string is refused with `400`."
                  }
                },
                "required": [
                  "address",
                  "type",
                  "amount"
                ]
              },
              "example": {
                "address": "ada@example.com",
                "type": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73",
                "amount": 100,
                "note": "Thanks!"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The transfer was accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "An empty object."
                },
                "example": {}
              }
            }
          },
          "400": {
            "description": "No body was sent, `address` is missing, or it is neither an email address nor a twin URL; or `note` is not a string.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "integer"
                    },
                    "error": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string",
                      "description": "What was wrong with the address."
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "The sending twin’s balance does not cover the amount.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller may not transfer from this twin, or holds an OIDC token and `address` is not their own agent wallet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The caller has no primary twin to send from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The transfer failed after the balance check.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The twin node could not be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/twin": {
      "post": {
        "tags": [
          "Twins"
        ],
        "summary": "Create a twin",
        "description": "Provisions a twin for the authenticated principal and registers it against the given digital quantity. The response carries the twin `key` for your own first-party token or an account token; an OIDC token creating a twin for its user gets `null`.",
        "operationId": "postV4Twin",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "dq": {
                    "$ref": "#/components/schemas/DigitalQuantityHash",
                    "description": "The digital quantity the twin will transact in."
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "user",
                      "account",
                      "agent",
                      "provider",
                      "primary"
                    ],
                    "description": "Role the twin plays. `user` is a person’s wallet and `account` a business account’s, each only for that caller; `provider` twins sell commodities, `agent` twins pay on a user’s behalf. Omitted, or the older `primary`, is the caller’s own: `user` for a user, `account` for an account."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Contact address associated with the twin."
                  },
                  "paywall": {
                    "type": "string",
                    "description": "Paywall configuration, for provider twins."
                  }
                },
                "required": [
                  "dq"
                ]
              },
              "example": {
                "dq": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73",
                "type": "provider"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The twin was created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Twin"
                },
                "example": {
                  "id": 3539,
                  "hostname": "00000000000000000000000000000000.micro.biz.todaq.net",
                  "key": "00000000-0000-4000-8000-000000000000",
                  "balances": {
                    "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "The `type` given is not a valid twin type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No authenticated principal to own the twin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The principal may not create twins.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/twin/dq": {
      "get": {
        "tags": [
          "Digital Quantities"
        ],
        "summary": "List digital quantities",
        "description": "Returns every digital quantity the platform transacts in, together with the metadata describing how each should be displayed.",
        "operationId": "getV4TwinDq",
        "security": [],
        "responses": {
          "200": {
            "description": "The available digital quantities.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "digital_quantities": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DigitalQuantityHash"
                      }
                    },
                    "metadata": {
                      "type": "object",
                      "description": "Display metadata for each quantity, keyed by DQ hash.",
                      "additionalProperties": {
                        "type": "object"
                      }
                    }
                  },
                  "required": [
                    "digital_quantities"
                  ]
                },
                "example": {
                  "digital_quantities": [
                    "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73"
                  ],
                  "metadata": {
                    "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73": {
                      "name": "US Dollar",
                      "symbol": "$"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v4/twin/nonce": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Send a sign-in code",
        "description": "Emails a one-time code to `email` and records the attempt. Follow it with `PUT /v4/twin/nonce` to exchange the code the user types back.",
        "operationId": "postV4TwinNonce",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Where to send the code."
                  }
                },
                "required": [
                  "email"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The code was sent. The body is `{ created_at }` whether or not the address has an account."
          },
          "400": {
            "description": "`email` is missing or malformed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller may not issue sign-in codes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many codes were requested for this address or from this caller; see `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The code could not be sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Authentication"
        ],
        "summary": "Redeem a sign-in code",
        "description": "Exchanges the code the user received for their account. The user, their twin and their persona are provisioned on first sign-in — `201` says this call created them, `200` that they already existed.",
        "operationId": "putV4TwinNonce",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "The address the code was sent to."
                  },
                  "code": {
                    "type": "string",
                    "description": "The code the user received."
                  }
                },
                "required": [
                  "email",
                  "code"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The code was accepted for an existing user.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/User"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "twin": {
                          "$ref": "#/components/schemas/Twin"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "201": {
            "description": "The code was accepted and the user was provisioned.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/User"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "twin": {
                          "$ref": "#/components/schemas/Twin"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The caller may not redeem sign-in codes, or the code is wrong, used or expired. A code is destroyed after five wrong attempts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The user could not be provisioned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/twin/{hostname}": {
      "get": {
        "tags": [
          "Twins"
        ],
        "summary": "Get a twin",
        "description": "Returns a twin you manage and its balance in every digital quantity the platform tracks, read live from the twin node. A twin you cannot manage answers `404`, the same as one that does not exist.\n\n`key` comes with your own first-party token or an account token. An OIDC token gets it only for a twin your account holds, never for your own wallets, and an agent never gets it.",
        "operationId": "getV4TwinByHostname",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The twin and its current balances.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Twin"
                },
                "example": {
                  "id": 3539,
                  "hostname": "00000000000000000000000000000000.micro.biz.todaq.net",
                  "key": "00000000-0000-4000-8000-000000000000",
                  "balances": {
                    "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73": 1250
                  }
                }
              }
            }
          },
          "403": {
            "description": "Not signed in with a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No twin with that hostname that you manage.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "description": "Hostname identifying the twin.",
            "schema": {
              "type": "string"
            },
            "example": "00000000000000000000000000000000.micro.biz.todaq.net"
          }
        ]
      },
      "put": {
        "tags": [
          "Twins"
        ],
        "summary": "Rename a twin",
        "description": "Sets or clears the label shown for a twin you manage. The label is yours alone: it is not visible to payers and changes nothing about how the twin transacts. `label` is the only updatable field.\n\nA twin you cannot manage answers `404`, the same as one that does not exist, so this cannot be used to discover hostnames.",
        "operationId": "putV4TwinByHostname",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255,
                    "description": "The new label. `null` or an empty string clears it. Surrounding whitespace is trimmed."
                  }
                },
                "required": [
                  "label"
                ]
              },
              "example": {
                "label": "Storefront"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The twin, with its new label.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "twin": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "hostname": {
                          "type": "string"
                        },
                        "twin_type": {
                          "type": "string",
                          "enum": [
                            "user",
                            "account",
                            "agent",
                            "provider"
                          ]
                        },
                        "label": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  }
                },
                "example": {
                  "twin": {
                    "id": 3539,
                    "hostname": "00000000000000000000000000000000.micro.biz.todaq.net",
                    "twin_type": "provider",
                    "label": "Storefront"
                  }
                }
              }
            }
          },
          "400": {
            "description": "`label` is missing, not a string or null, or longer than 255 characters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The principal may not update twins.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No twin with that hostname that you manage.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "description": "Hostname identifying the twin.",
            "schema": {
              "type": "string"
            },
            "example": "00000000000000000000000000000000.micro.biz.todaq.net"
          }
        ]
      }
    },
    "/v4/twin/{hostname}/binder": {
      "get": {
        "tags": [
          "Twins"
        ],
        "summary": "Get a twin’s binder",
        "description": "Returns the twin’s binder — the record of every TODA file it holds and how each came to be there. The `Accept` header chooses the shape: JSON by default (no `Accept`, or `*/*`), `text/csv` for a spreadsheet-friendly export, or `application/octet-stream` for the raw binder file. Only a user or account that may manage the twin can read it.",
        "operationId": "getV4TwinByHostnameBinder",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The binder, in the requested representation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "No bearer token. An account's public secret is not enough."
          },
          "404": {
            "description": "No twin with that hostname that the caller may manage."
          },
          "406": {
            "description": "`Accept` names none of the representations above."
          },
          "502": {
            "description": "The twin failed, did not answer in time, or answered more than 10 MB."
          }
        },
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "description": "Hostname of the twin whose binder is read.",
            "schema": {
              "type": "string"
            },
            "example": "00000000000000000000000000000000.micro.biz.todaq.net"
          },
          {
            "name": "Accept",
            "in": "header",
            "description": "Representation to return.",
            "schema": {
              "type": "string",
              "enum": [
                "application/json",
                "text/csv",
                "application/octet-stream"
              ],
              "default": "application/json"
            }
          }
        ]
      }
    },
    "/v4/twin/{hostname}/charge/complete": {
      "post": {
        "tags": [
          "Twins"
        ],
        "summary": "Complete a card top-up",
        "description": "Turns a settled Stripe charge into digital quantity on the twin. The intent must have settled with Stripe first, otherwise the call is refused rather than crediting anything.",
        "operationId": "postV4TwinByHostnameChargeComplete",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "intent": {
                    "type": "string",
                    "description": "The Stripe payment intent id."
                  },
                  "dq": {
                    "$ref": "#/components/schemas/DigitalQuantityHash",
                    "description": "The digital quantity to credit."
                  }
                },
                "required": [
                  "intent",
                  "dq"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The twin was credited.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "402": {
            "description": "The card payment has not settled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller may not charge this twin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such twin or charge.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The credit failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "description": "Hostname of the twin being topped up.",
            "schema": {
              "type": "string"
            },
            "example": "00000000000000000000000000000000.micro.biz.todaq.net"
          }
        ]
      }
    },
    "/v4/twin/{hostname}/charge/intent": {
      "post": {
        "tags": [
          "Twins"
        ],
        "summary": "Start a card top-up",
        "description": "Opens a Stripe payment intent for topping a twin up with real money, and returns the client secret for the browser to confirm the card payment with. Confirm the card first, then call `/charge/complete` to turn the settled charge into a balance.",
        "operationId": "postV4TwinByHostnameChargeIntent",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount": {
                    "type": "number",
                    "description": "Amount to charge, in the smallest currency unit."
                  }
                },
                "required": [
                  "amount"
                ]
              },
              "example": {
                "amount": 2500
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The payment intent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "The Stripe payment intent id."
                    },
                    "secret": {
                      "type": "string",
                      "description": "Client secret for confirming the card payment."
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "403": {
            "description": "The caller may not charge this twin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The payment intent could not be created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "description": "Hostname of the twin being topped up.",
            "schema": {
              "type": "string"
            },
            "example": "00000000000000000000000000000000.micro.biz.todaq.net"
          }
        ]
      }
    },
    "/v4/twin/{hostname}/charge/transfer": {
      "post": {
        "tags": [
          "Twins"
        ],
        "summary": "Transfer a settled charge to a twin",
        "description": "Buys digital quantity from the treasury against a settled charge and transfers it to the twin. A charge can only be transferred once; a repeat attempt is refused with `409`.",
        "operationId": "postV4TwinByHostnameChargeTransfer",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "intent": {
                    "type": "string",
                    "description": "The Stripe payment intent id."
                  },
                  "dq": {
                    "$ref": "#/components/schemas/DigitalQuantityHash",
                    "description": "The digital quantity to buy and transfer."
                  }
                },
                "required": [
                  "intent",
                  "dq"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The transfer completed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "402": {
            "description": "The card payment has not settled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller may not charge this twin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such twin or charge.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "This charge has already been transferred.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The treasury is not configured, or the transfer failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "description": "Hostname of the twin being topped up.",
            "schema": {
              "type": "string"
            },
            "example": "00000000000000000000000000000000.micro.biz.todaq.net"
          }
        ]
      }
    },
    "/v4/twin/{hostname}/commodities": {
      "get": {
        "tags": [
          "Twins"
        ],
        "summary": "List a twin’s commodities",
        "description": "Returns everything this twin sells, ordered by descriptor. Paying for any of these credits the twin’s balance.",
        "operationId": "getV4TwinByHostnameCommodities",
        "security": [],
        "responses": {
          "200": {
            "description": "The commodities owned by the twin.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "hash": {
                        "type": "string",
                        "description": "Identifies the commodity."
                      },
                      "descriptor": {
                        "type": "string",
                        "description": "Human-readable name."
                      },
                      "dq": {
                        "$ref": "#/components/schemas/DigitalQuantityHash"
                      },
                      "cost": {
                        "type": "number",
                        "description": "Price in the smallest unit of `dq`."
                      },
                      "hostname": {
                        "type": "string",
                        "description": "The owning twin."
                      }
                    }
                  }
                },
                "example": [
                  {
                    "hash": "41b0e7dcb5f8a0c2d9e4f1a8b3c6d5e2f7a9b4c1d8e3f6a2b5c9d4e7f1a3b6c8d2",
                    "descriptor": "Premium article",
                    "dq": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73",
                    "cost": 25,
                    "hostname": "00000000000000000000000000000000.micro.biz.todaq.net"
                  }
                ]
              }
            }
          },
          "404": {
            "description": "No such twin, or the caller may not read it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "description": "Hostname of the twin whose commodities are listed.",
            "schema": {
              "type": "string"
            },
            "example": "00000000000000000000000000000000.micro.biz.todaq.net"
          }
        ]
      }
    },
    "/v4/twin/{hostname}/config": {
      "post": {
        "tags": [
          "Twins"
        ],
        "summary": "Configure a twin’s paywall",
        "description": "Points the twin’s own paywall at the upstream service it fronts and sets what a call to it costs. The caller must own the twin.\n\nThe `paywallUrl` in the response is what to register as `service_url` when listing this endpoint in the MCP tool registry.",
        "operationId": "postV4TwinByHostnameConfig",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "targetUrl": {
                    "type": "string",
                    "format": "uri",
                    "description": "The upstream service the twin fronts. Must be https."
                  },
                  "targetPayType": {
                    "$ref": "#/components/schemas/DigitalQuantityHash",
                    "description": "The digital quantity to charge in. A hex hash beginning `41`."
                  },
                  "targetPayQuantity": {
                    "type": "number",
                    "minimum": 0,
                    "description": "What a single call costs. Must be positive."
                  }
                },
                "required": [
                  "targetUrl",
                  "targetPayType",
                  "targetPayQuantity"
                ]
              },
              "example": {
                "targetUrl": "https://api.example.com/summarize",
                "targetPayType": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73",
                "targetPayQuantity": 0.05
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The twin was configured.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "hostname": {
                      "type": "string"
                    },
                    "config": {
                      "type": "object",
                      "properties": {
                        "targetUrl": {
                          "type": "string",
                          "format": "uri"
                        },
                        "targetPayType": {
                          "$ref": "#/components/schemas/DigitalQuantityHash"
                        },
                        "targetPayQuantity": {
                          "type": "number"
                        }
                      }
                    },
                    "paywallUrl": {
                      "type": "string",
                      "format": "uri",
                      "description": "Register this as `service_url` when listing the tool."
                    },
                    "twinResponse": {
                      "type": "object",
                      "description": "The twin node’s own reply."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing, `targetUrl` is not an https URL, `targetPayType` is not a DQ hash, or `targetPayQuantity` is not positive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller does not own this twin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No twin with that hostname.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The twin node was unreachable or refused the configuration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "description": "Hostname of the twin to configure.",
            "schema": {
              "type": "string"
            },
            "example": "00000000000000000000000000000000.micro.biz.todaq.net"
          }
        ]
      }
    },
    "/v4/twin/{hostname}/info": {
      "get": {
        "tags": [
          "Twins"
        ],
        "summary": "Get twin node configuration",
        "description": "Proxies the `/info` endpoint of the underlying TODA twin node. The caller must own the twin, not merely be able to read it.",
        "operationId": "getV4TwinByHostnameInfo",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The twin node’s configuration, passed through verbatim.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "No authenticated principal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller does not own this twin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No twin with that hostname.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The twin node was unreachable or returned an error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "description": "Hostname of the twin to inspect.",
            "schema": {
              "type": "string"
            },
            "example": "00000000000000000000000000000000.micro.biz.todaq.net"
          }
        ]
      }
    },
    "/v4/twin/{hostname}/inventory": {
      "get": {
        "tags": [
          "Twins"
        ],
        "summary": "Export twin inventory",
        "description": "Exports the TODA files backing this twin’s holdings. The response format is chosen by the `Accept` header: `text/csv` returns one row per file with its issuer, type, amount and integrity check, and `application/octet-stream` returns the raw binder for the first file. Only a user or account that may manage the twin can export it.",
        "operationId": "getV4TwinByHostnameInventory",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The inventory, in the requested representation.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "example": "issuer,file,fileType,amount,integrityCheck,lastTetherTimestamp\nTODAQ Micro Inc.,41f3…,416c71…,25,true,"
              }
            }
          },
          "401": {
            "description": "No bearer token. An account's public secret is not enough."
          },
          "404": {
            "description": "No twin with that hostname that the caller may manage."
          }
        },
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "description": "Hostname of the twin whose inventory is exported.",
            "schema": {
              "type": "string"
            },
            "example": "00000000000000000000000000000000.micro.biz.todaq.net"
          },
          {
            "name": "Accept",
            "in": "header",
            "description": "Either `text/csv` or `application/octet-stream`.",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "text/csv",
                "application/octet-stream"
              ]
            }
          }
        ]
      }
    },
    "/v4/twins": {
      "get": {
        "tags": [
          "Twins"
        ],
        "summary": "List your twins",
        "description": "Returns every twin owned by the authenticated principal, each with its live balances. Balances are read from the twin nodes themselves, so a node that cannot be reached simply contributes no entry.\n\n`key` is `null` for the user's own wallets when the token is an OIDC token (the dashboard's); the user's own first-party token and an account token get every key.",
        "operationId": "getV4Twins",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The twins owned by the caller.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "twins": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/Twin"
                          },
                          {
                            "type": "object",
                            "properties": {
                              "twin_type": {
                                "type": "string",
                                "enum": [
                                  "user",
                                  "account",
                                  "agent",
                                  "provider"
                                ]
                              },
                              "is_primary": {
                                "type": "boolean",
                                "description": "The calling user's primary twin, listed first: where their payments go. Some users hold several `user` twins from older sign-ups."
                              }
                            }
                          }
                        ]
                      }
                    }
                  },
                  "required": [
                    "twins"
                  ]
                },
                "example": {
                  "twins": [
                    {
                      "id": 3539,
                      "hostname": "00000000000000000000000000000000.micro.biz.todaq.net",
                      "key": "00000000-0000-4000-8000-000000000000",
                      "twin_type": "provider",
                      "is_primary": false,
                      "balances": {
                        "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73": 1250
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The `type` filter is not a valid twin role.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No authenticated principal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The principal may not read twins.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "description": "Restrict the result to one twin role. Omit to return all. `primary`, the type user and account wallets shared before they were told apart, returns both.",
            "schema": {
              "type": "string",
              "enum": [
                "user",
                "account",
                "agent",
                "provider",
                "primary"
              ]
            }
          }
        ]
      }
    },
    "/v4/user": {
      "get": {
        "tags": [
          "Users"
        ],
        "summary": "Get a user",
        "description": "What comes back depends on the principal behind the token.\n\nA **user** token returns that user, and `email` is ignored. The user's API secrets are included only for their own first-party token, never for an OIDC token. An **account** token looks up a user by `email`, which is then required; the account may only read users it is linked to.",
        "operationId": "getV4User",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The user.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/User"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "is_ios": {
                          "type": "boolean"
                        },
                        "is_android": {
                          "type": "boolean"
                        },
                        "secrets": {
                          "type": "array",
                          "description": "Returned to the user's own first-party token only, never to an OIDC token.",
                          "items": {
                            "type": "object"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "An account token was used without an `email`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The account is not linked to that user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No user with that address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "email",
            "in": "query",
            "description": "The user to look up. Required for account tokens, ignored for user tokens.",
            "schema": {
              "type": "string",
              "format": "email"
            }
          }
        ]
      },
      "post": {
        "tags": [
          "Users"
        ],
        "summary": "Create a user",
        "description": "Creates a user and, alongside them, their twin and persona. When an account token is used the new user is linked to that account. A password is generated if none is supplied.\n\nCalling this for an address that already exists answers 201 with that user's id; it does not link them to your account and returns no credentials. A new user's credentials are not returned either: the user signs in by code.",
        "operationId": "postV4User",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string",
                    "description": "Generated when omitted."
                  }
                },
                "required": [
                  "email"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "400": {
            "description": "`email` was not supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller may not create users.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The user could not be created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Users"
        ],
        "summary": "Update a user",
        "description": "Updates the calling user, with their own first-party **user** token; an OIDC or account token may not. Send back the record you read, or just the fields to change: `id` may be sent, and must then be your own (any other is refused with `403`); `created_at` and `updated_at` are accepted and ignored. Only the device platform flags are applied, and only when at least one of them is `true`: both are then set, one left out reading as `false`.",
        "operationId": "putV4User",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "integer",
                    "description": "Optional; must be your own."
                  },
                  "created_at": {
                    "type": "integer"
                  },
                  "updated_at": {
                    "type": "integer"
                  },
                  "is_ios": {
                    "type": "boolean"
                  },
                  "is_android": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The user as updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "email": {
                      "type": "string",
                      "format": "email"
                    },
                    "created_at": {
                      "type": "string",
                      "description": "Unix time, in seconds."
                    },
                    "updated_at": {
                      "type": "string",
                      "description": "Unix time, in seconds."
                    },
                    "is_ios": {
                      "type": "boolean"
                    },
                    "is_android": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`is_ios` or `is_android` is not a boolean.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not the user's own first-party token, or `id` is someone else's.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The user no longer exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The update failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/user/2fa": {
      "put": {
        "tags": [
          "Authentication"
        ],
        "summary": "Check which devices a user has",
        "description": "Reports whether the user has registered an iOS or Android device, which tells you whether a push-based second factor can reach them at all. Poll this while waiting for a second factor to be completed.",
        "operationId": "putV4User2fa",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "The user to check."
                  }
                },
                "required": [
                  "email"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The user’s registered platforms.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "is_ios": {
                      "type": "boolean"
                    },
                    "is_android": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not check second factors.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No user with that address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/user/2fa/reject": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Reject a second factor",
        "description": "Declines the pending second factor for `session`. Only the user it was raised against may reject it, and only while it is still pending.\n\nRejecting a request that has already been resolved does nothing and still answers `200` with `rejected: false`, so a client can retry safely.",
        "operationId": "postV4User2faReject",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "session": {
                    "type": "string",
                    "description": "The session to reject."
                  }
                },
                "required": [
                  "session"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rejection was processed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "rejected": {
                      "type": "boolean",
                      "description": "True when this call rejected a pending request; false when there was nothing to reject."
                    }
                  }
                },
                "example": {
                  "rejected": true
                }
              }
            }
          },
          "400": {
            "description": "`session` was not supplied, or the principal is not a user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller may not reject second factors: only the user's own first-party token may, never an OIDC token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/user/2fa/request": {
      "get": {
        "tags": [
          "Authentication"
        ],
        "summary": "Get your pending second factor",
        "description": "Returns the second factor outstanding against the signed-in user, if there is one that has been neither answered nor left for more than ten minutes; the newest, should there be several. The device app polls this to know what to prompt for.",
        "operationId": "getV4User2faRequest",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The pending request.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "user_id": {
                      "type": "integer"
                    },
                    "code": {
                      "type": "integer",
                      "description": "The code the user must match."
                    },
                    "session": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "integer"
                    },
                    "expires_at": {
                      "type": "integer",
                      "description": "When the request lapses, in seconds since the epoch."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The principal is not a user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller may not read second factors: only the user's own first-party token may, never an OIDC token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Nothing is pending.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Raise a second factor",
        "description": "Starts a second factor for the user at `email` and returns the code to display. The user confirms it on their registered device, after which the tokens can be collected with `PUT /v4/user/2fa/token`.\n\nRaising a new request replaces any earlier one still outstanding for that user, so there is only ever one. A request lapses ten minutes after it is raised.\n\nRegister the `publicKey` here: it is the RSA-OAEP key the resulting tokens will be encrypted with. `session` and `publicKey` are required: a request without them could never be completed, so it is refused with `400` and nothing is raised.\n\nThe user is shown who is asking: the account whose credentials raised the request, the browser and system its `User-Agent` names, and `origin` if one is given. `origin` is what the caller says; the user is not told it was checked.\n\nThe answer is the same whether or not `email` belongs to a user, so it cannot be used to find out who has an account; a request for an unknown address is simply never approved.",
        "operationId": "postV4User2faRequest",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "The user to challenge."
                  },
                  "session": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Identifies this attempt across the flow."
                  },
                  "publicKey": {
                    "type": "string",
                    "description": "Base64-encoded RSA-OAEP (SHA-256) public key, as a JWK, the tokens will be encrypted with. Must import as one."
                  },
                  "origin": {
                    "type": "string",
                    "format": "uri",
                    "description": "The site the sign-in is for, as shown to the user. Reduced to its origin; anything that is not an http(s) URL is ignored."
                  }
                },
                "required": [
                  "email",
                  "session",
                  "publicKey"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The request was raised.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "description": "The two-digit code to show the user so they can match it on their device."
                    }
                  }
                },
                "example": {
                  "code": 47
                }
              }
            }
          },
          "400": {
            "description": "`email` was not supplied, `session` is not a non-empty string, or `publicKey` is not a base64 JWK that imports as an RSA-OAEP public key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller may not raise second factors.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/user/2fa/requests": {
      "get": {
        "tags": [
          "Authentication"
        ],
        "summary": "Get your pending second factor",
        "description": "Returns the second factor outstanding against the signed-in user, if there is one that has been neither answered nor left for more than ten minutes; the newest, should there be several. The device app polls this to know what to prompt for.",
        "operationId": "getV4User2faRequests",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The pending request.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "user_id": {
                      "type": "integer"
                    },
                    "code": {
                      "type": "integer",
                      "description": "The code the user must match."
                    },
                    "session": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "integer"
                    },
                    "expires_at": {
                      "type": "integer",
                      "description": "When the request lapses, in seconds since the epoch."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The principal is not a user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller may not read second factors: only the user's own first-party token may, never an OIDC token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Nothing is pending.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Raise a second factor",
        "description": "Starts a second factor for the user at `email` and returns the code to display. The user confirms it on their registered device, after which the tokens can be collected with `PUT /v4/user/2fa/token`.\n\nRaising a new request replaces any earlier one still outstanding for that user, so there is only ever one. A request lapses ten minutes after it is raised.\n\nRegister the `publicKey` here: it is the RSA-OAEP key the resulting tokens will be encrypted with. `session` and `publicKey` are required: a request without them could never be completed, so it is refused with `400` and nothing is raised.\n\nThe user is shown who is asking: the account whose credentials raised the request, the browser and system its `User-Agent` names, and `origin` if one is given. `origin` is what the caller says; the user is not told it was checked.\n\nThe answer is the same whether or not `email` belongs to a user, so it cannot be used to find out who has an account; a request for an unknown address is simply never approved.",
        "operationId": "postV4User2faRequests",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "The user to challenge."
                  },
                  "session": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Identifies this attempt across the flow."
                  },
                  "publicKey": {
                    "type": "string",
                    "description": "Base64-encoded RSA-OAEP (SHA-256) public key, as a JWK, the tokens will be encrypted with. Must import as one."
                  },
                  "origin": {
                    "type": "string",
                    "format": "uri",
                    "description": "The site the sign-in is for, as shown to the user. Reduced to its origin; anything that is not an http(s) URL is ignored."
                  }
                },
                "required": [
                  "email",
                  "session",
                  "publicKey"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The request was raised.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "description": "The two-digit code to show the user so they can match it on their device."
                    }
                  }
                },
                "example": {
                  "code": 47
                }
              }
            }
          },
          "400": {
            "description": "`email` was not supplied, `session` is not a non-empty string, or `publicKey` is not a base64 JWK that imports as an RSA-OAEP public key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller may not raise second factors.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/user/2fa/token": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Complete a second factor",
        "description": "Marks the second factor for `session` as satisfied and mints the access and refresh tokens for it. The session must name a sign-in request the caller raised for themselves, still outstanding (neither completed nor rejected, and under ten minutes old); a request completes once.\n\nThe tokens are never returned in the clear. They are encrypted with the RSA-OAEP public key registered when the request was raised, and the result is stored for the waiting client to collect with `PUT /v4/user/2fa/token` — so only the holder of the matching private key can read them. Both tokens expire ten minutes after being minted.\n\n`/v4/user/2fa/token` and `/v4/user/2fa/verify` are the same endpoint.",
        "operationId": "postV4User2faToken",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "session": {
                    "type": "string",
                    "description": "The session the second factor was raised against."
                  },
                  "publicKey": {
                    "type": "string",
                    "description": "Accepted for compatibility and ignored: the tokens are encrypted with the key registered when the request was raised."
                  }
                },
                "required": [
                  "session"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The second factor was accepted and the tokens were stored for collection.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "An empty object."
                },
                "example": {}
              }
            }
          },
          "400": {
            "description": "The request cannot be completed: the device that raised it registered no key the tokens can be encrypted to. It is left unanswered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller may not complete second factors: only the user's own first-party token may, never an OIDC token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The caller has no outstanding sign-in request for this session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Authentication"
        ],
        "summary": "Collect the tokens for a session",
        "description": "Polls for the outcome of a second factor. While it is outstanding this answers `404`; once it has been completed the encrypted token bundle is returned. Decrypt `digest` with the private key matching the public key you registered to get the access and refresh tokens.\n\n`/v4/user/2fa/token` and `/v4/user/2fa/verify` are the same endpoint.",
        "operationId": "putV4User2faToken",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "session": {
                    "type": "string",
                    "description": "The session to collect tokens for."
                  }
                },
                "required": [
                  "session"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The second factor is complete.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "created_at": {
                      "type": "integer",
                      "description": "Unix time the second factor completed."
                    },
                    "digest": {
                      "type": "string",
                      "description": "Base64 of the RSA-OAEP encrypted token bundle. Decrypts to an object with `access` and `refresh` entries, each carrying `token` and `expires_at`."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not read this second factor.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The second factor has not been completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/user/2fa/verify": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Complete a second factor",
        "description": "Marks the second factor for `session` as satisfied and mints the access and refresh tokens for it. The session must name a sign-in request the caller raised for themselves, still outstanding (neither completed nor rejected, and under ten minutes old); a request completes once.\n\nThe tokens are never returned in the clear. They are encrypted with the RSA-OAEP public key registered when the request was raised, and the result is stored for the waiting client to collect with `PUT /v4/user/2fa/token` — so only the holder of the matching private key can read them. Both tokens expire ten minutes after being minted.\n\n`/v4/user/2fa/token` and `/v4/user/2fa/verify` are the same endpoint.",
        "operationId": "postV4User2faVerify",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "session": {
                    "type": "string",
                    "description": "The session the second factor was raised against."
                  },
                  "publicKey": {
                    "type": "string",
                    "description": "Accepted for compatibility and ignored: the tokens are encrypted with the key registered when the request was raised."
                  }
                },
                "required": [
                  "session"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The second factor was accepted and the tokens were stored for collection.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "An empty object."
                },
                "example": {}
              }
            }
          },
          "400": {
            "description": "The request cannot be completed: the device that raised it registered no key the tokens can be encrypted to. It is left unanswered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The caller may not complete second factors: only the user's own first-party token may, never an OIDC token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The caller has no outstanding sign-in request for this session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Authentication"
        ],
        "summary": "Collect the tokens for a session",
        "description": "Polls for the outcome of a second factor. While it is outstanding this answers `404`; once it has been completed the encrypted token bundle is returned. Decrypt `digest` with the private key matching the public key you registered to get the access and refresh tokens.\n\n`/v4/user/2fa/token` and `/v4/user/2fa/verify` are the same endpoint.",
        "operationId": "putV4User2faVerify",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "session": {
                    "type": "string",
                    "description": "The session to collect tokens for."
                  }
                },
                "required": [
                  "session"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The second factor is complete.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "created_at": {
                      "type": "integer",
                      "description": "Unix time the second factor completed."
                    },
                    "digest": {
                      "type": "string",
                      "description": "Base64 of the RSA-OAEP encrypted token bundle. Decrypts to an object with `access` and `refresh` entries, each carrying `token` and `expires_at`."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The caller may not read this second factor.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The second factor has not been completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/user/accounts": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "summary": "List the accounts you belong to",
        "description": "Returns every account the signed-in user belongs to, newest first, with its business and billing fields. Use an account's `id` with the other `/v4/user/accounts/{id}` endpoints.\n\nRequires a bearer token for a **user** principal (from `POST /v4/user/oauth/token` or a sign-in with the platform's OpenID Connect provider). Account-principal tokens are refused with `401`.",
        "operationId": "getV4UserAccounts",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The accounts, newest first. Empty when the user belongs to none.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "accounts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "plan": {
                            "type": "string",
                            "enum": [
                              "starter",
                              "pro",
                              "team",
                              "enterprise"
                            ],
                            "description": "The plan the account is on. `starter` is the free Builder plan."
                          },
                          "business_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "business_address": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "billing_email": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "email"
                          },
                          "tax_number": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "billing_status": {
                            "type": "string",
                            "enum": [
                              "none",
                              "active",
                              "trialing",
                              "past_due",
                              "unpaid",
                              "canceled",
                              "incomplete"
                            ],
                            "description": "State of the paid subscription. `none` until a paid plan is first chosen."
                          },
                          "created_at": {
                            "type": "string",
                            "pattern": "^[0-9]+$",
                            "description": "Unix time in seconds, encoded as a decimal string.",
                            "example": "1758700800"
                          },
                          "updated_at": {
                            "type": "string",
                            "pattern": "^[0-9]+$",
                            "description": "Unix time in seconds, encoded as a decimal string.",
                            "example": "1758700800"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "accounts"
                  ]
                },
                "example": {
                  "accounts": [
                    {
                      "id": 1024,
                      "name": "Northwind",
                      "plan": "pro",
                      "business_name": "Northwind Traders Ltd",
                      "business_address": "1 Harbour Street, Portsmouth",
                      "billing_email": "billing@example.com",
                      "tax_number": "GB123456789",
                      "billing_status": "active",
                      "created_at": "1758700800",
                      "updated_at": "1758787200"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or the token is not a user token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "status": 401
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Accounts"
        ],
        "summary": "Create an account you own",
        "description": "Creates an account owned by the signed-in user, together with its client credentials and a primary twin for it to be paid into. Every field is optional. The account starts on the free Builder plan; choose a paid plan with `PUT /v4/user/accounts/{id}/billing/plan`. A `plan` other than `starter` is refused (`plan_not_settable`).\n\nThe response echoes the fields you sent, in the same camelCase spelling, so a field you left out is absent rather than `null`.\n\nRequires a bearer token for a **user** principal (from `POST /v4/user/oauth/token` or a sign-in with the platform's OpenID Connect provider). Account-principal tokens are refused with `401`.",
        "operationId": "postV4UserAccounts",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Display name for the account."
                  },
                  "businessName": {
                    "type": "string",
                    "description": "Legal or trading name of the business."
                  },
                  "businessAddress": {
                    "type": "string",
                    "description": "Postal address of the business, as free text."
                  },
                  "billingEmail": {
                    "type": "string",
                    "format": "email",
                    "description": "Where invoices and billing notices are sent."
                  },
                  "taxNumber": {
                    "type": "string",
                    "description": "VAT, GST or other tax registration number."
                  }
                }
              },
              "example": {
                "name": "Northwind",
                "businessName": "Northwind Traders Ltd",
                "businessAddress": "1 Harbour Street, Portsmouth",
                "billingEmail": "billing@example.com",
                "taxNumber": "GB123456789"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The account and its primary twin were created.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "plan": {
                          "type": "string",
                          "enum": [
                            "starter",
                            "pro",
                            "team",
                            "enterprise"
                          ],
                          "description": "The plan the account is on. `starter` is the free Builder plan."
                        },
                        "businessName": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "businessAddress": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "billingEmail": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "email"
                        },
                        "taxNumber": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string",
                          "pattern": "^[0-9]+$",
                          "description": "Unix time in seconds, encoded as a decimal string.",
                          "example": "1758700800"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "twin": {
                          "type": "object",
                          "description": "The account's main twin.",
                          "properties": {
                            "id": {
                              "type": "integer"
                            },
                            "hostname": {
                              "type": "string"
                            },
                            "twinType": {
                              "type": "string",
                              "enum": [
                                "account"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "id": 1024,
                  "name": "Northwind",
                  "businessName": "Northwind Traders Ltd",
                  "businessAddress": "1 Harbour Street, Portsmouth",
                  "billingEmail": "billing@example.com",
                  "taxNumber": "GB123456789",
                  "created_at": "1758700800",
                  "twin": {
                    "id": 3539,
                    "hostname": "00000000000000000000000000000000.micro.biz.todaq.net",
                    "twinType": "account"
                  }
                }
              }
            }
          },
          "400": {
            "description": "`plan_not_settable`: the request names a plan, which only billing changes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_not_settable",
                  "message": "The plan is chosen with PUT /v4/user/accounts/{id}/billing/plan, which bills for it.",
                  "status": 400
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or the token is not a user token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "status": 401
                }
              }
            }
          },
          "500": {
            "description": "The account or its twin could not be created. Nothing was saved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Internal Server Error",
                  "status": 500
                }
              }
            }
          }
        }
      }
    },
    "/v4/user/accounts/{id}": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "summary": "Get an account you belong to",
        "description": "Returns one account the signed-in user belongs to: its name, business name and plan. An agent the user granted this account when they signed it in may read it too; every other account answers `403` to that agent.\n\nRequires a bearer token for a **user** principal (from `POST /v4/user/oauth/token` or a sign-in with the platform's OpenID Connect provider). Account-principal tokens are refused with `401`.",
        "operationId": "getV4UserAccountsById",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "business_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "plan": {
                          "type": "string",
                          "enum": [
                            "starter",
                            "pro",
                            "team",
                            "enterprise"
                          ],
                          "description": "The plan the account is on. `starter` is the free Builder plan."
                        },
                        "billing_status": {
                          "type": "string",
                          "enum": [
                            "none",
                            "active",
                            "trialing",
                            "past_due",
                            "unpaid",
                            "canceled",
                            "incomplete"
                          ]
                        },
                        "created_at": {
                          "type": "string",
                          "pattern": "^[0-9]+$",
                          "description": "Unix time in seconds, encoded as a decimal string.",
                          "example": "1758700800"
                        },
                        "updated_at": {
                          "type": "string",
                          "pattern": "^[0-9]+$",
                          "description": "Unix time in seconds, encoded as a decimal string.",
                          "example": "1758700800"
                        }
                      }
                    }
                  },
                  "required": [
                    "account"
                  ]
                },
                "example": {
                  "account": {
                    "id": 1024,
                    "name": "Northwind",
                    "business_name": "Northwind Traders Ltd",
                    "plan": "pro",
                    "billing_status": "active",
                    "created_at": "1758700800",
                    "updated_at": "1758873600"
                  }
                }
              }
            }
          },
          "400": {
            "description": "The account id in the path is not a number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Bad Request",
                  "status": 400
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or the token is not a user token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "status": 401
                }
              }
            }
          },
          "404": {
            "description": "No account with that id that you belong to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not Found",
                  "status": 404
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The account id, as returned by `GET /v4/user/accounts`.",
            "schema": {
              "type": "integer"
            },
            "example": 1024
          }
        ]
      },
      "put": {
        "tags": [
          "Accounts"
        ],
        "summary": "Update an account you belong to",
        "description": "Updates the business fields of an account. Only the fields you send are changed; a field sent as `null` is cleared. Sending no fields leaves the account as it is and returns it. `plan` is refused (`plan_not_settable`): it is chosen, and billed, with `PUT /v4/user/accounts/{id}/billing/plan`.\n\nRequires a bearer token for a **user** principal (from `POST /v4/user/oauth/token` or a sign-in with the platform's OpenID Connect provider). Account-principal tokens are refused with `401`.",
        "operationId": "putV4UserAccountsById",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Display name for the account."
                  },
                  "businessName": {
                    "type": "string",
                    "description": "Legal or trading name of the business."
                  },
                  "businessAddress": {
                    "type": "string",
                    "description": "Postal address of the business, as free text."
                  },
                  "billingEmail": {
                    "type": "string",
                    "format": "email",
                    "description": "Where invoices and billing notices are sent."
                  },
                  "taxNumber": {
                    "type": "string",
                    "description": "VAT, GST or other tax registration number."
                  }
                }
              },
              "example": {
                "billingEmail": "accounts@example.com",
                "taxNumber": "GB987654321"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The account after the update.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "plan": {
                          "type": "string",
                          "enum": [
                            "starter",
                            "pro",
                            "team",
                            "enterprise"
                          ],
                          "description": "The plan the account is on. `starter` is the free Builder plan."
                        },
                        "businessName": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "businessAddress": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "billingEmail": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "email"
                        },
                        "taxNumber": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string",
                          "pattern": "^[0-9]+$",
                          "description": "Unix time in seconds, encoded as a decimal string.",
                          "example": "1758700800"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "updated_at": {
                          "type": "string",
                          "pattern": "^[0-9]+$",
                          "description": "Unix time in seconds, encoded as a decimal string.",
                          "example": "1758700800"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "id": 1024,
                  "name": "Northwind",
                  "plan": "pro",
                  "businessName": "Northwind Traders Ltd",
                  "businessAddress": "1 Harbour Street, Portsmouth",
                  "billingEmail": "accounts@example.com",
                  "taxNumber": "GB987654321",
                  "created_at": "1758700800",
                  "updated_at": "1758873600"
                }
              }
            }
          },
          "400": {
            "description": "The account id in the path is not a number (`Bad Request`), or the request names a plan (`plan_not_settable`), which only billing changes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan_not_settable",
                  "message": "The plan is chosen with PUT /v4/user/accounts/{id}/billing/plan, which bills for it.",
                  "status": 400
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or the token is not a user token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "status": 401
                }
              }
            }
          },
          "404": {
            "description": "No account with that id that you belong to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not Found",
                  "status": 404
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The account id, as returned by `GET /v4/user/accounts`.",
            "schema": {
              "type": "integer"
            },
            "example": 1024
          }
        ]
      }
    },
    "/v4/user/accounts/{id}/billing": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "Get an account's billing",
        "description": "Returns the account's plan, subscription, card on file, next invoice and recent invoices, read live from Stripe. An account that has never had a Stripe customer gets its plan and `billing_status` with every other section empty.\n\nRequires a bearer token for a **user** principal who belongs to the account (from `POST /v4/user/oauth/token` or a sign-in with the platform's OpenID Connect provider). Account-principal tokens are refused with `401`.",
        "operationId": "getV4UserAccountsByIdBilling",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The billing summary.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The account's billing state, read live from Stripe.",
                  "properties": {
                    "plan": {
                      "type": "object",
                      "description": "The plan the account is on. Same catalog entry as `GET /v4/billing/plans`, but with camelCase field names.",
                      "properties": {
                        "id": {
                          "type": "string",
                          "enum": [
                            "starter",
                            "pro",
                            "team",
                            "enterprise"
                          ]
                        },
                        "name": {
                          "type": "string"
                        },
                        "priceCents": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "Monthly price in US cents."
                        },
                        "lookupKey": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Stripe price lookup key; `null` for plans without a subscription."
                        },
                        "blurb": {
                          "type": "string"
                        },
                        "features": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "selfServe": {
                          "type": "boolean"
                        }
                      }
                    },
                    "billing_status": {
                      "type": "string",
                      "enum": [
                        "none",
                        "active",
                        "trialing",
                        "past_due",
                        "unpaid",
                        "canceled",
                        "incomplete"
                      ],
                      "description": "`none` until a paid plan is first chosen; afterwards the Stripe subscription status."
                    },
                    "subscription": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "description": "The paid subscription, or `null` when there has never been one.",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Stripe subscription id."
                        },
                        "status": {
                          "type": "string",
                          "description": "Stripe subscription status."
                        },
                        "current_period_end": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "cancel_at_period_end": {
                          "type": "boolean",
                          "description": "True when the plan drops to Builder at `current_period_end`. Undo with the resume endpoint."
                        },
                        "plan": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "starter",
                            "pro",
                            "team",
                            "enterprise",
                            null
                          ],
                          "description": "The plan the subscription is for."
                        }
                      }
                    },
                    "payment_method": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "description": "The default card, or `null` when none is on file.",
                      "properties": {
                        "brand": {
                          "type": "string",
                          "example": "visa"
                        },
                        "last4": {
                          "type": "string",
                          "example": "4242"
                        },
                        "exp_month": {
                          "type": "integer"
                        },
                        "exp_year": {
                          "type": "integer"
                        }
                      }
                    },
                    "upcoming_invoice": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "description": "The next invoice, or `null` when none is due (for example, when cancelling at period end).",
                      "properties": {
                        "amount_due_cents": {
                          "type": "integer"
                        },
                        "currency": {
                          "type": "string",
                          "example": "usd"
                        },
                        "due": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "description": {
                                "type": "string"
                              },
                              "amount_cents": {
                                "type": "integer",
                                "description": "Negative for a proration credit."
                              }
                            }
                          }
                        }
                      }
                    },
                    "invoices": {
                      "type": "array",
                      "description": "The 12 most recent invoices, newest first.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Stripe invoice id."
                          },
                          "number": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "period_start": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "period_end": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "total_cents": {
                            "type": "integer"
                          },
                          "amount_paid_cents": {
                            "type": "integer"
                          },
                          "currency": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "draft",
                              "open",
                              "paid",
                              "uncollectible",
                              "void"
                            ]
                          },
                          "hosted_invoice_url": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uri",
                            "description": "Stripe-hosted page to view or pay the invoice."
                          },
                          "invoice_pdf": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uri"
                          },
                          "plan": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Display name of the plan invoiced."
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "plan",
                    "billing_status",
                    "subscription",
                    "payment_method",
                    "upcoming_invoice",
                    "invoices"
                  ]
                },
                "example": {
                  "plan": {
                    "id": "pro",
                    "name": "Pro",
                    "priceCents": 2000,
                    "lookupKey": "qatom_pro_monthly",
                    "blurb": "A real catalog, several agents, twin credentials.",
                    "features": [
                      "3 agents",
                      "Up to 50 catalog items",
                      "Up to 250 transactions / month",
                      "Twin URLs and API keys"
                    ],
                    "selfServe": true
                  },
                  "billing_status": "active",
                  "subscription": {
                    "id": "sub_1QExampleSubscription",
                    "status": "active",
                    "current_period_end": "2026-10-24T00:00:00.000Z",
                    "cancel_at_period_end": false,
                    "plan": "pro"
                  },
                  "payment_method": {
                    "brand": "visa",
                    "last4": "4242",
                    "exp_month": 12,
                    "exp_year": 2028
                  },
                  "upcoming_invoice": {
                    "amount_due_cents": 2000,
                    "currency": "usd",
                    "due": "2026-10-24T00:00:00.000Z",
                    "lines": [
                      {
                        "description": "1 × Pro (at $20.00 / month)",
                        "amount_cents": 2000
                      }
                    ]
                  },
                  "invoices": [
                    {
                      "id": "in_1QExampleInvoice",
                      "number": "A1B2C3D4-0001",
                      "created": "2026-09-24T00:00:00.000Z",
                      "period_start": "2026-09-24T00:00:00.000Z",
                      "period_end": "2026-10-24T00:00:00.000Z",
                      "total_cents": 2000,
                      "amount_paid_cents": 2000,
                      "currency": "usd",
                      "status": "paid",
                      "hosted_invoice_url": "https://invoice.stripe.com/i/acct_Example/test_Example",
                      "invoice_pdf": "https://pay.stripe.com/invoice/acct_Example/test_Example/pdf",
                      "plan": "Pro"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The account id in the path is not a number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Bad Request",
                  "status": 400
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or the token is not a user token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "status": 401
                }
              }
            }
          },
          "404": {
            "description": "No account with that id that you belong to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not Found",
                  "status": 404
                }
              }
            }
          },
          "502": {
            "description": "Stripe could not be reached or refused the request for a reason other than the card. Nothing is known to have changed; retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Billing is unavailable right now",
                  "status": 502
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The account id, as returned by `GET /v4/user/accounts`.",
            "schema": {
              "type": "integer"
            },
            "example": 1024
          }
        ]
      }
    },
    "/v4/user/accounts/{id}/billing/payment-method": {
      "post": {
        "tags": [
          "Billing"
        ],
        "summary": "Replace the card",
        "description": "Makes a payment method from a confirmed SetupIntent the account's default card. If the subscription is behind because a payment failed, the open invoice is paid with the new card straight away (`retried: true`) instead of waiting for Stripe's next retry.\n\nRequires a bearer token for a **user** principal who belongs to the account (from `POST /v4/user/oauth/token` or a sign-in with the platform's OpenID Connect provider). Account-principal tokens are refused with `401`.",
        "operationId": "postV4UserAccountsByIdBillingPaymentMethod",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "payment_method_id": {
                    "type": "string",
                    "pattern": "^pm_",
                    "description": "A payment method from a confirmed SetupIntent."
                  }
                },
                "required": [
                  "payment_method_id"
                ]
              },
              "example": {
                "payment_method_id": "pm_1QExamplePaymentMethod"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The card was replaced.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "retried": {
                      "type": "boolean",
                      "description": "Whether an open invoice was paid with the new card."
                    },
                    "billing": {
                      "type": "object",
                      "description": "The account's billing state, read live from Stripe.",
                      "properties": {
                        "plan": {
                          "type": "object",
                          "description": "The plan the account is on. Same catalog entry as `GET /v4/billing/plans`, but with camelCase field names.",
                          "properties": {
                            "id": {
                              "type": "string",
                              "enum": [
                                "starter",
                                "pro",
                                "team",
                                "enterprise"
                              ]
                            },
                            "name": {
                              "type": "string"
                            },
                            "priceCents": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "Monthly price in US cents."
                            },
                            "lookupKey": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Stripe price lookup key; `null` for plans without a subscription."
                            },
                            "blurb": {
                              "type": "string"
                            },
                            "features": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "selfServe": {
                              "type": "boolean"
                            }
                          }
                        },
                        "billing_status": {
                          "type": "string",
                          "enum": [
                            "none",
                            "active",
                            "trialing",
                            "past_due",
                            "unpaid",
                            "canceled",
                            "incomplete"
                          ],
                          "description": "`none` until a paid plan is first chosen; afterwards the Stripe subscription status."
                        },
                        "subscription": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "description": "The paid subscription, or `null` when there has never been one.",
                          "properties": {
                            "id": {
                              "type": "string",
                              "description": "Stripe subscription id."
                            },
                            "status": {
                              "type": "string",
                              "description": "Stripe subscription status."
                            },
                            "current_period_end": {
                              "type": "string",
                              "format": "date-time"
                            },
                            "cancel_at_period_end": {
                              "type": "boolean",
                              "description": "True when the plan drops to Builder at `current_period_end`. Undo with the resume endpoint."
                            },
                            "plan": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "starter",
                                "pro",
                                "team",
                                "enterprise",
                                null
                              ],
                              "description": "The plan the subscription is for."
                            }
                          }
                        },
                        "payment_method": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "description": "The default card, or `null` when none is on file.",
                          "properties": {
                            "brand": {
                              "type": "string",
                              "example": "visa"
                            },
                            "last4": {
                              "type": "string",
                              "example": "4242"
                            },
                            "exp_month": {
                              "type": "integer"
                            },
                            "exp_year": {
                              "type": "integer"
                            }
                          }
                        },
                        "upcoming_invoice": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "description": "The next invoice, or `null` when none is due (for example, when cancelling at period end).",
                          "properties": {
                            "amount_due_cents": {
                              "type": "integer"
                            },
                            "currency": {
                              "type": "string",
                              "example": "usd"
                            },
                            "due": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "date-time"
                            },
                            "lines": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "description": {
                                    "type": "string"
                                  },
                                  "amount_cents": {
                                    "type": "integer",
                                    "description": "Negative for a proration credit."
                                  }
                                }
                              }
                            }
                          }
                        },
                        "invoices": {
                          "type": "array",
                          "description": "The 12 most recent invoices, newest first.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "description": "Stripe invoice id."
                              },
                              "number": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "created": {
                                "type": "string",
                                "format": "date-time"
                              },
                              "period_start": {
                                "type": "string",
                                "format": "date-time"
                              },
                              "period_end": {
                                "type": "string",
                                "format": "date-time"
                              },
                              "total_cents": {
                                "type": "integer"
                              },
                              "amount_paid_cents": {
                                "type": "integer"
                              },
                              "currency": {
                                "type": "string"
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "draft",
                                  "open",
                                  "paid",
                                  "uncollectible",
                                  "void"
                                ]
                              },
                              "hosted_invoice_url": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "uri",
                                "description": "Stripe-hosted page to view or pay the invoice."
                              },
                              "invoice_pdf": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "uri"
                              },
                              "plan": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Display name of the plan invoiced."
                              }
                            }
                          }
                        }
                      },
                      "required": [
                        "plan",
                        "billing_status",
                        "subscription",
                        "payment_method",
                        "upcoming_invoice",
                        "invoices"
                      ]
                    }
                  },
                  "required": [
                    "retried",
                    "billing"
                  ]
                },
                "example": {
                  "retried": false,
                  "billing": {
                    "plan": {
                      "id": "pro",
                      "name": "Pro",
                      "priceCents": 2000,
                      "lookupKey": "qatom_pro_monthly",
                      "blurb": "A real catalog, several agents, twin credentials.",
                      "features": [
                        "3 agents",
                        "Up to 50 catalog items",
                        "Up to 250 transactions / month",
                        "Twin URLs and API keys"
                      ],
                      "selfServe": true
                    },
                    "billing_status": "active",
                    "subscription": {
                      "id": "sub_1QExampleSubscription",
                      "status": "active",
                      "current_period_end": "2026-10-24T00:00:00.000Z",
                      "cancel_at_period_end": false,
                      "plan": "pro"
                    },
                    "payment_method": {
                      "brand": "visa",
                      "last4": "4242",
                      "exp_month": 12,
                      "exp_year": 2028
                    },
                    "upcoming_invoice": {
                      "amount_due_cents": 2000,
                      "currency": "usd",
                      "due": "2026-10-24T00:00:00.000Z",
                      "lines": [
                        {
                          "description": "1 × Pro (at $20.00 / month)",
                          "amount_cents": 2000
                        }
                      ]
                    },
                    "invoices": [
                      {
                        "id": "in_1QExampleInvoice",
                        "number": "A1B2C3D4-0001",
                        "created": "2026-09-24T00:00:00.000Z",
                        "period_start": "2026-09-24T00:00:00.000Z",
                        "period_end": "2026-10-24T00:00:00.000Z",
                        "total_cents": 2000,
                        "amount_paid_cents": 2000,
                        "currency": "usd",
                        "status": "paid",
                        "hosted_invoice_url": "https://invoice.stripe.com/i/acct_Example/test_Example",
                        "invoice_pdf": "https://pay.stripe.com/invoice/acct_Example/test_Example/pdf",
                        "plan": "Pro"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "`payment_method_id` is missing or not a `pm_` id, the account id is not a number, or the payment method belongs to a different customer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "payment_method_id is required",
                  "status": 400
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or the token is not a user token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "status": 401
                }
              }
            }
          },
          "402": {
            "description": "The new card was saved but declined when paying the open invoice. The error carries Stripe's explanation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Your card has insufficient funds.",
                  "status": 402
                }
              }
            }
          },
          "404": {
            "description": "No account with that id that you belong to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not Found",
                  "status": 404
                }
              }
            }
          },
          "502": {
            "description": "Stripe could not be reached or refused the request for a reason other than the card. Nothing is known to have changed; retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Billing is unavailable right now",
                  "status": 502
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The account id, as returned by `GET /v4/user/accounts`.",
            "schema": {
              "type": "integer"
            },
            "example": 1024
          }
        ]
      }
    },
    "/v4/user/accounts/{id}/billing/plan": {
      "put": {
        "tags": [
          "Billing"
        ],
        "summary": "Change plan",
        "description": "Moves the account to another self-serve plan. Returns the billing summary afterwards.\n\n- **To a paid plan with no subscription:** subscribes and charges the card at once. A card is required: pass `payment_method_id` from a confirmed SetupIntent, or have one on file already.\n- **Between paid plans:** switches immediately. The difference is prorated both ways, so a downgrade credits the unused time on the next invoice. A pending cancellation is undone.\n- **To `starter` (Builder, free):** the paid plan runs to the end of the period already paid for and then ends. Until then the summary shows `subscription.cancel_at_period_end: true` and it can be undone with the resume endpoint.\n\n`enterprise` is arranged with sales and is refused here.\n\nRequires a bearer token for a **user** principal who belongs to the account (from `POST /v4/user/oauth/token` or a sign-in with the platform's OpenID Connect provider). Account-principal tokens are refused with `401`.",
        "operationId": "putV4UserAccountsByIdBillingPlan",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "plan": {
                    "type": "string",
                    "enum": [
                      "starter",
                      "pro",
                      "team"
                    ],
                    "description": "The plan to move to."
                  },
                  "payment_method_id": {
                    "type": "string",
                    "pattern": "^pm_",
                    "description": "A payment method from a confirmed SetupIntent. Becomes the account's default card. Needed the first time a paid plan is chosen, unless a card is already on file."
                  }
                },
                "required": [
                  "plan"
                ]
              },
              "examples": {
                "subscribe": {
                  "summary": "Subscribe with a new card",
                  "value": {
                    "plan": "pro",
                    "payment_method_id": "pm_1QExamplePaymentMethod"
                  }
                },
                "upgrade": {
                  "summary": "Upgrade using the card on file",
                  "value": {
                    "plan": "team"
                  }
                },
                "cancel": {
                  "summary": "Drop to the free plan at period end",
                  "value": {
                    "plan": "starter"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The billing summary after the change.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The account's billing state, read live from Stripe.",
                  "properties": {
                    "plan": {
                      "type": "object",
                      "description": "The plan the account is on. Same catalog entry as `GET /v4/billing/plans`, but with camelCase field names.",
                      "properties": {
                        "id": {
                          "type": "string",
                          "enum": [
                            "starter",
                            "pro",
                            "team",
                            "enterprise"
                          ]
                        },
                        "name": {
                          "type": "string"
                        },
                        "priceCents": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "Monthly price in US cents."
                        },
                        "lookupKey": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Stripe price lookup key; `null` for plans without a subscription."
                        },
                        "blurb": {
                          "type": "string"
                        },
                        "features": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "selfServe": {
                          "type": "boolean"
                        }
                      }
                    },
                    "billing_status": {
                      "type": "string",
                      "enum": [
                        "none",
                        "active",
                        "trialing",
                        "past_due",
                        "unpaid",
                        "canceled",
                        "incomplete"
                      ],
                      "description": "`none` until a paid plan is first chosen; afterwards the Stripe subscription status."
                    },
                    "subscription": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "description": "The paid subscription, or `null` when there has never been one.",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Stripe subscription id."
                        },
                        "status": {
                          "type": "string",
                          "description": "Stripe subscription status."
                        },
                        "current_period_end": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "cancel_at_period_end": {
                          "type": "boolean",
                          "description": "True when the plan drops to Builder at `current_period_end`. Undo with the resume endpoint."
                        },
                        "plan": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "starter",
                            "pro",
                            "team",
                            "enterprise",
                            null
                          ],
                          "description": "The plan the subscription is for."
                        }
                      }
                    },
                    "payment_method": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "description": "The default card, or `null` when none is on file.",
                      "properties": {
                        "brand": {
                          "type": "string",
                          "example": "visa"
                        },
                        "last4": {
                          "type": "string",
                          "example": "4242"
                        },
                        "exp_month": {
                          "type": "integer"
                        },
                        "exp_year": {
                          "type": "integer"
                        }
                      }
                    },
                    "upcoming_invoice": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "description": "The next invoice, or `null` when none is due (for example, when cancelling at period end).",
                      "properties": {
                        "amount_due_cents": {
                          "type": "integer"
                        },
                        "currency": {
                          "type": "string",
                          "example": "usd"
                        },
                        "due": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "description": {
                                "type": "string"
                              },
                              "amount_cents": {
                                "type": "integer",
                                "description": "Negative for a proration credit."
                              }
                            }
                          }
                        }
                      }
                    },
                    "invoices": {
                      "type": "array",
                      "description": "The 12 most recent invoices, newest first.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Stripe invoice id."
                          },
                          "number": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "period_start": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "period_end": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "total_cents": {
                            "type": "integer"
                          },
                          "amount_paid_cents": {
                            "type": "integer"
                          },
                          "currency": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "draft",
                              "open",
                              "paid",
                              "uncollectible",
                              "void"
                            ]
                          },
                          "hosted_invoice_url": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uri",
                            "description": "Stripe-hosted page to view or pay the invoice."
                          },
                          "invoice_pdf": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uri"
                          },
                          "plan": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Display name of the plan invoiced."
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "plan",
                    "billing_status",
                    "subscription",
                    "payment_method",
                    "upcoming_invoice",
                    "invoices"
                  ]
                },
                "example": {
                  "plan": {
                    "id": "pro",
                    "name": "Pro",
                    "priceCents": 2000,
                    "lookupKey": "qatom_pro_monthly",
                    "blurb": "A real catalog, several agents, twin credentials.",
                    "features": [
                      "3 agents",
                      "Up to 50 catalog items",
                      "Up to 250 transactions / month",
                      "Twin URLs and API keys"
                    ],
                    "selfServe": true
                  },
                  "billing_status": "active",
                  "subscription": {
                    "id": "sub_1QExampleSubscription",
                    "status": "active",
                    "current_period_end": "2026-10-24T00:00:00.000Z",
                    "cancel_at_period_end": false,
                    "plan": "pro"
                  },
                  "payment_method": {
                    "brand": "visa",
                    "last4": "4242",
                    "exp_month": 12,
                    "exp_year": 2028
                  },
                  "upcoming_invoice": {
                    "amount_due_cents": 2000,
                    "currency": "usd",
                    "due": "2026-10-24T00:00:00.000Z",
                    "lines": [
                      {
                        "description": "1 × Pro (at $20.00 / month)",
                        "amount_cents": 2000
                      }
                    ]
                  },
                  "invoices": [
                    {
                      "id": "in_1QExampleInvoice",
                      "number": "A1B2C3D4-0001",
                      "created": "2026-09-24T00:00:00.000Z",
                      "period_start": "2026-09-24T00:00:00.000Z",
                      "period_end": "2026-10-24T00:00:00.000Z",
                      "total_cents": 2000,
                      "amount_paid_cents": 2000,
                      "currency": "usd",
                      "status": "paid",
                      "hosted_invoice_url": "https://invoice.stripe.com/i/acct_Example/test_Example",
                      "invoice_pdf": "https://pay.stripe.com/invoice/acct_Example/test_Example/pdf",
                      "plan": "Pro"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The plan is unknown or not self-serve, the account id is not a number, or the payment method belongs to a different customer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "plan must be one of starter, pro, team, enterprise",
                  "status": 400
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or the token is not a user token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "status": 401
                }
              }
            }
          },
          "402": {
            "description": "The card was declined, or no card is on file for a paid plan. The error carries Stripe's explanation, suitable to show beside the card form. The plan did not change.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Your card was declined.",
                  "status": 402
                }
              }
            }
          },
          "404": {
            "description": "No account with that id that you belong to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not Found",
                  "status": 404
                }
              }
            }
          },
          "502": {
            "description": "Stripe could not be reached or refused the request for a reason other than the card. Nothing is known to have changed; retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Billing is unavailable right now",
                  "status": 502
                }
              }
            }
          },
          "503": {
            "description": "The paid plan's price is not configured in Stripe. Contact support.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The Pro price is not set up in Stripe (lookup key qatom_pro_monthly)",
                  "status": 503
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The account id, as returned by `GET /v4/user/accounts`.",
            "schema": {
              "type": "integer"
            },
            "example": 1024
          }
        ]
      }
    },
    "/v4/user/accounts/{id}/billing/resume": {
      "post": {
        "tags": [
          "Billing"
        ],
        "summary": "Keep the paid plan",
        "description": "Undoes a move to the free plan that has not taken effect yet, so the paid subscription carries on renewing. Succeeds without change when no cancellation is pending. No request body is needed.\n\nRequires a bearer token for a **user** principal who belongs to the account (from `POST /v4/user/oauth/token` or a sign-in with the platform's OpenID Connect provider). Account-principal tokens are refused with `401`.",
        "operationId": "postV4UserAccountsByIdBillingResume",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "The billing summary afterwards.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "The account's billing state, read live from Stripe.",
                  "properties": {
                    "plan": {
                      "type": "object",
                      "description": "The plan the account is on. Same catalog entry as `GET /v4/billing/plans`, but with camelCase field names.",
                      "properties": {
                        "id": {
                          "type": "string",
                          "enum": [
                            "starter",
                            "pro",
                            "team",
                            "enterprise"
                          ]
                        },
                        "name": {
                          "type": "string"
                        },
                        "priceCents": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "Monthly price in US cents."
                        },
                        "lookupKey": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Stripe price lookup key; `null` for plans without a subscription."
                        },
                        "blurb": {
                          "type": "string"
                        },
                        "features": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "selfServe": {
                          "type": "boolean"
                        }
                      }
                    },
                    "billing_status": {
                      "type": "string",
                      "enum": [
                        "none",
                        "active",
                        "trialing",
                        "past_due",
                        "unpaid",
                        "canceled",
                        "incomplete"
                      ],
                      "description": "`none` until a paid plan is first chosen; afterwards the Stripe subscription status."
                    },
                    "subscription": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "description": "The paid subscription, or `null` when there has never been one.",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Stripe subscription id."
                        },
                        "status": {
                          "type": "string",
                          "description": "Stripe subscription status."
                        },
                        "current_period_end": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "cancel_at_period_end": {
                          "type": "boolean",
                          "description": "True when the plan drops to Builder at `current_period_end`. Undo with the resume endpoint."
                        },
                        "plan": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "starter",
                            "pro",
                            "team",
                            "enterprise",
                            null
                          ],
                          "description": "The plan the subscription is for."
                        }
                      }
                    },
                    "payment_method": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "description": "The default card, or `null` when none is on file.",
                      "properties": {
                        "brand": {
                          "type": "string",
                          "example": "visa"
                        },
                        "last4": {
                          "type": "string",
                          "example": "4242"
                        },
                        "exp_month": {
                          "type": "integer"
                        },
                        "exp_year": {
                          "type": "integer"
                        }
                      }
                    },
                    "upcoming_invoice": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "description": "The next invoice, or `null` when none is due (for example, when cancelling at period end).",
                      "properties": {
                        "amount_due_cents": {
                          "type": "integer"
                        },
                        "currency": {
                          "type": "string",
                          "example": "usd"
                        },
                        "due": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "description": {
                                "type": "string"
                              },
                              "amount_cents": {
                                "type": "integer",
                                "description": "Negative for a proration credit."
                              }
                            }
                          }
                        }
                      }
                    },
                    "invoices": {
                      "type": "array",
                      "description": "The 12 most recent invoices, newest first.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Stripe invoice id."
                          },
                          "number": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "period_start": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "period_end": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "total_cents": {
                            "type": "integer"
                          },
                          "amount_paid_cents": {
                            "type": "integer"
                          },
                          "currency": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "draft",
                              "open",
                              "paid",
                              "uncollectible",
                              "void"
                            ]
                          },
                          "hosted_invoice_url": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uri",
                            "description": "Stripe-hosted page to view or pay the invoice."
                          },
                          "invoice_pdf": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uri"
                          },
                          "plan": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Display name of the plan invoiced."
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "plan",
                    "billing_status",
                    "subscription",
                    "payment_method",
                    "upcoming_invoice",
                    "invoices"
                  ]
                },
                "example": {
                  "plan": {
                    "id": "pro",
                    "name": "Pro",
                    "priceCents": 2000,
                    "lookupKey": "qatom_pro_monthly",
                    "blurb": "A real catalog, several agents, twin credentials.",
                    "features": [
                      "3 agents",
                      "Up to 50 catalog items",
                      "Up to 250 transactions / month",
                      "Twin URLs and API keys"
                    ],
                    "selfServe": true
                  },
                  "billing_status": "active",
                  "subscription": {
                    "id": "sub_1QExampleSubscription",
                    "status": "active",
                    "current_period_end": "2026-10-24T00:00:00.000Z",
                    "cancel_at_period_end": false,
                    "plan": "pro"
                  },
                  "payment_method": {
                    "brand": "visa",
                    "last4": "4242",
                    "exp_month": 12,
                    "exp_year": 2028
                  },
                  "upcoming_invoice": {
                    "amount_due_cents": 2000,
                    "currency": "usd",
                    "due": "2026-10-24T00:00:00.000Z",
                    "lines": [
                      {
                        "description": "1 × Pro (at $20.00 / month)",
                        "amount_cents": 2000
                      }
                    ]
                  },
                  "invoices": [
                    {
                      "id": "in_1QExampleInvoice",
                      "number": "A1B2C3D4-0001",
                      "created": "2026-09-24T00:00:00.000Z",
                      "period_start": "2026-09-24T00:00:00.000Z",
                      "period_end": "2026-10-24T00:00:00.000Z",
                      "total_cents": 2000,
                      "amount_paid_cents": 2000,
                      "currency": "usd",
                      "status": "paid",
                      "hosted_invoice_url": "https://invoice.stripe.com/i/acct_Example/test_Example",
                      "invoice_pdf": "https://pay.stripe.com/invoice/acct_Example/test_Example/pdf",
                      "plan": "Pro"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The account id in the path is not a number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Bad Request",
                  "status": 400
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or the token is not a user token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "status": 401
                }
              }
            }
          },
          "404": {
            "description": "No account with that id that you belong to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not Found",
                  "status": 404
                }
              }
            }
          },
          "409": {
            "description": "There is no subscription, or it has already ended. Choose a plan instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The subscription has already ended; choose a plan instead",
                  "status": 409
                }
              }
            }
          },
          "502": {
            "description": "Stripe could not be reached or refused the request for a reason other than the card. Nothing is known to have changed; retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Billing is unavailable right now",
                  "status": 502
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The account id, as returned by `GET /v4/user/accounts`.",
            "schema": {
              "type": "integer"
            },
            "example": 1024
          }
        ]
      }
    },
    "/v4/user/accounts/{id}/billing/setup-intent": {
      "post": {
        "tags": [
          "Billing"
        ],
        "summary": "Start collecting a card",
        "description": "Creates a Stripe SetupIntent for saving a card against the account, creating the account's Stripe customer first if it has none. Confirm the returned `client_secret` with Stripe Elements (`stripe.confirmSetup`); the payment method id Stripe gives back is what the plan and payment-method endpoints take. Nothing is charged. No request body is needed.\n\nRequires a bearer token for a **user** principal who belongs to the account (from `POST /v4/user/oauth/token` or a sign-in with the platform's OpenID Connect provider). Account-principal tokens are refused with `401`.",
        "operationId": "postV4UserAccountsByIdBillingSetupIntent",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "The SetupIntent to confirm in the browser.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "client_secret": {
                      "type": "string",
                      "description": "The SetupIntent client secret. Pass it to Stripe Elements; do not log or store it."
                    },
                    "customer_id": {
                      "type": "string",
                      "description": "The account's Stripe customer id."
                    }
                  },
                  "required": [
                    "client_secret",
                    "customer_id"
                  ]
                },
                "example": {
                  "client_secret": "seti_1QExampleSetupIntent_secret_ExampleExample",
                  "customer_id": "cus_ExampleCustomer"
                }
              }
            }
          },
          "400": {
            "description": "The account id in the path is not a number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Bad Request",
                  "status": 400
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or the token is not a user token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "status": 401
                }
              }
            }
          },
          "404": {
            "description": "No account with that id that you belong to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not Found",
                  "status": 404
                }
              }
            }
          },
          "500": {
            "description": "Stripe created the SetupIntent without a client secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Stripe returned no client secret",
                  "status": 500
                }
              }
            }
          },
          "502": {
            "description": "Stripe could not be reached or refused the request for a reason other than the card. Nothing is known to have changed; retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Billing is unavailable right now",
                  "status": 502
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The account id, as returned by `GET /v4/user/accounts`.",
            "schema": {
              "type": "integer"
            },
            "example": 1024
          }
        ]
      }
    },
    "/v4/user/accounts/{id}/mcp-servers": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "summary": "List an account's managed MCP servers",
        "description": "Returns the account's managed MCP servers, newest first, as last recorded by the fleet manager: status, address, custom domain, and whether each runs the fleet's latest release (`update_available`). Deleted servers are left out. An agent the user granted this account when they signed it in may read it too.\n\nRequires a bearer token for a **user** principal (from `POST /v4/user/oauth/token` or a sign-in with the platform's OpenID Connect provider). Account-principal tokens are refused with `401`.",
        "operationId": "getV4UserAccountsByIdMcpServers",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The servers, newest first. Empty when the account has none.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "servers": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "name": {
                            "type": "string"
                          },
                          "description": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "hostname": {
                            "type": "string"
                          },
                          "private": {
                            "type": "boolean"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "provisioning",
                              "running",
                              "stopped",
                              "failed"
                            ]
                          },
                          "custom_domain": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "custom_domain_status": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "enum": [
                              "pending_validation",
                              "active",
                              "failed",
                              null
                            ]
                          },
                          "deploy_version": {
                            "type": "integer"
                          },
                          "url": {
                            "type": "string",
                            "format": "uri",
                            "description": "Where agents connect: the custom domain once active."
                          },
                          "update_available": {
                            "type": "boolean"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    },
                    "latest_deploy_version": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "servers",
                    "latest_deploy_version"
                  ]
                },
                "example": {
                  "servers": [
                    {
                      "id": 12,
                      "name": "Northwind tools",
                      "description": null,
                      "hostname": "northwind-a1b2",
                      "private": false,
                      "status": "running",
                      "custom_domain": null,
                      "custom_domain_status": null,
                      "deploy_version": 16,
                      "url": "https://northwind-a1b2.mcp.m.todaq.net",
                      "update_available": false,
                      "created_at": "2026-09-01T12:00:00.000Z",
                      "updated_at": "2026-10-08T15:31:20.000Z"
                    }
                  ],
                  "latest_deploy_version": 16
                }
              }
            }
          },
          "400": {
            "description": "The account id in the path is not a number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Bad Request",
                  "status": 400
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or the token is not a user token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "status": 401
                }
              }
            }
          },
          "404": {
            "description": "No account with that id that you belong to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not Found",
                  "status": 404
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The account id, as returned by `GET /v4/user/accounts`.",
            "schema": {
              "type": "integer"
            },
            "example": 1024
          }
        ]
      }
    },
    "/v4/user/accounts/{id}/secrets": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "summary": "Get an account's secrets",
        "description": "Returns the account's `client_id`/`client_secret` pair, used to obtain an account token, and its publishable `public_secret`, used by browser-side embeds and guest checkout. Keep the client secret on your server. The same credentials `GET /v4/account` returns, for a user who belongs to the account rather than the account itself. Every read is recorded.\n\nOnly from the platform's own apps: a token from `POST /v4/user/oauth/token`, or a sign-in with the OpenID Connect provider by one of the platform's own clients. A token held by an app the user connected, such as an AI agent's MCP connector, is refused with `403`.\n\nRequires a bearer token for a **user** principal (from `POST /v4/user/oauth/token` or a sign-in with the platform's OpenID Connect provider). Account-principal tokens are refused with `401`.",
        "operationId": "getV4UserAccountsByIdSecrets",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The account's secrets, or `null` when the account has none.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "secrets": {
                      "oneOf": [
                        {
                          "type": "object",
                          "properties": {
                            "client_id": {
                              "type": "string",
                              "description": "Identifies the account when obtaining a token."
                            },
                            "client_secret": {
                              "type": "string",
                              "description": "Server-side only. With `client_id`, obtains an account token from `POST /v4/account/oauth/token`."
                            },
                            "public_secret": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Publishable: safe to put in a browser, as embeds and guest checkout do."
                            },
                            "created_at": {
                              "type": "string",
                              "pattern": "^[0-9]+$",
                              "description": "Unix time in seconds, encoded as a decimal string.",
                              "example": "1758700800"
                            },
                            "updated_at": {
                              "type": "string",
                              "pattern": "^[0-9]+$",
                              "description": "Unix time in seconds, encoded as a decimal string.",
                              "example": "1758700800"
                            }
                          },
                          "required": [
                            "client_id",
                            "client_secret",
                            "public_secret",
                            "created_at",
                            "updated_at"
                          ]
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "secrets"
                  ]
                },
                "example": {
                  "secrets": {
                    "client_id": "mid_4f1c9a2e7b3d4e8f9a0b1c2d3e4f5a6b",
                    "client_secret": "mc_9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b",
                    "public_secret": "mp_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d",
                    "created_at": "1758700800",
                    "updated_at": "1758700800"
                  }
                }
              }
            }
          },
          "400": {
            "description": "The account id in the path is not a number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Bad Request",
                  "status": 400
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or the token is not a user token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "status": 401
                }
              }
            }
          },
          "403": {
            "description": "The token belongs to an app the user connected, not to one of the platform's own.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Account secrets are not available to connected apps",
                  "status": 403
                }
              }
            }
          },
          "404": {
            "description": "No account with that id that you belong to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not Found",
                  "status": 404
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The account id, as returned by `GET /v4/user/accounts`.",
            "schema": {
              "type": "integer"
            },
            "example": 1024
          }
        ]
      }
    },
    "/v4/user/accounts/{id}/twin": {
      "post": {
        "tags": [
          "Accounts"
        ],
        "summary": "Ensure the account has a twin",
        "description": "Idempotent. When the account already has a twin, returns it with `created: false` and status `200`. Otherwise provisions a primary twin for the account and returns it with `created: true` and status `201`. Neither carries the twin's `key`: `GET /v4/twins` lists it. No request body is needed.\n\nRequires a bearer token for a **user** principal (from `POST /v4/user/oauth/token` or a sign-in with the platform's OpenID Connect provider). Account-principal tokens are refused with `401`.",
        "operationId": "postV4UserAccountsByIdTwin",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "The account already had a twin; it is returned unchanged.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "twin": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "hostname": {
                          "type": "string"
                        },
                        "twin_type": {
                          "type": "string",
                          "enum": [
                            "user",
                            "account",
                            "agent",
                            "provider"
                          ]
                        }
                      }
                    },
                    "created": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    }
                  }
                },
                "example": {
                  "twin": {
                    "id": 3539,
                    "hostname": "00000000000000000000000000000000.micro.biz.todaq.net",
                    "twin_type": "account"
                  },
                  "created": false
                }
              }
            }
          },
          "201": {
            "description": "A main twin was provisioned for the account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "twin": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "hostname": {
                          "type": "string"
                        },
                        "twin_type": {
                          "type": "string",
                          "enum": [
                            "account"
                          ]
                        },
                        "label": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    },
                    "created": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                },
                "example": {
                  "twin": {
                    "id": 3540,
                    "hostname": "00000000000000000000000000000001.micro.biz.todaq.net",
                    "twin_type": "account",
                    "label": null
                  },
                  "created": true
                }
              }
            }
          },
          "400": {
            "description": "The account id in the path is not a number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Bad Request",
                  "status": 400
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or the token is not a user token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "status": 401
                }
              }
            }
          },
          "404": {
            "description": "No account with that id that you belong to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not Found",
                  "status": 404
                }
              }
            }
          },
          "500": {
            "description": "The twin could not be provisioned. Nothing was created; retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Internal Server Error",
                  "status": 500
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The account id, as returned by `GET /v4/user/accounts`.",
            "schema": {
              "type": "integer"
            },
            "example": 1024
          }
        ]
      }
    },
    "/v4/user/accounts/{id}/twins": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "summary": "List an account's twins",
        "description": "Returns every twin registered to the account, oldest first, with its balance in each digital quantity the platform tracks, read live from the twin. Balances are best-effort: a twin that does not answer in time is listed with the balances it did return, possibly none. Twin keys are never included.\n\nRequires a bearer token for a **user** principal (from `POST /v4/user/oauth/token` or a sign-in with the platform's OpenID Connect provider). Account-principal tokens are refused with `401`.",
        "operationId": "getV4UserAccountsByIdTwins",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The account's twins and their balances.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "twins": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer"
                              },
                              "hostname": {
                                "type": "string",
                                "description": "Unique hostname identifying the twin."
                              },
                              "twin_type": {
                                "type": "string",
                                "enum": [
                                  "user",
                                  "account",
                                  "agent",
                                  "provider"
                                ]
                              },
                              "label": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Display name, when one was given."
                              }
                            },
                            "required": [
                              "id",
                              "hostname",
                              "twin_type"
                            ]
                          },
                          {
                            "type": "object",
                            "properties": {
                              "balances": {
                                "$ref": "#/components/schemas/Balances"
                              },
                              "amounts": {
                                "type": "array",
                                "description": "The same balances, each named by its currency as `GET /v4/wallets` names them.",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "currency": {
                                      "type": "string",
                                      "example": "USD-TDN"
                                    },
                                    "dq_type": {
                                      "type": "string"
                                    },
                                    "amount": {
                                      "type": "number"
                                    }
                                  }
                                }
                              }
                            }
                          }
                        ]
                      }
                    }
                  },
                  "required": [
                    "twins"
                  ]
                },
                "example": {
                  "twins": [
                    {
                      "id": 3539,
                      "hostname": "00000000000000000000000000000000.micro.biz.todaq.net",
                      "twin_type": "account",
                      "label": null,
                      "balances": {
                        "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73": 1250
                      },
                      "amounts": [
                        {
                          "currency": "USD-TDN",
                          "dq_type": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73",
                          "amount": 1250
                        }
                      ]
                    },
                    {
                      "id": 3601,
                      "hostname": "0f6d2b9e4c3a41d7a8e5b6c1d2f3a4b5.micro.biz.todaq.net",
                      "twin_type": "provider",
                      "label": "Weather API",
                      "balances": {
                        "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73": 0
                      },
                      "amounts": [
                        {
                          "currency": "USD-TDN",
                          "dq_type": "412964f209c966234250eba05d1a118da128925084df9f5459eb9243157e452e73",
                          "amount": 0
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The account id in the path is not a number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Bad Request",
                  "status": 400
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or the token is not a user token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "status": 401
                }
              }
            }
          },
          "404": {
            "description": "No account with that id that you belong to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not Found",
                  "status": 404
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The account id, as returned by `GET /v4/user/accounts`.",
            "schema": {
              "type": "integer"
            },
            "example": 1024
          }
        ]
      },
      "post": {
        "tags": [
          "Accounts"
        ],
        "summary": "Add a provider twin to an account",
        "description": "Provisions a new provider twin for the account. Unlike `POST /v4/user/accounts/{id}/twin` this is not idempotent: every call creates another twin, so each thing you sell can be paid into its own twin. Provisioning waits for the twin to come up, so allow it more time than a read.\n\nRequires a bearer token for a **user** principal (from `POST /v4/user/oauth/token` or a sign-in with the platform's OpenID Connect provider). Account-principal tokens are refused with `401`.",
        "operationId": "postV4UserAccountsByIdTwins",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Display name for the twin, such as the product it is paid for. Trimmed; longer values are cut to 255 characters."
                  }
                }
              },
              "example": {
                "label": "Weather API"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The twin was provisioned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "twin": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "hostname": {
                          "type": "string",
                          "description": "Unique hostname identifying the twin."
                        },
                        "twin_type": {
                          "type": "string",
                          "enum": [
                            "user",
                            "account",
                            "agent",
                            "provider"
                          ]
                        },
                        "label": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Display name, when one was given."
                        }
                      },
                      "required": [
                        "id",
                        "hostname",
                        "twin_type"
                      ]
                    },
                    "created": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                },
                "example": {
                  "twin": {
                    "id": 3601,
                    "hostname": "0f6d2b9e4c3a41d7a8e5b6c1d2f3a4b5.micro.biz.todaq.net",
                    "twin_type": "provider",
                    "label": "Weather API"
                  },
                  "created": true
                }
              }
            }
          },
          "400": {
            "description": "The account id is not a number, or `label` is not a string.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "label must be a string",
                  "status": 400
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or the token is not a user token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "status": 401
                }
              }
            }
          },
          "404": {
            "description": "No account with that id that you belong to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not Found",
                  "status": 404
                }
              }
            }
          },
          "500": {
            "description": "The twin could not be provisioned. Nothing was created; retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Internal Server Error",
                  "status": 500
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The account id, as returned by `GET /v4/user/accounts`.",
            "schema": {
              "type": "integer"
            },
            "example": 1024
          }
        ]
      }
    },
    "/v4/user/accounts/{id}/users": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "summary": "List an account's users",
        "description": "Returns the users who belong to the account, in the order they joined.\n\nRequires a bearer token for a **user** principal (from `POST /v4/user/oauth/token` or a sign-in with the platform's OpenID Connect provider). Account-principal tokens are refused with `401`.",
        "operationId": "getV4UserAccountsByIdUsers",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The account's users.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "users": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "email": {
                            "type": "string",
                            "format": "email"
                          },
                          "metadata": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "description": "Free-form data stored against the user, if any."
                          },
                          "created_at": {
                            "type": "string",
                            "pattern": "^[0-9]+$",
                            "description": "Unix time in seconds, encoded as a decimal string.",
                            "example": "1758700800"
                          },
                          "updated_at": {
                            "type": "string",
                            "pattern": "^[0-9]+$",
                            "description": "Unix time in seconds, encoded as a decimal string.",
                            "example": "1758700800"
                          },
                          "deleted_at": {
                            "type": "null",
                            "description": "Always `null`: removed users are not listed."
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "users"
                  ]
                },
                "example": {
                  "users": [
                    {
                      "id": 88,
                      "email": "owner@example.com",
                      "metadata": null,
                      "created_at": "1758700800",
                      "updated_at": "1758700800",
                      "deleted_at": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The account id in the path is not a number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Bad Request",
                  "status": 400
                }
              }
            }
          },
          "401": {
            "description": "No bearer token, or the token is not a user token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unauthorized",
                  "status": 401
                }
              }
            }
          },
          "404": {
            "description": "No account with that id that you belong to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not Found",
                  "status": 404
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The account id, as returned by `GET /v4/user/accounts`.",
            "schema": {
              "type": "integer"
            },
            "example": 1024
          }
        ]
      }
    },
    "/v4/user/agent-approvals/settings": {
      "get": {
        "tags": [
          "Agent approvals"
        ],
        "summary": "Your auto-approve limit",
        "description": "The price at or under which an AI agent's purchase is approved without asking you.",
        "operationId": "getV4UserAgentApprovalsSettings",
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The setting.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "auto_approve_limit": {
                      "type": "string",
                      "description": "A purchase priced at or under this is approved as the agent asks; anything more asks the user. `0.000` asks every time. Top-ups of the agent wallet always ask."
                    }
                  },
                  "required": [
                    "auto_approve_limit"
                  ]
                },
                "example": {
                  "auto_approve_limit": "1.000"
                }
              }
            }
          },
          "401": {
            "description": "No first-party credential: the user's client credentials (Basic) or a token from `POST /v4/user/oauth/token`, never an OIDC access token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Agent approvals"
        ],
        "summary": "Set your auto-approve limit",
        "description": "Set how much an AI agent may spend on one purchase without asking you. Only you can change it, with your own credentials: an agent's OIDC token is refused. `0` turns auto-approval off.",
        "operationId": "putV4UserAgentApprovalsSettings",
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "auto_approve_limit": {
                    "type": "number",
                    "minimum": 0
                  }
                },
                "required": [
                  "auto_approve_limit"
                ]
              },
              "example": {
                "auto_approve_limit": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "auto_approve_limit": {
                      "type": "string",
                      "description": "A purchase priced at or under this is approved as the agent asks; anything more asks the user. `0.000` asks every time. Top-ups of the agent wallet always ask."
                    }
                  },
                  "required": [
                    "auto_approve_limit"
                  ]
                },
                "example": {
                  "auto_approve_limit": "1.000"
                }
              }
            }
          },
          "400": {
            "description": "Not a number from 0 to 1,000,000.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No first-party credential: the user's client credentials (Basic) or a token from `POST /v4/user/oauth/token`, never an OIDC access token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/user/oauth/token": {
      "post": {
        "tags": [
          "Accounts"
        ],
        "summary": "Get or refresh an access token",
        "description": "Issues the tokens every other authenticated call depends on. There are two ways in, chosen by the `Authorization` header:\n\n**Client credentials** — send `Basic <base64(client_id:client_secret)>`. An access token with at least five minutes left is returned as-is rather than being replaced, so calling this repeatedly is cheap. New access tokens last an hour, and are refused once `expires_at` has passed.\n\n**Refresh** — send `Bearer <refresh_token>`. A fresh access token (one hour) and refresh token (24 hours) are issued and the refresh token you presented is destroyed, so each one works exactly once. Store the new pair immediately.\n\nThe endpoint is mounted at both `/v4/account/oauth/token` and `/v4/user/oauth/token`; which principal you get depends on the credentials you present, not on the path.",
        "operationId": "postV4UserOauthToken",
        "security": [
          {
            "basicAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The token pair.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "access_token": {
                      "type": "string",
                      "example": "tqmt_9f2c…"
                    },
                    "expires_at": {
                      "type": "integer",
                      "description": "Unix time the access token expires."
                    },
                    "refresh_token": {
                      "type": "string"
                    },
                    "refresh_expires_at": {
                      "type": "integer",
                      "description": "Unix time the refresh token expires."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization` header, unrecognised or revoked client credentials, or an invalid, expired or revoked refresh token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The `Authorization` header is neither Basic nor Bearer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v4/user/passkeys": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "summary": "List your passkeys",
        "description": "The passkeys the user can sign in with instead of an emailed code, newest first. Only the user's own first-party credentials can list them: an OIDC access token held by a relying party cannot.",
        "operationId": "getV4UserPasskeys",
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The user's passkeys.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "passkeys": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "rp_id": {
                            "type": "string",
                            "description": "The domain the passkey works on."
                          },
                          "aaguid": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Identifies the kind of authenticator (a password manager, a security key model). All zeros when it does not say."
                          },
                          "device_type": {
                            "type": "string",
                            "enum": [
                              "singleDevice",
                              "multiDevice"
                            ],
                            "description": "`multiDevice` passkeys sync between the user's devices; `singleDevice` ones live on one authenticator."
                          },
                          "backed_up": {
                            "type": "boolean",
                            "description": "Whether the passkey is currently synced or backed up."
                          },
                          "transports": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "last_used_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          }
                        },
                        "required": [
                          "id",
                          "rp_id",
                          "device_type",
                          "backed_up",
                          "created_at"
                        ]
                      }
                    }
                  },
                  "required": [
                    "passkeys"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No first-party credential. These routes take the user's client credentials (Basic) or a token from `POST /v4/user/oauth/token`, never an OIDC access token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Authentication required",
                  "status": 401
                }
              }
            }
          }
        }
      }
    },
    "/v4/user/passkeys/{id}": {
      "delete": {
        "tags": [
          "Accounts"
        ],
        "summary": "Remove a passkey",
        "description": "Removes the passkey for good: it cannot sign in again. Use it for a lost or replaced device. The authenticator keeps its copy until the user deletes it there; signing in with it is refused.",
        "operationId": "deleteV4UserPasskeysById",
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "204": {
            "description": "Removed."
          },
          "401": {
            "description": "No first-party credential. These routes take the user's client credentials (Basic) or a token from `POST /v4/user/oauth/token`, never an OIDC access token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Authentication required",
                  "status": 401
                }
              }
            }
          },
          "404": {
            "description": "The user has no passkey with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Identifies the passkey.",
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v4/user/{email}": {
      "get": {
        "tags": [
          "Users"
        ],
        "summary": "Check whether a user exists",
        "description": "Returns the user’s id and which mobile platforms they have registered. Use it before starting a sign-in flow to tell a returning user from a new one.",
        "operationId": "getV4UserByEmail",
        "security": [],
        "responses": {
          "200": {
            "description": "The user exists.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "is_ios": {
                      "type": "boolean"
                    },
                    "is_android": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No user with that address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "email",
            "in": "path",
            "required": true,
            "description": "The user’s email address, URL-encoded.",
            "schema": {
              "type": "string"
            },
            "example": "ada%40example.com"
          }
        ]
      },
      "post": {
        "tags": [
          "Users"
        ],
        "summary": "Record a user’s device platforms",
        "description": "Sets whether the user has an iOS or Android device registered, which governs whether push-based second factors can reach them. Both flags are set as given (an absent one is `false`). Only for your own address, with your own first-party token; an OIDC token may not.",
        "operationId": "postV4UserByEmail",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "is_ios": {
                    "type": "boolean",
                    "default": false
                  },
                  "is_android": {
                    "type": "boolean",
                    "default": false
                  }
                }
              },
              "example": {
                "is_ios": true,
                "is_android": false
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "The platforms were recorded."
          },
          "400": {
            "description": "The address is not validly encoded, or a flag is not a boolean.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not your own address, or not your own first-party token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The update failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "email",
            "in": "path",
            "required": true,
            "description": "The user’s email address, URL-encoded.",
            "schema": {
              "type": "string"
            },
            "example": "ada%40example.com"
          }
        ]
      }
    },
    "/v4/verify/button/{hash}": {
      "get": {
        "tags": [
          "Embeds"
        ],
        "summary": "Render a verification button",
        "description": "Returns the HTML for the verification button on its own, without the surrounding embed chrome.",
        "operationId": "getV4VerifyButtonByHash",
        "security": [],
        "responses": {
          "200": {
            "description": "The button document.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "`style` is not a JSON object of CSS properties."
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Hash of the persona being verified.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "style",
            "in": "query",
            "description": "JSON-encoded CSS properties applied to the button.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/verify/embed/{hash}": {
      "get": {
        "tags": [
          "Embeds"
        ],
        "summary": "Render a verification embed",
        "description": "Returns the HTML for the persona verification iframe, which asks the user to consent to sharing proof of their persona with the host site.",
        "operationId": "getV4VerifyEmbedByHash",
        "security": [],
        "responses": {
          "200": {
            "description": "The embed document.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "`theme` is not `light` or `dark`, or `style` is not a JSON object of CSS properties."
          }
        },
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "Hash of the persona being verified.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "theme",
            "in": "query",
            "description": "Colour theme to render with.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "style",
            "in": "query",
            "description": "JSON-encoded CSS properties applied to the embed.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "consent",
            "in": "query",
            "description": "Consent text shown to the user.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "issueMin",
            "in": "query",
            "description": "Lowest delegate issue number accepted.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "issueMax",
            "in": "query",
            "description": "Highest delegate issue number accepted.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "leading",
            "in": "query",
            "description": "Text shown before the label.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "middle",
            "in": "query",
            "description": "Text shown in the middle of the label.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "trailing",
            "in": "query",
            "description": "Text shown after the label.",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v4/wallets": {
      "get": {
        "tags": [
          "Twins"
        ],
        "summary": "List your wallets and balances",
        "description": "The authenticated user’s primary and agent twins with their live balances. Accepts the same credentials as `POST /v4/paywall`, including agent (OIDC) access tokens bound to a managed MCP server. Read-only: no keys are returned and nothing here grants spend. A twin node that cannot be reached contributes an empty `balances` list.",
        "operationId": "getV4Wallets",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "sessionId": []
          }
        ],
        "responses": {
          "200": {
            "description": "The caller’s wallets, primary first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "wallets": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "role": {
                            "type": "string",
                            "enum": [
                              "primary",
                              "agent"
                            ]
                          },
                          "hostname": {
                            "type": "string"
                          },
                          "label": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "is_primary": {
                            "type": "boolean",
                            "description": "The user's primary wallet. Some users hold several `primary`-role wallets from older sign-ups; this one is listed first and is where payments go."
                          },
                          "balances": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "currency": {
                                  "type": "string",
                                  "description": "`USD-TDN` for a quantity this platform is configured with; otherwise the DQ hash itself."
                                },
                                "dq_type": {
                                  "$ref": "#/components/schemas/DigitalQuantityHash"
                                },
                                "amount": {
                                  "type": "string",
                                  "description": "Three decimal places, e.g. `\"8.866\"`."
                                }
                              },
                              "required": [
                                "currency",
                                "dq_type",
                                "amount"
                              ]
                            }
                          }
                        },
                        "required": [
                          "role",
                          "hostname",
                          "label",
                          "is_primary",
                          "balances"
                        ]
                      }
                    }
                  },
                  "required": [
                    "wallets"
                  ]
                },
                "example": {
                  "wallets": [
                    {
                      "role": "primary",
                      "hostname": "00000000000000000000000000000000.micro.biz.todaq.net",
                      "label": "Main wallet",
                      "is_primary": true,
                      "balances": [
                        {
                          "currency": "USD-TDN",
                          "dq_type": "41…2e",
                          "amount": "8.866"
                        }
                      ]
                    },
                    {
                      "role": "agent",
                      "hostname": "9c2d1f0a7be34c55a1d0e8b7f6a54321.micro.biz.todaq.net",
                      "label": null,
                      "is_primary": false,
                      "balances": [
                        {
                          "currency": "USD-TDN",
                          "dq_type": "41…2e",
                          "amount": "1.250"
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No usable authentication.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token acts for an account (a managed agent), which holds no personal wallets.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  }
}