{
  "openapi": "3.1.0",
  "info": {
    "title": "X-TKN Public API",
    "description": "Public API for X-TKN — a token-as-a-service platform. Every endpoint is authenticated with a per-customer API-key token passed in the Authorization header. A monthly quota derived from the customer's tier is enforced per-customer by the application, and reported on every response via the `x-rate-limit`, `x-rate-usage` and `x-rate-ceiling` headers. Anti-abuse rate limiting is enforced at the edge by WAF, per source IP and per API key; a WAF block carries none of these headers.\nThe headers are on **every** metered response, success included. Only the shared responses below restate them; the per-operation 2xx schemas contributed from the route files do not repeat them.\n**Passing the monthly quota does not stop your requests.** The overage policy is warn-then-grace: past `x-rate-limit` calls keep being served up to `x-rate-ceiling`, and only past the ceiling do they return 429. You are emailed once on approach, once on crossing the quota, and once when requests stop.\n**Keys may be pinned to source IP ranges** on Team and above. A key with an allowlist is refused with `403` and `details.code: \"IP_NOT_ALLOWED\"` when the request arrives from anywhere else — the credential is valid, so this is never reported as `401`. The address compared is the one the connection was made from, as recorded by the API edge; `x-forwarded-for` is not consulted and cannot be used to influence the decision. A key with no allowlist is unaffected, which is every key by default.\nPer-handler operations are contributed from JSDoc `@openapi` blocks in the route files under `src/app/api/xtkn/**`. This file defines the shared components (security schemes, schemas, responses) those operations reference.\n",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://api.x-tkn.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "xtkn",
      "description": "X-TKN public API — token lifecycle (create, read, update, revoke, redeem) and app dispatch."
    }
  ],
  "components": {
    "securitySchemes": {
      "xtknApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "The customer's API key. Sent as the raw value of the `Authorization` header (no `Bearer` prefix) — a `Bearer ` prefix is not stripped and authenticates as nothing.\n\n**The key is not a token.** Credentials live in their own collection, separate from the tokens this API manages, which is why no token `type` is reserved and why a token called `api-key` authenticates nothing. Authenticating is a lookup against `sha256(key)`, not a redemption: nothing on the key is written, and in particular its `lastUsedAt` does not move — a write per authenticated request is contention this path will not pay for. The one counter a request does advance is the customer's monthly quota.\n"
      }
    },
    "schemas": {
      "SuccessEnvelope": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "data": {}
        },
        "description": "Standard success envelope produced by `successResponse()`."
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message"
          },
          "details": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Optional field-level error map."
          }
        }
      },
      "WafBlock": {
        "type": "object",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string",
            "const": "Too Many Requests"
          },
          "message": {
            "type": "string",
            "description": "Human-readable retry hint"
          }
        },
        "description": "Body returned by the WAF edge rules, which block before the request reaches the application. The shape differs from `ErrorEnvelope`: `error` carries a fixed status label and the human-readable text sits in `message`.\n"
      },
      "XtknToken": {
        "type": "object",
        "description": "A token owned by a customer. This is the `data` of every single-token response — create, read, redeem, revoke and update all return this shape.\n",
        "required": [
          "id",
          "type",
          "uses",
          "isRevoked",
          "isActive",
          "isExpired",
          "isUsed",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Token id — a UUID string, not a number. Stable for the life of the row, and safe to put in URLs or DOM attributes, which `code` is not.\n"
          },
          "code": {
            "type": "string",
            "description": "The token code in clear, 64 hex characters. **Returned only by `POST /tokens/create`, and only in that one response.** The server stores `sha256(code)` and never the code itself, so no read, redeem, revoke or update can hand it back — the field is simply absent from every other response. Store it when you create the token, or it is unrecoverable.\n"
          },
          "displayName": {
            "type": "string",
            "maxLength": 24,
            "description": "First 5 and last 3 characters of the code. Not a credential — this is what to show in a UI, since `code` is unavailable after create.\n"
          },
          "type": {
            "type": "string",
            "description": "Caller-defined token type, lowercased on storage, e.g. \"handoff\". Defaults to \"generic\". No name is reserved — credentials live in their own collection, so a type cannot select a code shape or authenticate whatever it is called."
          },
          "refId": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 256,
            "description": "Reference id linking the token to an external entity. Null when the token carries none; never an empty string, which the write paths reject."
          },
          "payload": {
            "oneOf": [
              {
                "type": "object"
              },
              {
                "type": "array"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Caller payload. Stored as text exactly as sent, but **parsed back on read when it is valid JSON** — send the string `{\"a\":1}` and reads return the object, not the string. Anything that does not parse as JSON (an `xtkn.v1` envelope, for instance) comes back verbatim as a string.\n"
          },
          "uses": {
            "type": "integer",
            "minimum": 0
          },
          "maxUses": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "description": "null = unlimited."
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Explicit expiry. When null, the token still ages out 30 days after `createdAt` — `isExpired` accounts for that fallback."
          },
          "lastUsedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "isRevoked": {
            "type": "boolean"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 4000
          },
          "customer": {
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "object"
              }
            ],
            "description": "Owning customer, always the authenticated caller. Arrives either as the bare numeric id or as the populated customer object depending on how deep the underlying query resolved relationships. Read `customerId` instead, which is normalized on every response.\n"
          },
          "customerId": {
            "type": "string",
            "description": "Owning customer id, normalized to a string. Present whenever `customer` is."
          },
          "isActive": {
            "type": "boolean",
            "description": "Computed per response: not revoked, not exhausted, not expired. Not a stored column."
          },
          "isExpired": {
            "type": "boolean",
            "description": "Computed per response: past `expiresAt`, or — when none was set — past 30 days from `createdAt`."
          },
          "isUsed": {
            "type": "boolean",
            "description": "Computed per response: `uses` has reached `maxUses`. Always false when `maxUses` is null."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "XtknTokenEnvelope": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/XtknToken"
              }
            }
          }
        ]
      },
      "XtknCreatedTokenEnvelope": {
        "description": "The create response. Identical to `XtknTokenEnvelope` except that `code` is required, because create is the only operation that returns one. `XtknToken` cannot require it — every other response omits it — so a generated client would otherwise type the code optional on the one response that always carries it, and force a null check on a value that is unrecoverable if not captured here.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/XtknToken",
                "required": [
                  "code"
                ]
              }
            }
          }
        ]
      },
      "XtknBulkRevokeEnvelope": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "required": [
                  "revoked",
                  "hasMore"
                ],
                "properties": {
                  "revoked": {
                    "type": "integer",
                    "description": "Number of tokens revoked by this call. At most 1000."
                  },
                  "hasMore": {
                    "type": "boolean",
                    "description": "True when the filter matched more active tokens than this call revoked. Re-send the identical request until it comes back false — the filter skips already-revoked tokens, so each call drains the next batch. Ignoring this leaves matching tokens live.\n"
                  }
                }
              }
            }
          }
        ]
      },
      "XtknTokenListEnvelope": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "required": [
                  "tokens",
                  "count"
                ],
                "properties": {
                  "tokens": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/XtknToken"
                    },
                    "description": "This page of results — at most `limit` items."
                  },
                  "count": {
                    "type": "integer",
                    "description": "Total tokens matching the filter across every page, not the length of `tokens`. There is no `totalPages` or `hasNextPage` on this endpoint: page with `page`/`limit` and keep going while `page * limit < count`.\n"
                  }
                }
              }
            }
          }
        ]
      }
    },
    "headers": {
      "XRateLimit": {
        "description": "Operations included this calendar month on the customer's tier — the entitlement, not the cut-off. Requests past this are still served, up to `x-rate-ceiling`. Absent on unlimited tiers, and on responses that never reached the quota check (an unauthenticated 401).\n",
        "schema": {
          "type": "integer",
          "minimum": 0
        },
        "example": 10000
      },
      "XRateCeiling": {
        "description": "Where requests stop being served. Once `x-rate-usage` passes this, every call returns 429 until the counter resets. Absent on unlimited tiers.\n",
        "schema": {
          "type": "integer",
          "minimum": 0
        },
        "example": 20000
      },
      "XRateUsage": {
        "description": "Operations consumed this calendar month, **including the request that produced this response**. The counter is incremented before the limit is checked, so a 429 always reports a value greater than `x-rate-ceiling`. A value between `x-rate-limit` and `x-rate-ceiling` means you are over quota and still being served. It also continues to climb while blocked — a rejected call still counts. Resets to zero on the 1st of the month.\n",
        "schema": {
          "type": "integer",
          "minimum": 0
        },
        "example": 8342
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid or missing fields.",
        "headers": {
          "x-rate-limit": {
            "$ref": "#/components/headers/XRateLimit"
          },
          "x-rate-usage": {
            "$ref": "#/components/headers/XRateUsage"
          },
          "x-rate-ceiling": {
            "$ref": "#/components/headers/XRateCeiling"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "The account's plan does not include the capability this endpoint needs. The request was understood and the credential is valid — upgrading is the only thing that changes the answer, which is what separates this from `403`.\n\n`details` carries what an agent needs to hand a human:\n\n- **`code`** — always `UPGRADE_REQUIRED`.\n- **`capability`** — the entitlement that was missing, e.g. `recipientAuth`.\n- **`requiredPlan`** — the cheapest plan that grants it.\n- **`upgradeUrl`** — the link to open. **Absent** when one could not be built (Stripe unreachable or slow, or no price configured for the plan), and always absent when `upgradeMethod` is `claim`; the refusal is still correct, it simply carries no link.\n\n- **`upgradeMethod`** — `checkout`, `portal` or `claim`, saying how this account gets unblocked. An account with no subscription buys one through Stripe Checkout. An account that already has one changes plan in the Billing Portal instead, because a second subscription would bill twice — so a Team subscriber refused an Enterprise-only capability gets a portal link here. An unclaimed sandbox account gets neither: it is anonymous and reaped within the day, so it cannot hold a subscription at all. **There is a fourth answer: absent.** `checkout` and `portal` are each written beside the `upgradeUrl` they describe, so a refusal that could not build a link — Stripe slow, no price configured, a portal session unavailable — names no method either. `claim` is the one method that appears without a URL, because the action there is an API call rather than a link. So branch on this field's presence first, and only then on which of the three it holds.\n\n- **`claimEndpoint`** — `claim` only. Where to turn the sandbox into a real account. Unlike the other two methods this is a call your own code makes, not a link for a human, which is why there is no `upgradeUrl` beside it. Claiming grants an ordinary free account, so `requiredPlan` is still needed afterwards — it is the first of two steps, not the fix.\n\n- **`claimReason`** — `claim` only. A sentence explaining that, worth showing a human verbatim.\n\n- **`upgradeToken`** — the same link wrapped as a single-use X-TKN secret, safe to paste into a chat log. `checkout` only, and absent if the mint failed.\n\n- **`upgradeExpiresAt`** — when both stop working. `checkout` only; a portal session carries its own lifetime.\n",
        "headers": {
          "x-rate-limit": {
            "$ref": "#/components/headers/XRateLimit"
          },
          "x-rate-usage": {
            "$ref": "#/components/headers/XRateUsage"
          },
          "x-rate-ceiling": {
            "$ref": "#/components/headers/XRateCeiling"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The request is understood and the credential is valid; the server is refusing it anyway. Three conditions produce this status:\n\n**`IP_NOT_ALLOWED`** in `details.code` — the key carries an IP allowlist and this request did not come from it. Add the range to the key, or use a key without an allowlist. Retrying from the same network will not succeed.\n\n**No `details.code`, a full allowance** — a countable allowance is full: API keys, or member seats. Revoking one frees a slot, and upgrading raises the cap, so either fixes it.\n\n**No `details.code`, a role refusal** — on Enterprise, the key was issued from a `member` seat (or from a seat since revoked) and the operation is one only an owner or an admin may perform: reading or streaming the audit trail, or adding, changing or removing a webhook endpoint. The message names who can. Use a key issued from an owner or admin seat; retrying with this one will not succeed. A key minted before seats existed has no seat behind it and is not refused this way, and below Enterprise nothing is.\n\nA plan that does not include a feature *at all* is `402`, not this. That split is deliberate: `402` means paying is the only way forward, and this status means it is not.\n",
        "headers": {
          "x-rate-limit": {
            "$ref": "#/components/headers/XRateLimit"
          },
          "x-rate-usage": {
            "$ref": "#/components/headers/XRateUsage"
          },
          "x-rate-ceiling": {
            "$ref": "#/components/headers/XRateCeiling"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "NotFound": {
        "description": "Token not found, or not owned by the authenticated customer.",
        "headers": {
          "x-rate-limit": {
            "$ref": "#/components/headers/XRateLimit"
          },
          "x-rate-usage": {
            "$ref": "#/components/headers/XRateUsage"
          },
          "x-rate-ceiling": {
            "$ref": "#/components/headers/XRateCeiling"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Gone": {
        "description": "Token exists but is no longer usable — revoked, expired, or exhausted.",
        "headers": {
          "x-rate-limit": {
            "$ref": "#/components/headers/XRateLimit"
          },
          "x-rate-usage": {
            "$ref": "#/components/headers/XRateUsage"
          },
          "x-rate-ceiling": {
            "$ref": "#/components/headers/XRateCeiling"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Two distinct conditions share this status, and they carry different bodies.\n**Monthly quota ceiling reached** — `x-rate-usage` has passed `x-rate-ceiling`. Merely exceeding `x-rate-limit` does **not** produce this; those calls are served, because the overage policy is warn-then-grace. Returned by the application as an `ErrorEnvelope` with `error: \"Monthly quota has been exceeded\"`. Retrying will not succeed until the counter resets on the 1st, or the tier is upgraded.\n**Anti-abuse rate limit** — WAF blocked the request at the edge, per source IP or per API key, before it reached the application. Returned as a `WafBlock`. These limits sit above every tier's entitlement, so this signals abnormal traffic; backing off and retrying is the right response.\nThe quota headers below are present on the first condition only. A WAF block never reaches the application, so it carries none of them — their absence is how the two are told apart programmatically.\n",
        "headers": {
          "x-rate-limit": {
            "$ref": "#/components/headers/XRateLimit"
          },
          "x-rate-usage": {
            "$ref": "#/components/headers/XRateUsage"
          },
          "x-rate-ceiling": {
            "$ref": "#/components/headers/XRateCeiling"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                {
                  "$ref": "#/components/schemas/WafBlock"
                }
              ]
            }
          }
        }
      },
      "WafRateLimited": {
        "description": "**Anti-abuse rate limit** — the WAF blocked the request at the edge, per source IP or per API key over a five-minute window, before it reached the application. Returned as a `WafBlock`. Backing off and retrying is the right response.\n\nOnly this condition. Unlike `RateLimited`, the monthly-quota `429` is not reachable here: the operation either carries no API key at all or waives the charge, so no quota is evaluated and none of the `x-rate-*` headers is sent. A client handling this status on these operations never has to tell the two apart.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/WafBlock"
            }
          }
        }
      },
      "ServerError": {
        "description": "Unexpected server error.",
        "headers": {
          "x-rate-limit": {
            "$ref": "#/components/headers/XRateLimit"
          },
          "x-rate-usage": {
            "$ref": "#/components/headers/XRateUsage"
          },
          "x-rate-ceiling": {
            "$ref": "#/components/headers/XRateCeiling"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "UnmeteredServerError": {
        "description": "Unexpected server error.\n\nCarries no `x-rate-*` headers, unlike `ServerError`. These operations evaluate no quota — keyless, or charge-waived — so there is no position to report, and the wrapper that would attach the headers is a no-op when nothing recorded one.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "The request body is past the ceiling, refused before anything parsed it.\n\nThis is not the tier's payload cap and must not be read as one. It is the point past which a body could not be a request *any* plan would accept: the largest tier's stored payload cap at the worst case `JSON.stringify` expansion, plus room for the other fields — 2,163,256 bytes today, and named in the message so a client can tell a body it should split from one it should shrink. A body under this still meets the plan's own `maxPayloadBytes` a moment later and is still refused there, as a `400`.\n\nThe recipient retrieval endpoints (`/secrets/*`) do not apply it; they run outside the shared wrapper that does.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "xtknApiKey": []
    }
  ],
  "paths": {
    "/account": {
      "get": {
        "operationId": "xtknGetAccount",
        "summary": "Read this key's account, plan and quota position",
        "description": "Everything the authenticated key is entitled to: the plan, every cap and capability on it, where the month's usage stands, and the trial and subscription state.\n\nCall this to find out what you may do *before* doing it. The alternative is discovering each fence by tripping it, which costs a failed request and — for anything with a side effect — leaves you guessing what did and did not happen.\n\n`tier` is the **effective** plan, which is not always the stored one. A trial that lapsed minutes ago still reads as Team on the account row until the nightly sweep runs; this reports Developer, because that is what the fences will enforce.\n\n`usage.remaining` counts against `usage.ceiling`, not `usage.monthlyLimit`. Crossing the limit does not stop you — requests are served into a grace band and only refused past the ceiling — so the ceiling is the number to budget against.\n\nReaches nothing external, so it stays fast and cannot fail because Stripe is having a bad day. It carries no prices for the same reason: to buy something, call `/billing/upgrade-intent`.\n",
        "tags": [
          "xtkn"
        ],
        "responses": {
          "200": {
            "description": "The account as it stands.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "required": [
                            "tier",
                            "limits",
                            "usage",
                            "trial",
                            "subscription",
                            "sandbox"
                          ],
                          "properties": {
                            "tier": {
                              "type": "string",
                              "enum": [
                                "developer",
                                "team",
                                "enterprise"
                              ],
                              "description": "The effective plan, not necessarily the stored one."
                            },
                            "limits": {
                              "type": "object",
                              "description": "Every cap and capability of `tier`. Numeric caps use `null` for unlimited; capabilities are booleans.\n\n`monthlyLimit` is the ceiling actually enforced, which is not always the tier's own — a sandbox account is Developer-classed but capped far lower. It always matches `usage.monthlyLimit`.\n",
                              "properties": {
                                "monthlyLimit": {
                                  "type": "integer",
                                  "nullable": true
                                },
                                "maxPayloadBytes": {
                                  "type": "integer"
                                },
                                "maxExpiryDays": {
                                  "type": "integer",
                                  "nullable": true
                                },
                                "apiKeys": {
                                  "type": "integer",
                                  "nullable": true
                                },
                                "seats": {
                                  "type": "integer",
                                  "nullable": true
                                },
                                "rbac": {
                                  "type": "boolean",
                                  "description": "Whether seat roles are enforced. When true, what a key may do depends on the role of the seat that issued it — a `member` seat is refused `403` on seat management, on the audit trail, and on adding, changing or removing a webhook endpoint."
                                },
                                "customDomains": {
                                  "type": "integer",
                                  "nullable": true
                                },
                                "auditRetentionDays": {
                                  "type": "integer",
                                  "nullable": true,
                                  "description": "How long this account's audit trail is kept, and the window `GET /audit/events` will serve. Usually the plan's own, but an Enterprise account with a negotiated term reads that term here instead — it is only ever longer than the plan's."
                                },
                                "branding": {
                                  "type": "boolean"
                                },
                                "recipientAuth": {
                                  "type": "boolean"
                                },
                                "webhooks": {
                                  "type": "boolean"
                                },
                                "auditExport": {
                                  "type": "boolean"
                                },
                                "auditStreaming": {
                                  "type": "boolean"
                                },
                                "ipAllowlist": {
                                  "type": "boolean"
                                }
                              }
                            },
                            "usage": {
                              "type": "object",
                              "properties": {
                                "monthlyOps": {
                                  "type": "integer",
                                  "description": "Operations this month",
                                  "including any refused.": null
                                },
                                "monthlyLimit": {
                                  "type": "integer",
                                  "nullable": true,
                                  "description": "The entitlement. null = unlimited."
                                },
                                "ceiling": {
                                  "type": "integer",
                                  "nullable": true,
                                  "description": "Where requests actually stop."
                                },
                                "remaining": {
                                  "type": "integer",
                                  "nullable": true,
                                  "description": "Operations left before the ceiling."
                                },
                                "monthlyWindow": {
                                  "type": "string",
                                  "nullable": true,
                                  "format": "date-time"
                                },
                                "level": {
                                  "type": "string",
                                  "enum": [
                                    "normal",
                                    "approaching",
                                    "exceeded",
                                    "stopped"
                                  ]
                                }
                              }
                            },
                            "trial": {
                              "type": "object",
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "none",
                                    "active",
                                    "converted",
                                    "expired",
                                    "cancelled"
                                  ]
                                },
                                "endsAt": {
                                  "type": "string",
                                  "nullable": true,
                                  "format": "date-time"
                                },
                                "daysRemaining": {
                                  "type": "integer"
                                }
                              }
                            },
                            "subscription": {
                              "type": "object",
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "description": "Stripe's status verbatim, or 'none'."
                                },
                                "active": {
                                  "type": "boolean",
                                  "description": "Whether that status currently entitles the tier."
                                },
                                "cancelAtPeriodEnd": {
                                  "type": "boolean"
                                },
                                "currentPeriodEnd": {
                                  "type": "string",
                                  "nullable": true,
                                  "format": "date-time"
                                }
                              }
                            },
                            "sandbox": {
                              "type": "object",
                              "description": "Sandbox state. A sandbox is a Developer account wearing a timer rather than a plan of its own, so `tier` cannot tell you the account is temporary or that it may not mint secrets.\n",
                              "properties": {
                                "active": {
                                  "type": "boolean",
                                  "description": "True while this is an unclaimed sandbox."
                                },
                                "expiresAt": {
                                  "type": "string",
                                  "nullable": true,
                                  "format": "date-time",
                                  "description": "When the account is reaped if unclaimed. Null once claimed."
                                },
                                "expired": {
                                  "type": "boolean",
                                  "description": "True once the window has closed."
                                },
                                "forbiddenTokenTypes": {
                                  "type": "array",
                                  "items": {
                                    "type": "string"
                                  },
                                  "description": "Token types this account may not create. Empty unless sandboxed."
                                },
                                "claimEndpoint": {
                                  "type": "string",
                                  "nullable": true,
                                  "description": "Where to turn this sandbox into a real account. Null unless sandboxed. The same route a capability fence names in `claimEndpoint`, reported here so a caller can find it without tripping a fence first."
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/accounts/claim": {
      "post": {
        "operationId": "xtknClaimSandboxAccount",
        "summary": "Keep a sandbox account permanently",
        "description": "Turns the sandbox account the API key belongs to into an ordinary Developer account: 10,000 operations a month, no expiry, and `type: secret` unlocked. Existing tokens and keys are kept.\nNeeds both credentials. The API key in `Authorization` says which account is being claimed — it is never taken from the body — and the Firebase ID token in `X-Firebase-Token` says who is claiming it.\nRefuses with 409 when that email already owns an account. Merging two accounts is not supported; sign in with the existing one instead.\n",
        "tags": [
          "xtkn"
        ],
        "parameters": [
          {
            "in": "header",
            "name": "X-Firebase-Token",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "A Firebase ID token for the identity taking the account over."
          }
        ],
        "responses": {
          "200": {
            "description": "The account is now an ordinary Developer account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "customerId": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string",
                      "format": "email"
                    },
                    "class": {
                      "type": "string",
                      "example": "developer"
                    },
                    "monthlyOperations": {
                      "type": "integer",
                      "example": 10000
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The Firebase token is missing, unverifiable, or carries no email.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Not an unclaimed sandbox account, or that email already owns one.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/WafRateLimited"
          },
          "500": {
            "$ref": "#/components/responses/UnmeteredServerError"
          }
        }
      }
    },
    "/accounts/sandbox": {
      "post": {
        "operationId": "xtknCreateSandboxAccount",
        "summary": "Create a sandbox account and get a working API key",
        "description": "Unauthenticated. Returns a live API key with no signup, no inbox and no reCAPTCHA, so a coding agent can go from `npm install` to a working call in one step.\nThe account is capped: 100 operations, 24 hours, and no `type: secret` tokens. Call `POST /accounts/claim` with a Firebase ID token to keep it — that turns it into an ordinary Developer account with 10,000 operations a month and no expiry.\nThe key is returned once and cannot be read back.\n",
        "tags": [
          "xtkn"
        ],
        "security": [],
        "responses": {
          "201": {
            "description": "A new sandbox account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiKey": {
                      "type": "string",
                      "description": "Plaintext key. Shown once."
                    },
                    "customerId": {
                      "type": "string"
                    },
                    "expiresAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "limits": {
                      "type": "object",
                      "properties": {
                        "monthlyOperations": {
                          "type": "integer",
                          "example": 100
                        },
                        "ttlHours": {
                          "type": "integer",
                          "example": 24
                        },
                        "forbiddenTokenTypes": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "example": [
                            "secret"
                          ]
                        }
                      }
                    },
                    "claim": {
                      "type": "object",
                      "properties": {
                        "endpoint": {
                          "type": "string"
                        },
                        "description": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/WafRateLimited"
          },
          "500": {
            "$ref": "#/components/responses/UnmeteredServerError"
          }
        }
      }
    },
    "/audit/destinations/{id}": {
      "delete": {
        "operationId": "xtknDeleteAuditDestination",
        "summary": "Remove an audit streaming destination",
        "description": "Stops delivery immediately. The events themselves are untouched — they stay readable through `/audit/events/list` for as long as the plan's retention window allows.\n\nAnswers 200 with `{ \"success\": true, \"data\": null }`, not 204, matching every other X-TKN endpoint.\n",
        "tags": [
          "xtkn"
        ],
        "security": [
          {
            "xtknApiKey": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No such destination on this account."
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/audit/destinations/create": {
      "post": {
        "operationId": "xtknCreateAuditDestination",
        "summary": "Register an audit streaming destination",
        "description": "Where your audit trail is delivered continuously. Enterprise plan only.\n\n**`type: webhook`** — a signed HTTPS POST per event, in real time, using the same signature scheme and retry ladder as retrieval webhooks. The `signingSecret` comes back **once, on this response, and is never readable again**. Store it before you close the connection.\n\n**`type: s3`** — batched NDJSON objects written to a bucket you own, at `<prefix>/<date>/<id>.ndjson`. You create a role in your own account that grants `s3:PutObject` on the bucket and trusts ours; the `externalId` on the response must be quoted in that role's trust policy, which is what stops anyone else naming your role. Batched, not per-event: an object per event is millions of tiny keys and no tool reads it. Use a webhook if you need sub-minute delivery.\n",
        "tags": [
          "xtkn"
        ],
        "security": [
          {
            "xtknApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "webhook",
                      "s3"
                    ],
                    "default": "webhook"
                  },
                  "url": {
                    "type": "string",
                    "description": "HTTPS endpoint. Required when type is webhook."
                  },
                  "bucket": {
                    "type": "string",
                    "description": "Required when type is s3."
                  },
                  "region": {
                    "type": "string",
                    "description": "Bucket region. Required when type is s3."
                  },
                  "prefix": {
                    "type": "string",
                    "description": "Key prefix. Optional."
                  },
                  "roleArn": {
                    "type": "string",
                    "description": "Role in your account granting s3:PutObject. Required when type is s3."
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "token.created",
                        "token.read",
                        "token.revoked",
                        "token.expired",
                        "token.failed"
                      ]
                    },
                    "description": "Defaults to every event."
                  },
                  "description": {
                    "type": "string"
                  }
                }
              },
              "examples": {
                "webhook": {
                  "summary": "Real-time delivery to a SIEM",
                  "value": {
                    "type": "webhook",
                    "url": "https://siem.acme.com/hooks/xtkn",
                    "description": "Splunk HEC"
                  }
                },
                "s3": {
                  "summary": "Batched delivery to a customer bucket",
                  "value": {
                    "type": "s3",
                    "bucket": "acme-security-audit",
                    "region": "us-east-1",
                    "prefix": "xtkn",
                    "roleArn": "arn:aws:iam::123456789012:role/XtknAuditWriter"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The destination. `signingSecret` is present only here and only for a webhook; `externalId` only for s3.\n"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "description": "The key carries an IP allowlist and the request did not come from it (`details.code` is `IP_NOT_ALLOWED`). A plan that does not include audit streaming is `402`, not this.\n"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/audit/destinations/list": {
      "post": {
        "operationId": "xtknListAuditDestinations",
        "summary": "List audit streaming destinations",
        "description": "Every destination registered on the account. `signingSecret` is never returned here — it exists only on the create response. `lastStreamedAt` is the S3 batch watermark, and `lastError` is why the most recent attempt failed, which is the surface for \"the stream stopped\".\n",
        "tags": [
          "xtkn"
        ],
        "security": [
          {
            "xtknApiKey": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "page": {
                    "type": "integer",
                    "minimum": 1,
                    "default": 1
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "default": 100
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Destinations as `data.destinations`, with `data.count`."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/audit/events/export": {
      "post": {
        "operationId": "xtknExportAuditEvents",
        "summary": "Export the audit trail as CSV",
        "description": "The same events `/audit/events/list` returns, as a CSV file. Team plan and above — a Developer account gets `402` with the plan that lifts it and a link to buy it.\n\nAnswers `text/csv`, not the JSON envelope: this is a file, and the point of it is that it lands in a spreadsheet or an evidence folder without being reshaped first.\n\nBounded at 10,000 rows per call. `X-Xtkn-Audit-Truncated: true` on the response means older events remain — page backwards by re-requesting with `before` set to the `X-Xtkn-Audit-Next-Before` value. An account that wants the trail continuously wants a streaming destination, not a loop over this.\n\nFields opening with `=`, `+`, `-` or `@` are prefixed with an apostrophe so a spreadsheet renders them as text rather than executing them.\n",
        "tags": [
          "xtkn"
        ],
        "security": [
          {
            "xtknApiKey": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "token.created",
                        "token.read",
                        "token.revoked",
                        "token.expired",
                        "token.failed"
                      ]
                    }
                  },
                  "tokenId": {
                    "type": "string"
                  },
                  "refId": {
                    "type": "string"
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "api",
                      "retrieval",
                      "system"
                    ]
                  },
                  "since": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "before": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10000,
                    "default": 10000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "CSV, one row per event, newest first.",
            "headers": {
              "X-Xtkn-Audit-Rows": {
                "schema": {
                  "type": "integer"
                },
                "description": "Rows in this file, excluding the header."
              },
              "X-Xtkn-Audit-Truncated": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "true",
                    "false"
                  ]
                },
                "description": "Whether older events remain beyond this file."
              },
              "X-Xtkn-Audit-Next-Before": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "Send this back as `before` to fetch the next page. Present only when truncated. Use it verbatim rather than deriving a cursor from the file — `before` is exclusive, and the oldest `occurredAt` in the file can have siblings at the same millisecond that this value is set behind.\n"
              }
            },
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "example": "id,occurredAt,event,tokenId,tokenType,refId,source,reason,ip,apiKeyId\n\"0d0a…\",\"2026-08-30T12:00:00.000Z\",\"token.read\",\"9b1c…\",\"secret\",\"user_abc\",\"retrieval\",\"\",\"203.0.113.7\",\"\"\n"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "description": "The key carries an IP allowlist and the request did not come from it (`details.code` is `IP_NOT_ALLOWED`). A plan that does not include audit export is `402`, not this.\n"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/audit/events/list": {
      "post": {
        "operationId": "xtknListAuditEvents",
        "summary": "The token audit trail",
        "description": "Every lifecycle event on the authenticated customer's tokens, newest first — created, read, revoked, expired, and failed attempts against a token that exists.\n\n**Reading your own trail is never gated.** What the plan sets is how far back it goes: 7 days on Developer, 90 on Team, 365 on Enterprise. An Enterprise account that has negotiated a longer term reads that term instead — it is only ever longer than the plan's. The window is applied server-side, so a `since` reaching further back than the window allows is clamped rather than refused, and `retentionDays` on the response says which window was in force.\n\n`source` separates who acted: `api` is an authenticated call against your own integration, `retrieval` is the anonymous recipient page, and `system` is our expiry sweep. A `token.read` from `retrieval` is the recipient opening the link.\n\nNever present: the token code or its payload. A failed attempt against a code matching no token of yours is also absent — there is nothing to attribute it to.\n",
        "tags": [
          "xtkn"
        ],
        "security": [
          {
            "xtknApiKey": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "token.created",
                        "token.read",
                        "token.revoked",
                        "token.expired",
                        "token.failed"
                      ]
                    },
                    "description": "Restrict to these event names. An unknown name is a 400, not an empty page."
                  },
                  "tokenId": {
                    "type": "string"
                  },
                  "refId": {
                    "type": "string"
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "api",
                      "retrieval",
                      "system"
                    ]
                  },
                  "since": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Inclusive lower bound. Clamped up to the plan's retention floor."
                  },
                  "before": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Exclusive upper bound. Page backwards with this."
                  },
                  "page": {
                    "type": "integer",
                    "minimum": 1,
                    "default": 1
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 200,
                    "default": 50
                  }
                }
              },
              "examples": {
                "reads": {
                  "summary": "Every retrieval of one secret",
                  "value": {
                    "events": [
                      "token.read"
                    ],
                    "tokenId": "9b1c7a04-3f52-4d8e-a0b6-5e2c81f4d7a3"
                  }
                },
                "failures": {
                  "summary": "Failed attempts in the last day",
                  "value": {
                    "events": [
                      "token.failed"
                    ],
                    "since": "2026-08-29T00:00:00Z"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One page of events as `data.events`, `data.count` holding the total across every page, and `data.retentionDays` naming the window in force so a client can say why history stops.\n",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "events": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string"
                                  },
                                  "event": {
                                    "type": "string",
                                    "example": "token.read"
                                  },
                                  "tokenId": {
                                    "type": "string",
                                    "nullable": true
                                  },
                                  "tokenType": {
                                    "type": "string",
                                    "nullable": true
                                  },
                                  "refId": {
                                    "type": "string",
                                    "nullable": true
                                  },
                                  "source": {
                                    "type": "string",
                                    "enum": [
                                      "api",
                                      "retrieval",
                                      "system"
                                    ]
                                  },
                                  "reason": {
                                    "type": "string",
                                    "nullable": true
                                  },
                                  "ip": {
                                    "type": "string",
                                    "nullable": true
                                  },
                                  "apiKeyId": {
                                    "type": "string",
                                    "nullable": true
                                  },
                                  "occurredAt": {
                                    "type": "string",
                                    "format": "date-time"
                                  }
                                }
                              }
                            },
                            "count": {
                              "type": "integer"
                            },
                            "retentionDays": {
                              "type": "integer",
                              "nullable": true
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/billing/upgrade-intent": {
      "post": {
        "operationId": "xtknCreateUpgradeIntent",
        "summary": "Get a Checkout link a human can pay through",
        "description": "Produces a Stripe Checkout URL for a plan change, so an agent that cannot pay can still hand a human something to click.\n\n**This does not upgrade anything.** It creates a link. The plan changes when Stripe reports the completed subscription back through the webhook, which is the only path that grants a tier. Poll `/account` if you need to know when that has happened.\n\nPaying is deliberately left to a person. Recurring-subscription consent is the consent an agent has the weakest claim to give, an SCA challenge is browser-shaped, and a chargeback from a cardholder who never saw a checkout page lands on us.\n\nSay what you want one of three ways: name a `tier`, name the `capability` you were refused and get the cheapest plan granting it, or send nothing and get the next plan up from the one you are on.\n\nThe response carries the Checkout URL and, alongside it, `tokenUrl` — the same link minted as a single-use X-TKN secret that burns on first read and expires with the intent. Prefer it when the link is going somewhere it will be logged. `tokenUrl` is null if the mint failed; `url` still works.\n",
        "tags": [
          "xtkn"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tier": {
                    "type": "string",
                    "enum": [
                      "team",
                      "enterprise"
                    ],
                    "description": "Buy this plan. Wins over `capability`."
                  },
                  "capability": {
                    "type": "string",
                    "enum": [
                      "branding",
                      "recipientAuth",
                      "webhooks",
                      "auditExport",
                      "auditStreaming",
                      "ipAllowlist"
                    ],
                    "description": "Buy the cheapest plan granting this. Pass the `capability` from a `402` body to buy exactly what you were refused.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A link to pay through. Nothing has been charged or changed.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "required": [
                            "url",
                            "tier",
                            "priceId",
                            "expiresAt"
                          ],
                          "properties": {
                            "url": {
                              "type": "string",
                              "format": "uri",
                              "description": "Stripe Checkout link."
                            },
                            "tokenUrl": {
                              "type": "string",
                              "nullable": true,
                              "description": "The same link as a single-use X-TKN secret, or null if the mint failed."
                            },
                            "code": {
                              "type": "string",
                              "nullable": true,
                              "description": "That token's code."
                            },
                            "tier": {
                              "type": "string",
                              "enum": [
                                "team",
                                "enterprise"
                              ]
                            },
                            "priceId": {
                              "type": "string"
                            },
                            "expiresAt": {
                              "type": "string",
                              "format": "date-time"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`tier` is not a plan we sell, or `capability` is not one we recognise. The message lists what is accepted.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "This account cannot be sold a subscription in the state it is in. Either it already has an active one — plan changes go through the billing portal, where Stripe prorates them — or it is an unclaimed sandbox account, which is anonymous and reaped within the day, so claim it at `POST /accounts/claim` first. The message says which.\n"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Nothing can be sold — the account is already on the highest plan, or no Stripe price is configured for the plan it asked for. Both are ours to fix, not the caller's.\n"
          }
        }
      }
    },
    "/secrets/challenge": {
      "post": {
        "operationId": "xtknRequestSecretChallenge",
        "summary": "Request the one-time code for an email-gated secret",
        "description": "Unauthenticated. Answers 202 whether or not the address matches, so it cannot be used to confirm who a secret was sent to. A mismatch still costs one attempt from the secret's budget, which is what bounds enumeration.\nOnly tokens minted with `type: secret` and `authMethod: email` have a challenge to issue; every other case answers 202 as well.\n",
        "tags": [
          "xtkn"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code",
                  "email"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 64,
                    "maxLength": 64,
                    "pattern": "^[0-9a-fA-F]{64}$",
                    "description": "The 64 hex characters after the `#` in the recipient link. Case-insensitive — folded to lowercase before the lookup, so a retyped or case-mangled link still resolves."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 320
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted. A code has been sent if the address was the right one. Deliberately indistinguishable from a mismatch.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "description": "Too many failed verifications; the secret is locked."
          },
          "410": {
            "description": "The secret is gone — already viewed, revoked, or expired."
          },
          "429": {
            "$ref": "#/components/responses/WafRateLimited"
          },
          "500": {
            "$ref": "#/components/responses/UnmeteredServerError"
          },
          "502": {
            "description": "We could not send the mail. Retrying is the right response."
          }
        }
      }
    },
    "/secrets/peek": {
      "post": {
        "operationId": "xtknPeekSecret",
        "summary": "Check whether a secret can still be viewed",
        "description": "Unauthenticated. Answers the state of a one-time secret without consuming it, so a retrieval page can warn the recipient before anything is burned.\nOnly tokens minted with `type: secret` are reachable here; every other token in the tenant answers 404 exactly as an unknown code does.\n",
        "tags": [
          "xtkn"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 64,
                    "maxLength": 64,
                    "pattern": "^[0-9a-fA-F]{64}$",
                    "description": "The 64 hex characters after the `#` in the recipient link. Case-insensitive — folded to lowercase before the lookup, so a retyped or case-mangled link still resolves."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The secret exists. `state` is one of ready, revoked, burned, expired, locked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "state": {
                      "type": "string",
                      "enum": [
                        "ready",
                        "revoked",
                        "burned",
                        "expired",
                        "locked"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "expiresAt": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "auth": {
                      "type": "object",
                      "description": "Present only when the secret is gated. Names the method so the page can render the right prompt. The address behind an `email` gate is deliberately not disclosed.\n",
                      "properties": {
                        "method": {
                          "type": "string",
                          "enum": [
                            "passphrase",
                            "email"
                          ]
                        },
                        "attemptsRemaining": {
                          "type": "integer"
                        }
                      }
                    },
                    "branding": {
                      "type": "object",
                      "description": "Present only when the sending tenant is on Team or above and has configured branding. Absent entirely otherwise, so an unbranded secret's body is unchanged.\n",
                      "properties": {
                        "logoUrl": {
                          "type": "string",
                          "format": "uri"
                        },
                        "color": {
                          "type": "string",
                          "pattern": "^#[0-9a-f]{6}$"
                        },
                        "colorForeground": {
                          "type": "string",
                          "pattern": "^#[0-9a-f]{6}$"
                        },
                        "senderName": {
                          "type": "string",
                          "maxLength": 64
                        },
                        "supportUrl": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/WafRateLimited"
          },
          "500": {
            "$ref": "#/components/responses/UnmeteredServerError"
          }
        }
      }
    },
    "/secrets/reveal": {
      "post": {
        "operationId": "xtknRevealSecret",
        "summary": "View a one-time secret, consuming it",
        "description": "Unauthenticated, and destructive: a successful call increments `uses` atomically, so exactly one caller can succeed on a single-use secret no matter how many arrive at once. Every subsequent call answers 410 with `state: burned`.\nOnly tokens minted with `type: secret` are reachable here.\n",
        "tags": [
          "xtkn"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 64,
                    "maxLength": 64,
                    "pattern": "^[0-9a-fA-F]{64}$",
                    "description": "The 64 hex characters after the `#` in the recipient link. Case-insensitive — folded to lowercase before the lookup, so a retyped or case-mangled link still resolves."
                  },
                  "passphrase": {
                    "type": "string",
                    "maxLength": 256,
                    "description": "Required when the secret was minted with `authMethod: passphrase`. Ignored otherwise.\n"
                  },
                  "challenge": {
                    "type": "string",
                    "pattern": "^[0-9]{6}$",
                    "description": "The code from `POST /secrets/challenge`, required when the secret was minted with `authMethod: email`.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The secret was burned. `payload` is the stored blob.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "payload": {
                      "description": "The blob the sender stored. Opaque to the server in fragment-key mode."
                    },
                    "revealedAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "description": "The secret is gated and no credential was presented, or the one presented was wrong. `auth.attemptsRemaining` counts down only on a wrong credential; presenting none costs nothing.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "auth": {
                      "type": "object",
                      "properties": {
                        "method": {
                          "type": "string",
                          "enum": [
                            "passphrase",
                            "email"
                          ]
                        },
                        "state": {
                          "type": "string",
                          "enum": [
                            "required",
                            "failed"
                          ],
                          "description": "`required` when nothing was presented, `failed` when what was presented was wrong. Only `failed` has cost an attempt.\n"
                        },
                        "attemptsRemaining": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Too many failed verifications. The secret is intact but refused; only the sender can lift it, by re-sending.\n"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "description": "The code is valid but the secret is gone — already viewed, revoked, or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "state": {
                      "type": "string",
                      "enum": [
                        "revoked",
                        "burned",
                        "expired"
                      ]
                    },
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/WafRateLimited"
          },
          "500": {
            "description": "Unexpected server error — and on this endpoint that can mean the use was spent. `consumed: true` says so: the secret was burned and we could not hand it back, so it cannot be recovered and retrying will not produce it. Without that flag the call never reached the burn and a retry is worth making. Branch on it rather than offering \"try reloading\" either way — reloading a consumed secret answers 410 `burned`, which reads as though somebody else opened it.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "consumed": {
                      "type": "boolean",
                      "description": "Present and true only when the use was spent and the payload was lost."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/tokens/{code}/read": {
      "get": {
        "operationId": "xtknReadToken",
        "summary": "Read a token",
        "description": "Returns the token identified by `code` if it belongs to the authenticated customer. This is a pure read — it does **not** increment `uses` or update `lastUsedAt`. Use `/tokens/{code}/redeem` to consume a use.\nThe response does **not** echo `code` back. Only create returns a code in clear; from then on the server holds nothing but its hash. Use `displayName` to identify the token to a human, and `id` to address it anywhere a code should not appear.\n",
        "tags": [
          "xtkn"
        ],
        "security": [
          {
            "xtknApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Public token code. Unique per customer."
          }
        ],
        "responses": {
          "200": {
            "description": "The token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/XtknTokenEnvelope"
                },
                "examples": {
                  "token": {
                    "summary": "Example token",
                    "value": {
                      "success": true,
                      "data": {
                        "id": "9b1c7a04-3f52-4d8e-a0b6-5e2c81f4d7a3",
                        "displayName": "7f3a9...b18",
                        "type": "handoff",
                        "refId": "user_abc",
                        "payload": null,
                        "uses": 0,
                        "maxUses": 1,
                        "expiresAt": "2026-04-13T12:05:00Z",
                        "lastUsedAt": null,
                        "isRevoked": false,
                        "description": null,
                        "customer": 17,
                        "customerId": "17",
                        "isActive": true,
                        "isExpired": false,
                        "isUsed": false,
                        "createdAt": "2026-04-13T12:00:00Z",
                        "updatedAt": "2026-04-13T12:00:00Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/tokens/{code}/redeem": {
      "patch": {
        "operationId": "xtknRedeemToken",
        "summary": "Redeem a token",
        "description": "Consumes one use of the token identified by `code`. On success, `uses` is incremented by 1 and `lastUsedAt` is set to the current time.\nPass `refId` to bind the check to the identity the code was issued for. Without it, a code only has to match *some* live token in your tenant; with it, the code must be the one issued to that specific user.\nPass `type` alongside it. It confines the lookup to that kind of token, so verifying a passcode cannot redeem the coupon or the invite issued to the same `refId`. Send it whenever you know what you are verifying.\nRedemption fails with `410 Gone` if the token is revoked, expired (`expiresAt` passed), or has reached `maxUses`. Callers should treat 404 (not found / not owned) and 410 (no longer usable) as distinct: 404 means the `code` is wrong, 410 means it cannot be redeemed.\nGuess rate is bounded by the per-customer rate limit rather than a per-token attempt budget: codes are 256-bit, so there is nothing to brute-force.\nTypical uses: consuming one-time passcodes, single-use coupon codes, magic-link session tokens. Callers that just want to inspect a token without consuming a use should call `/tokens/{code}/read` instead.\n",
        "tags": [
          "xtkn"
        ],
        "security": [
          {
            "xtknApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Public token code to redeem."
          },
          {
            "name": "refId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 256
            },
            "description": "Identity the code was issued for. Alternative to sending `refId` in the body, which takes precedence when both are present.\n"
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 64,
              "pattern": "^[A-Za-z0-9_-]+$"
            },
            "description": "Kind of token being verified. Alternative to sending `type` in the body, which takes precedence when both are present.\n"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "refId": {
                    "type": "string",
                    "maxLength": 256,
                    "description": "Identity the code was issued for."
                  },
                  "type": {
                    "type": "string",
                    "maxLength": 64,
                    "pattern": "^[A-Za-z0-9_-]+$",
                    "description": "Kind of token being verified. Confines the lookup to that type, so verifying a passcode cannot consume the coupon or invite issued to the same `refId`. Case-insensitive — lowercased on the way in, as it is on create."
                  }
                }
              },
              "examples": {
                "scoped": {
                  "summary": "Verify a passcode against the user it was issued to",
                  "value": {
                    "refId": "user_abc",
                    "type": "handoff"
                  }
                },
                "unscoped": {
                  "summary": "Legacy code-only verify — unbounded guessing, not recommended",
                  "value": {}
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful redemption. `data` is the updated token with incremented `uses`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/XtknTokenEnvelope"
                },
                "examples": {
                  "redeemed": {
                    "summary": "Single-use handoff token, now spent",
                    "value": {
                      "success": true,
                      "data": {
                        "id": "9b1c7a04-3f52-4d8e-a0b6-5e2c81f4d7a3",
                        "displayName": "7f3a9...b18",
                        "type": "handoff",
                        "refId": "user_abc",
                        "payload": null,
                        "uses": 1,
                        "maxUses": 1,
                        "expiresAt": "2026-04-13T12:05:00Z",
                        "lastUsedAt": "2026-04-13T12:04:00Z",
                        "isRevoked": false,
                        "description": null,
                        "customer": 17,
                        "customerId": "17",
                        "isActive": false,
                        "isExpired": false,
                        "isUsed": true,
                        "createdAt": "2026-04-13T12:00:00Z",
                        "updatedAt": "2026-04-13T12:04:00Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "description": "Token cannot be redeemed — revoked, expired, or at its `maxUses`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "inactive": {
                    "value": {
                      "error": "Token is revoked, expired, or exhausted"
                    }
                  }
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/tokens/{code}/revoke": {
      "patch": {
        "operationId": "xtknRevokeToken",
        "summary": "Revoke a token",
        "description": "Sets `isRevoked: true` on the token identified by `code`. A revoked token is permanently unusable — subsequent `redeem` calls will fail even if it still has remaining `uses` or unexpired `expiresAt`. Revocation is idempotent: revoking an already-revoked token succeeds and returns the token unchanged.\n",
        "tags": [
          "xtkn"
        ],
        "security": [
          {
            "xtknApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Public token code."
          }
        ],
        "responses": {
          "200": {
            "description": "The revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/XtknTokenEnvelope"
                },
                "examples": {
                  "revoked": {
                    "summary": "Token after revocation",
                    "value": {
                      "success": true,
                      "data": {
                        "id": "9b1c7a04-3f52-4d8e-a0b6-5e2c81f4d7a3",
                        "displayName": "7f3a9...b18",
                        "type": "handoff",
                        "refId": "user_abc",
                        "payload": null,
                        "uses": 0,
                        "maxUses": 1,
                        "expiresAt": "2026-04-13T12:05:00Z",
                        "lastUsedAt": null,
                        "isRevoked": true,
                        "description": null,
                        "customer": 17,
                        "customerId": "17",
                        "isActive": false,
                        "isExpired": false,
                        "isUsed": false,
                        "createdAt": "2026-04-13T12:00:00Z",
                        "updatedAt": "2026-04-13T12:02:00Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/tokens/{code}": {
      "delete": {
        "operationId": "xtknDeleteToken",
        "summary": "Delete a token",
        "description": "Permanently deletes a token owned by the authenticated customer. This is a hard delete — the row is removed from the database and the `code` becomes available for reuse by future `createToken` calls for this same customer. Prefer `/tokens/{code}/revoke` if you need an auditable record that the token existed.\n",
        "tags": [
          "xtkn"
        ],
        "security": [
          {
            "xtknApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Public token code."
          }
        ],
        "responses": {
          "200": {
            "description": "Token deleted. `data` is null.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "null"
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "deleted": {
                    "value": {
                      "success": true,
                      "data": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/tokens/{code}/update": {
      "patch": {
        "operationId": "xtknUpdateToken",
        "summary": "Update a token",
        "description": "Updates mutable fields on a token owned by the authenticated customer. Only the fields listed below may be changed — in particular, `code`, `uses`, `isRevoked`, and `customer` are immutable via this endpoint. Unknown fields are silently ignored. Send only the fields you want to change; omitted fields are preserved.\n\n**Payload encryption.** Replacing `payload` is a second way to write the column, so it is held to exactly the same rules as `POST /tokens/create`: the same per-plan stored cap, the same `xtkn.v1.` / `xtkn.v1r.` envelope requirement, and the same `400` for a malformed envelope. See that endpoint's description for the envelope format. Sending `payload: null` clears it.\n\n**`expiresAt` is held to the plan's window**, for the same reason — otherwise an update would be a way around the fence create applies. One exemption: resubmitting the date already stored is always accepted, so a read-modify-write client editing an unrelated field is never refused a value it did not choose. Reaching any further forward than `limits.maxExpiryDays` is a `400`.\n\n**Recipient authentication** is set here exactly as on `POST /tokens/create`: Team and Enterprise only, a `402` on Developer, and `type: secret` only — the type this same request writes, if it sends one, otherwise the stored type. Setting a method or a new passphrase starts the gate over, with a fresh failed-check count and no emailed code still live. `authMethod: null` clears the gate, on any plan, including one that has lapsed since the gate was set. Omit `authMethod` to leave the gate as it is; `authPassphrase` and `authEmail` sent without it are ignored.\n\nUnlike `expiresAt`, an echoed gate is not exempt. A token read back carries its `authMethod`, so a read-modify-write client that sends the whole object restates the gate: on a passphrase gate that is a `400` for the missing `authPassphrase`, and on an email gate it starts the gate over. Leave `authMethod` out of such a request.\n",
        "tags": [
          "xtkn"
        ],
        "security": [
          {
            "xtknApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Public token code."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "maxLength": 64,
                    "pattern": "^[A-Za-z0-9_-]+$",
                    "description": "Case-insensitive, lowercased on storage — the same rule as create. No name is reserved: retyping a token cannot turn it into a credential, since credentials are a separate collection. Retyping to `secret` is refused on an unclaimed sandbox account, which create refuses too."
                  },
                  "refId": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256,
                    "nullable": true,
                    "description": "Replacement reference id. Same 1-256 character bound as POST /tokens/create — send `null` to clear it, since an empty string is rejected. Omitting the key leaves the stored value untouched."
                  },
                  "payload": {
                    "type": "string",
                    "nullable": true,
                    "maxLength": 349620,
                    "description": "Replacement payload, stored as text. **Capped by your plan**, exactly as on create: 87,476 stored bytes on Developer (64 KB of your own data), 349,620 on Team and Enterprise (256 KB). The `maxLength` here is the largest of them, since a schema cannot vary by plan. Must be an `xtkn.v1.` or `xtkn.v1r.` envelope. `null` clears the payload."
                  },
                  "expiresAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Replacement expiry, held to the plan's window unless it echoes the date already stored. See the endpoint description."
                  },
                  "maxUses": {
                    "type": "integer",
                    "description": "Replacement use budget; `null` = unlimited. **This path does not re-validate it.** Unlike `type`, `refId`, `payload` and `expiresAt`, `maxUses` is written to the column exactly as sent, so the 1–1,000,000 bound `POST /tokens/create` enforces does not apply here and no `maximum` is published. Stay inside that range regardless: `0` or a negative mints a token that is already exhausted, and nothing here refuses one."
                  },
                  "authMethod": {
                    "type": "string",
                    "enum": [
                      "passphrase",
                      "email"
                    ],
                    "nullable": true,
                    "description": "Set, replace or clear the recipient-authentication gate. **Team and Enterprise only** to set one, and `type: secret` only. `null` clears it on any plan. Omitting the key leaves the gate untouched. See the endpoint description."
                  },
                  "authPassphrase": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 256,
                    "description": "Required when `authMethod` is `passphrase`, ignored otherwise — including when `authMethod` is omitted, so a passphrase cannot be changed without restating the method. Same 8-256 character bound as create."
                  },
                  "authEmail": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 320,
                    "description": "Required when `authMethod` is `email`, ignored otherwise. Same rules as create."
                  }
                }
              },
              "examples": {
                "extendExpiry": {
                  "summary": "Push out the expiration date",
                  "value": {
                    "expiresAt": "2026-05-01T00:00:00Z"
                  }
                },
                "relink": {
                  "summary": "Re-link to a different external entity",
                  "value": {
                    "refId": "user_xyz"
                  }
                },
                "replaceEncryptedPayload": {
                  "summary": "Replace the payload with a client-encrypted envelope",
                  "value": {
                    "payload": "xtkn.v1.q83vEjRWeJCrze8S.5vHqLXwPTmR2aFk9dQ7nZbYs1uMcOgIhE4tKjVlN"
                  }
                },
                "clearRecipientAuth": {
                  "summary": "Lift the recipient-authentication gate from a secret",
                  "value": {
                    "authMethod": null
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated token. Like every response other than create, this does **not** echo `code` back — a read-modify-write client cannot recover the code from here, only `id` and `displayName`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/XtknTokenEnvelope"
                },
                "examples": {
                  "updated": {
                    "summary": "Expiry pushed out; no code in the response",
                    "value": {
                      "success": true,
                      "data": {
                        "id": "9b1c7a04-3f52-4d8e-a0b6-5e2c81f4d7a3",
                        "displayName": "7f3a9...b18",
                        "type": "handoff",
                        "refId": "user_abc",
                        "payload": null,
                        "uses": 0,
                        "maxUses": 1,
                        "expiresAt": "2026-05-01T00:00:00Z",
                        "lastUsedAt": null,
                        "isRevoked": false,
                        "description": null,
                        "customer": 17,
                        "customerId": "17",
                        "isActive": true,
                        "isExpired": false,
                        "isUsed": false,
                        "createdAt": "2026-04-13T12:00:00Z",
                        "updatedAt": "2026-04-13T12:06:00Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid field. Every plan fence on this endpoint answers here rather than with a `402`: a `payload` past the plan's stored cap, a malformed envelope, a payload that is not an envelope at all, an `expiresAt` reaching beyond the plan's window, a `refId` outside 1–256 characters, an `authMethod` other than `passphrase`, `email` or `null`, recipient authentication on a type other than `secret`, a missing or out-of-range `authPassphrase`, a missing or malformed `authEmail`, and — on an unclaimed sandbox account — retyping to `secret`. Setting recipient authentication on the Developer plan is the one exception, a `402`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/tokens/create": {
      "post": {
        "operationId": "xtknCreateToken",
        "summary": "Create a token",
        "description": "Creates a new token owned by the authenticated customer. The server generates a unique `code` (opaque, 64-char hex) — callers cannot specify it. All other fields are optional; unknown fields are silently ignored.\n\n**This is the only response that carries `code`.** Only `sha256(code)` is stored, so no later read, redeem, revoke or update can return it — those responses omit the field entirely. Capture it here or it is unrecoverable; `displayName` and `id` are what identify the token afterwards.\n\n**Payload encryption.** `payload` is stored exactly as sent and is never encrypted server-side — we hold no key and cannot read an encrypted one back. Every account must send one of two envelope forms, and gets `400` for anything else:\n\n`xtkn.v1.<nonce>.<ciphertext>` — AES-256-GCM. Unpadded base64url of the 12-byte nonce, a literal `.`, then unpadded base64url of the WebCrypto output (ciphertext with its 16-byte tag already appended).\n\n`xtkn.v1r.<bytes>` — ciphertext from a scheme of your own, stored opaque. Unpadded base64url; we check nothing else about it.\n\nPlaintext is refused on every account from the encrypted-payload cutover. Until 2026-10-07 support can exempt a single account whose integration needs time to start encrypting; after that date the exemption is gone. A malformed envelope is rejected either way, since nobody could decrypt it later. Note that the shape check is not proof of encryption — base64url of plaintext in a `v1r` envelope passes — so it catches an integration that forgot to encrypt, not a caller who chose not to.\n\n**Recipient authentication (Team and Enterprise).** A `type: secret` token can require its recipient to prove who they are before `POST /secrets/reveal` releases the ciphertext. Send `authMethod: passphrase` with an `authPassphrase` you share with the recipient out of band, or `authMethod: email` with the `authEmail` that `POST /secrets/challenge` will send a 6-digit code to. The gate releases the ciphertext and nothing more — your key is still the only thing that decrypts it. The passphrase is stored as a scrypt verifier and never returned; `POST /secrets/peek` reports the gate as `auth.method`. `authMethod` on the Developer plan is a `402`, and on any type other than `secret` a `400`. `authPassphrase` and `authEmail` are ignored unless `authMethod` is sent.\n",
        "tags": [
          "xtkn"
        ],
        "security": [
          {
            "xtknApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "default": "generic",
                    "maxLength": 64,
                    "pattern": "^[A-Za-z0-9_-]+$",
                    "description": "Caller-defined token type. Case-insensitive on the way in and lowercased on storage, so `Handoff` is accepted and stored — and read back — as `handoff`. No name is reserved: credentials live in a separate collection, so a token cannot select a code shape or authenticate whatever it is called."
                  },
                  "refId": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256,
                    "description": "Reference id linking the token to an external entity. Capped at 256 characters — the same bound the redeem endpoint applies when scoping a verify to it. Omit the key to store none; an empty string is rejected rather than treated as absent, because a verify cannot be scoped to one either, so a token carrying it could never be looked up by it."
                  },
                  "payload": {
                    "type": "string",
                    "maxLength": 349620,
                    "description": "Arbitrary payload stored as text; callers typically JSON-stringify an object. **The cap is your plan's, not one number.** It is the stored size of the plan's `maxPayloadBytes` under either envelope form: 87,476 bytes on Developer (64 KB of your own data) and 349,620 on Team and Enterprise (256 KB). `GET /account` reports `limits.maxPayloadBytes`, which is the plaintext budget the stored cap is derived from. The `maxLength` here is the largest of them, since a schema cannot vary by plan. Must be an `xtkn.v1.` or `xtkn.v1r.` envelope — see the endpoint description."
                  },
                  "expiresAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Absolute expiry. **Capped by your plan**, which is what `limits.maxExpiryDays` on `GET /account` reports: 7 days ahead on Developer, 30 on Team, uncapped on Enterprise. Reaching further forward than that is a `400`. A date in the past is accepted — minting an already-expired token is legal. Omit it and the token still ages out 30 days after creation."
                  },
                  "maxUses": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000000,
                    "description": "Omit for unlimited uses. 0 is rejected rather than treated as unlimited",
                    "since it would mint a token that is exhausted on arrival.": null
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 4000
                  },
                  "authMethod": {
                    "type": "string",
                    "enum": [
                      "passphrase",
                      "email"
                    ],
                    "description": "Gate retrieval of this secret behind recipient authentication. **Team and Enterprise only** — a `402` on Developer — and `type: secret` only, a `400` on any other type. Omit it for an ungated token. See the endpoint description."
                  },
                  "authPassphrase": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 256,
                    "description": "Required when `authMethod` is `passphrase`, ignored otherwise. 8 to 256 characters, counted after Unicode NFKC normalization and trimming. Stored only as a scrypt verifier: no response returns it, so share it with the recipient yourself."
                  },
                  "authEmail": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 320,
                    "description": "Required when `authMethod` is `email`, ignored otherwise. The address `POST /secrets/challenge` sends the 6-digit code to; a recipient must name it to be sent one. Stored lowercased. Its own field rather than a reading of `refId`, which stays an opaque key of yours."
                  }
                }
              },
              "examples": {
                "handoff": {
                  "summary": "Short-lived one-time handoff token",
                  "value": {
                    "type": "handoff",
                    "refId": "user_abc",
                    "maxUses": 1,
                    "expiresAt": "2026-04-13T12:05:00Z"
                  }
                },
                "encryptedPayload": {
                  "summary": "Client-encrypted payload in the published v1 envelope",
                  "value": {
                    "type": "handoff",
                    "refId": "user_abc",
                    "maxUses": 1,
                    "payload": "xtkn.v1.q83vEjRWeJCrze8S.5vHqLXwPTmR2aFk9dQ7nZbYs1uMcOgIhE4tKjVlN"
                  }
                },
                "ownSchemePayload": {
                  "summary": "Ciphertext from your own scheme, stored opaque under v1r",
                  "value": {
                    "type": "magic-link",
                    "refId": "user_abc",
                    "maxUses": 1,
                    "payload": "xtkn.v1r.VSEm9w6pPEZvE8-20rJyv9B_p3lNfRM0ZOXQxvGj"
                  }
                },
                "passphraseGatedSecret": {
                  "summary": "One-time secret the recipient must unlock with a passphrase (Team and Enterprise)",
                  "value": {
                    "type": "secret",
                    "maxUses": 1,
                    "payload": "xtkn.v1.q83vEjRWeJCrze8S.5vHqLXwPTmR2aFk9dQ7nZbYs1uMcOgIhE4tKjVlN",
                    "authMethod": "passphrase",
                    "authPassphrase": "correct horse battery staple"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created token, including its `code` in clear.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/XtknCreatedTokenEnvelope"
                },
                "examples": {
                  "created": {
                    "summary": "The one response that contains a code",
                    "value": {
                      "success": true,
                      "data": {
                        "id": "9b1c7a04-3f52-4d8e-a0b6-5e2c81f4d7a3",
                        "code": "7f3a9c1e04b8d25a6c0f91b3ed47a852f19d6b0c73a4e8512db9f0a6c34e7b18",
                        "displayName": "7f3a9...b18",
                        "type": "handoff",
                        "refId": "user_abc",
                        "payload": null,
                        "uses": 0,
                        "maxUses": 1,
                        "expiresAt": "2026-04-13T12:05:00Z",
                        "lastUsedAt": null,
                        "isRevoked": false,
                        "description": null,
                        "customer": 17,
                        "customerId": "17",
                        "isActive": true,
                        "isExpired": false,
                        "isUsed": false,
                        "createdAt": "2026-04-13T12:00:00Z",
                        "updatedAt": "2026-04-13T12:00:00Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid field. Every plan fence on this endpoint answers here rather than with a `402`, because each one is something the caller can send differently: a `payload` past the plan's stored cap, a malformed envelope, a payload that is not an envelope at all, an `expiresAt` beyond the plan's window, a `maxUses` outside 1–1,000,000, a `refId` outside 1–256 characters, an `authMethod` other than `passphrase` or `email`, recipient authentication on a type other than `secret`, a missing or out-of-range `authPassphrase`, a missing or malformed `authEmail`, and — on an unclaimed sandbox account — `type: secret`, which names `POST /accounts/claim` in the message. Recipient authentication on the Developer plan is the one exception, a `402`: sending it differently does not help, and upgrading does.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/tokens/list": {
      "post": {
        "operationId": "xtknListTokens",
        "summary": "List tokens",
        "description": "Returns a paginated list of tokens owned by the authenticated customer. Results are always scoped to the caller — the account filter is forced server-side and cannot be overridden. Only the filter keys listed in `where` below are honored; unknown keys are silently ignored. `limit` is capped at 100.\nEach `where` field accepts either a bare value for exact match, or an operator object — `{ \"in\": [\"a\", \"b\"] }`, `{ \"not_equals\": \"x\" }`. All conditions are AND-ed together. The supported set is `equals`, `not_equals`, `in` and `not_in`; an operator outside it is passed to the query layer unvalidated on `type`, `refId` and `isRevoked`, so it may appear to work and is not part of this contract.\n`code` is the exception, and the only field whose operators are checked. It is matched against the stored hash, so pass the plaintext code and let the server hash it — and being a hash, it answers only the four above. Any other operator on `code` is a 400 rather than a silently empty page.\nAn unrecognised `sort` is replaced with `-createdAt` rather than rejected, so a typo returns a plausible page in the wrong order.\n",
        "tags": [
          "xtkn"
        ],
        "security": [
          {
            "xtknApiKey": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "where": {
                    "type": "object",
                    "description": "Exact-match filter. Each field is AND-ed.",
                    "properties": {
                      "type": {
                        "type": "string",
                        "example": "handoff"
                      },
                      "refId": {
                        "type": "string",
                        "example": "user_abc"
                      },
                      "isRevoked": {
                        "type": "boolean",
                        "example": false
                      },
                      "code": {
                        "description": "Plaintext token code, hashed server-side before the lookup. Equality operators only.\n",
                        "oneOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "object"
                          }
                        ],
                        "example": "7f3a9c1e04b8d25a6c0f91b3ed47a852f19d6b0c73a4e8512db9f0a6c34e7b18"
                      }
                    }
                  },
                  "sort": {
                    "type": "string",
                    "default": "-createdAt",
                    "description": "Field name to sort by. Prefix with `-` for descending order. Supported fields — `createdAt`, `updatedAt`, `expiresAt`, `lastUsedAt`, `uses`.\n"
                  },
                  "page": {
                    "type": "integer",
                    "minimum": 1,
                    "default": 1
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "default": 100
                  }
                }
              },
              "examples": {
                "activeHandoffs": {
                  "summary": "Active (non-revoked) handoff tokens, newest first",
                  "value": {
                    "where": {
                      "type": "handoff",
                      "isRevoked": false
                    },
                    "sort": "-createdAt",
                    "page": 1,
                    "limit": 50
                  }
                },
                "byRefId": {
                  "summary": "All tokens linked to a specific external entity",
                  "value": {
                    "where": {
                      "refId": "user_abc"
                    }
                  }
                },
                "codeBatch": {
                  "summary": "Look several codes up at once",
                  "value": {
                    "where": {
                      "code": {
                        "in": [
                          "7f3a9c1e04b8d25a6c0f91b3ed47a852f19d6b0c73a4e8512db9f0a6c34e7b18"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One page of tokens as `data.tokens`, with `data.count` holding the total across every page. This endpoint does not return Payload's pagination block — there is no `docs`, `totalPages` or `hasNextPage`. Keep requesting while `page * limit < count`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/XtknTokenListEnvelope"
                },
                "examples": {
                  "onePage": {
                    "summary": "Two of nine matching tokens",
                    "value": {
                      "success": true,
                      "data": {
                        "tokens": [
                          {
                            "id": "9b1c7a04-3f52-4d8e-a0b6-5e2c81f4d7a3",
                            "displayName": "7f3a9...b18",
                            "type": "handoff",
                            "refId": "user_abc",
                            "uses": 0,
                            "maxUses": 1,
                            "isRevoked": false,
                            "isActive": true,
                            "isExpired": false,
                            "isUsed": false,
                            "customer": 17,
                            "customerId": "17",
                            "createdAt": "2026-04-13T12:00:00Z",
                            "updatedAt": "2026-04-13T12:00:00Z"
                          },
                          {
                            "id": "2d70f6b8-91ac-4e13-b5d0-7c48a3e29f61",
                            "displayName": "c41d8...8a1",
                            "type": "handoff",
                            "refId": "user_xyz",
                            "uses": 1,
                            "maxUses": 1,
                            "isRevoked": false,
                            "isActive": false,
                            "isExpired": false,
                            "isUsed": true,
                            "customer": 17,
                            "customerId": "17",
                            "createdAt": "2026-04-12T09:30:00Z",
                            "updatedAt": "2026-04-12T10:15:00Z"
                          }
                        ],
                        "count": 9
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/tokens/revoke": {
      "patch": {
        "operationId": "xtknBulkRevokeTokens",
        "summary": "Revoke tokens in bulk",
        "description": "Revokes every active (non-revoked) token owned by the authenticated customer that matches the given filter. **At least one of `type` or `refId` must be provided** — a filter-less call is rejected with 400 to prevent accidentally revoking every token on the account. The `customer` scope is always enforced server-side. Up to 1000 tokens are revoked per call. When the filter matches more than that, the response comes back with `hasMore: true` — re-send the identical request until it is false. The filter skips already-revoked tokens, so each repeat drains the next batch rather than redoing the last one.\n",
        "tags": [
          "xtkn"
        ],
        "security": [
          {
            "xtknApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "anyOf": [
                  {
                    "required": [
                      "type"
                    ]
                  },
                  {
                    "required": [
                      "refId"
                    ]
                  }
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "description": "Revoke all tokens of this type."
                  },
                  "refId": {
                    "type": "string",
                    "description": "Revoke all tokens linked to this refId."
                  }
                }
              },
              "examples": {
                "byType": {
                  "summary": "Revoke every handoff token",
                  "value": {
                    "type": "handoff"
                  }
                },
                "byEntity": {
                  "summary": "Revoke every token for a departing user",
                  "value": {
                    "refId": "user_abc"
                  }
                },
                "combined": {
                  "summary": "Narrow to a type + entity combination",
                  "value": {
                    "type": "session",
                    "refId": "user_abc"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Count of tokens revoked, plus whether the filter matched more than this call could take.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/XtknBulkRevokeEnvelope"
                },
                "examples": {
                  "revokedFour": {
                    "summary": "Filter fully drained — nothing left to revoke",
                    "value": {
                      "success": true,
                      "data": {
                        "revoked": 4,
                        "hasMore": false
                      }
                    }
                  },
                  "revokedPage": {
                    "summary": "Hit the 1000-per-call cap — send the same request again",
                    "value": {
                      "success": true,
                      "data": {
                        "revoked": 1000,
                        "hasMore": true
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/webhooks/deliveries/list": {
      "post": {
        "operationId": "xtknListWebhookDeliveries",
        "summary": "The retrieval webhook delivery log",
        "description": "What happened to each event we tried to deliver, newest first. This is the surface for \"the notification never arrived\": `status`, `responseStatus` and `lastError` say whether we sent it and what the endpoint answered.\n\n`status` is `delivered` or `failed`. A `failed` row with `deliveryAttempts` below the retry ceiling will be tried again; one at the ceiling has been given up on. `tokenId` identifies the secret the event was about — never its code or payload, neither of which appears here or in the delivery itself.\n",
        "tags": [
          "xtkn"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "page": {
                    "type": "integer",
                    "minimum": 1,
                    "default": 1
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "default": 50
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Delivery history for the authenticated customer.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "deliveries": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string"
                                  },
                                  "event": {
                                    "type": "string",
                                    "example": "secret.revealed"
                                  },
                                  "tokenId": {
                                    "type": "string",
                                    "nullable": true
                                  },
                                  "status": {
                                    "type": "string",
                                    "enum": [
                                      "delivered",
                                      "failed"
                                    ],
                                    "description": "A row is written once the attempt has an outcome, so it is never `pending` — a delivery that has not been tried has no row here yet."
                                  },
                                  "deliveryAttempts": {
                                    "type": "integer"
                                  },
                                  "responseStatus": {
                                    "type": "integer",
                                    "nullable": true
                                  },
                                  "lastError": {
                                    "type": "string",
                                    "nullable": true
                                  },
                                  "deliveredAt": {
                                    "type": "string",
                                    "format": "date-time",
                                    "nullable": true
                                  },
                                  "createdAt": {
                                    "type": "string",
                                    "format": "date-time"
                                  }
                                }
                              }
                            },
                            "count": {
                              "type": "integer"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/webhooks/endpoints/{id}": {
      "patch": {
        "operationId": "xtknUpdateWebhookEndpoint",
        "summary": "Update a retrieval webhook endpoint",
        "description": "Change an endpoint's URL, description, or enabled state. Only the fields present in the body are touched.\n\nSetting `enabled: false` is the reversible way to stop deliveries — the signing secret survives, so re-enabling does not require the receiver to be reconfigured. Deleting the endpoint destroys the secret.\n\nA new `url` is validated exactly as at creation: `https`, and not resolving to a private address.\n\nOn Enterprise, only a key issued from an owner or admin seat may change an endpoint; a `member` seat's key gets `403`.\n",
        "tags": [
          "xtkn"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2048
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 4000
                  },
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated endpoint. `signingSecret` is not included."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "delete": {
        "operationId": "xtknDeleteWebhookEndpoint",
        "summary": "Delete a retrieval webhook endpoint",
        "description": "Removes the endpoint and its signing secret permanently. Past deliveries stay in the log. To stop deliveries reversibly, `PATCH` with `enabled: false` instead. On Enterprise, only a key issued from an owner or admin seat may delete one; a `member` seat's key gets `403`.\n",
        "tags": [
          "xtkn"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Endpoint deleted. `data` is null."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/webhooks/endpoints/create": {
      "post": {
        "operationId": "xtknCreateWebhookEndpoint",
        "summary": "Register a retrieval webhook endpoint",
        "description": "Registers a URL we POST to when one of your secrets is read. Requires the Team plan or above; Developer accounts get `402` with a link to upgrade. On Enterprise the key must also have been issued from an owner or admin seat: an endpoint receives every retrieval on the account, so a `member` seat's key gets `403`.\n\n**The response carries `signingSecret`, and it is the only one that ever will.** Every later read omits the field. Store it — it is the HMAC key for the `x-xtkn-signature` header, and without it a receiver cannot tell our deliveries from anyone else's POST.\n\nThe URL must be `https` and must not resolve to a private address. It is re-checked on every delivery, so an endpoint that later resolves into a private range simply stops receiving.\n",
        "tags": [
          "xtkn"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2048,
                    "example": "https://hooks.acme.com/xtkn"
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 4000
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "secret.revealed"
                      ]
                    },
                    "default": [
                      "secret.revealed"
                    ],
                    "description": "Accepted but not yet meaningful: `secret.revealed` is the only retrieval event there is, and every endpoint is subscribed to it whatever this says. Sending any other name is a `400` rather than a subscription that silently matches nothing — which would read to a caller as a delivery failure. Omit it.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Endpoint registered. `signingSecret` appears here and nowhere else.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string"
                            },
                            "url": {
                              "type": "string"
                            },
                            "events": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "enabled": {
                              "type": "boolean"
                            },
                            "signingSecret": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`url` is missing, is not `https`, or resolves to a private address; `description` is not a string; or `events` names an event that does not exist.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "description": "The key carries an IP allowlist and the request did not come from it (`details.code` is `IP_NOT_ALLOWED`); or, on Enterprise, the key was issued from a `member` seat, and only an owner or an admin may register an endpoint. A plan that does not include webhooks is `402`, not this.\n"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/webhooks/endpoints/list": {
      "post": {
        "operationId": "xtknListWebhookEndpoints",
        "summary": "List your retrieval webhook endpoints",
        "description": "Every endpoint registered by the authenticated customer, newest first.\n\n`signingSecret` is never included. It is returned once, by `/webhooks/endpoints/create`, and cannot be read back afterwards — if it has been lost, delete the endpoint and register it again.\n\nUnlike `/webhooks/endpoints/create`, this is not tier-gated: an account that has dropped to Developer can still see and remove the endpoints it registered while on Team. Only delivery stops.\n",
        "tags": [
          "xtkn"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "page": {
                    "type": "integer",
                    "minimum": 1,
                    "default": 1
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "default": 100
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The customer's endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "endpoints": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string"
                                  },
                                  "url": {
                                    "type": "string"
                                  },
                                  "events": {
                                    "type": "array",
                                    "items": {
                                      "type": "string"
                                    }
                                  },
                                  "enabled": {
                                    "type": "boolean"
                                  },
                                  "description": {
                                    "type": "string",
                                    "nullable": true
                                  },
                                  "lastDeliveryAt": {
                                    "type": "string",
                                    "format": "date-time",
                                    "nullable": true
                                  },
                                  "createdAt": {
                                    "type": "string",
                                    "format": "date-time"
                                  }
                                }
                              }
                            },
                            "count": {
                              "type": "integer"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    }
  }
}
