{
  "openapi": "3.1.0",
  "info": {
    "title": "Stabledesk API",
    "version": "1.0.0",
    "summary": "Stablecoin measurement for Arc testnet — supply, real volume, TVL and fee economics.",
    "description": "Stabledesk measures every stablecoin on Arc testnet (chain id 5042002): supply,\nvolume, TVL, flows and network fee economics, read straight from the chain.\n\nThree volume measures are published side by side rather than one blended number:\n`volume` (every Transfer event), `rvolume` (**real** — one largest transfer per transaction\nper token, so routing hops and contract internals are not counted twice) and `avolume`\n(**adjusted** — real, minus infrastructure addresses talking to infrastructure). Publishing\nall three is what makes each filtering step auditable instead of asserted.\n\nGas on Arc testnet is paid in USDC, so fee figures are dollars read from transaction\nreceipts — no price feed and no oracle is involved.\n\n**Two conventions worth knowing before consuming any figure.** A value that could not be\nmeasured is `null`, never a carried-over previous value — an old number presented as\ncurrent is a wrong number, not an old one. And when the chain is frozen the rolling 24h\nwindows end at the last indexed minute rather than at now, reported as `windowEnd`;\nwithout that they would report \"24h volume: 0\" and state the chain sat idle when what\nactually happened is that it stopped. Check `GET /v1/status` for `degraded` and `chain.state`.\n\nThe measurement method, including every threshold, is published at /methodology.",
    "contact": {
      "name": "Stabledesk",
      "url": "https://stabledesk.xyz/docs",
      "email": "studiomoreno@icloud.com"
    },
    "license": {
      "name": "Free tier — attribution appreciated",
      "url": "https://stabledesk.xyz/docs"
    }
  },
  "servers": [
    {
      "url": "https://stabledesk.xyz",
      "description": "Arc testnet (chain id 5042002)"
    }
  ],
  "externalDocs": {
    "description": "Developer docs and measurement method",
    "url": "https://stabledesk.xyz/docs"
  },
  "tags": [
    {
      "name": "Status",
      "description": "Liveness of the chain and of the index. No API key required."
    },
    {
      "name": "Keys",
      "description": "Mint a free API key. No API key required."
    },
    {
      "name": "Network",
      "description": "Chain throughput and fee economics."
    },
    {
      "name": "Stablecoins",
      "description": "Supply, volume, velocity and issuance per token."
    },
    {
      "name": "Ecosystem",
      "description": "Protocol registry joined to measured TVL and flow."
    },
    {
      "name": "Addresses",
      "description": "Top addresses, per-address activity, and the noise filter."
    },
    {
      "name": "Alerts",
      "description": "Webhook alerts on matching transfers."
    },
    {
      "name": "Billing",
      "description": "Upgrade a key to Pro by paying in USDC."
    }
  ],
  "security": [
    {
      "ApiKeyHeader": []
    },
    {
      "ApiKeyQuery": []
    }
  ],
  "paths": {
    "/v1/status": {
      "get": {
        "tags": [
          "Status"
        ],
        "operationId": "getStatus",
        "security": [],
        "summary": "Index and chain liveness",
        "description": "Whether the figures are advancing, and if not, whose fault that is. A halted chain and a broken indexer produce the same symptom, so they are reported separately: `chain.state` describes the chain, `degraded` describes whether this API is serving live or indexed-only data. Poll this before trusting a timestamp elsewhere.",
        "responses": {
          "200": {
            "description": "Current status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          }
        }
      }
    },
    "/v1/keys": {
      "post": {
        "tags": [
          "Keys"
        ],
        "operationId": "createKey",
        "security": [],
        "summary": "Mint a free API key",
        "description": "Returns a `sbd_…` key. Send it as the `X-API-Key` header on every other /v1 request. Capped at 5 keys per hour per IP.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string",
                    "maxLength": 60,
                    "description": "Optional label to identify the key later."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Key created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NewKey"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/KeyLimit"
          }
        }
      }
    },
    "/v1/chain/uptime": {
      "get": {
        "tags": [
          "Status"
        ],
        "operationId": "getChainUptime",
        "summary": "Chain availability record",
        "description": "What the chain has done over time, folded from the log of state transitions.\n\n**Read `coveragePct` before `uptimePct`.** Uptime is a share of *observed* time, not of\nthe window: hours when Stabledesk was not running, or was being refused by the RPC,\nare not counted as chain uptime, because they are not evidence about the chain at all.\nA 99.9% uptime over 4% coverage is a statement about four percent of the period.\n`uptimePct` is `null` when nothing at all was observed, never 100.\n\nFor the same reason `unauthorized` time — our credentials being rejected — is booked as\nunobserved rather than as downtime. The chain may well have been producing blocks\nthroughout. `unreachable` is counted as down, but our own host losing connectivity is\nindistinguishable from the chain going dark, so the full `byState` breakdown is returned\nand a consumer who reads that boundary differently can recompute from it.\n\nCheck `recordBegan` before trusting a long window: a 30-day figure from a log that\nstarted a week ago is a seven-day figure.",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365,
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Availability totals and recent episodes.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChainUptime"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/network": {
      "get": {
        "tags": [
          "Network"
        ],
        "operationId": "getNetwork",
        "summary": "Chain throughput",
        "description": "Head block, block time, TPS and gas price. Live-only figures are `null` rather than stale whenever the chain cannot be read.",
        "responses": {
          "200": {
            "description": "Network state.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Network"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/network/fees": {
      "get": {
        "tags": [
          "Network"
        ],
        "operationId": "getFees",
        "summary": "Fee economics, in dollars",
        "description": "Gas on Arc testnet is paid in USDC, so every figure here is a dollar amount taken from transaction receipts. The headline metric is `perMillionMoved` — what it costs the network to move $1M of value. Exact fees only exist in receipts and fetching every block would bury the rate-limited public RPC, so blocks are sampled: `sample` reports how many blocks and transactions the extrapolation rests on, and an estimate is never presented as a measured total.",
        "responses": {
          "200": {
            "description": "Fee economics.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Fees"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/FeesUnavailable"
          }
        }
      }
    },
    "/v1/stablecoins": {
      "get": {
        "tags": [
          "Stablecoins"
        ],
        "operationId": "getStablecoins",
        "summary": "Supply and 24h summary, all tokens",
        "description": "`summary24h` sums everything since 24h ago, which on a young or freshly-restarted index is however much history exists rather than a full day. `coverage` states how much that actually is (`minutes`, and the first and last minute measured) and `windowEnd` states which instant the window ends at, so the figures can be labelled instead of assumed.",
        "responses": {
          "200": {
            "description": "Supply and rolling 24h totals per token, with the coverage behind them.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Stablecoins"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/stablecoins/history": {
      "get": {
        "tags": [
          "Stablecoins"
        ],
        "operationId": "getStablecoinHistory",
        "summary": "Volume / mint / burn time series",
        "description": "Bucketed series. `windowEnd` states which instant the range ends at — on a frozen chain that is the last indexed minute, not now.",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "ALL",
                "USDC",
                "EURC",
                "USYC",
                "USDT",
                "QCAD",
                "MXNB",
                "cNGN",
                "ZARU"
              ],
              "default": "ALL"
            }
          },
          {
            "name": "range",
            "in": "query",
            "required": false,
            "description": "Bucket sizes: `1h` → 60s, `24h` → 900s, `7d` → 3600s, `30d` → 86400s, `90d` → 86400s, `1y` → 86400s, `all` → 86400s.",
            "schema": {
              "type": "string",
              "enum": [
                "1h",
                "24h",
                "7d",
                "30d",
                "90d",
                "1y",
                "all"
              ],
              "default": "24h"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Time series.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/History"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/stablecoins/{token}": {
      "get": {
        "tags": [
          "Stablecoins"
        ],
        "operationId": "getStablecoin",
        "summary": "Per-token detail",
        "description": "Supply, dominance, velocity, 24h summary, net issuance and the transfer-size distribution.",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "USDC",
                "EURC",
                "USYC",
                "USDT",
                "QCAD",
                "MXNB",
                "cNGN",
                "ZARU"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Token detail.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenDetail"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/protocols": {
      "get": {
        "tags": [
          "Ecosystem"
        ],
        "operationId": "listProtocols",
        "summary": "Protocol registry with measured TVL",
        "description": "Every known protocol with its TVL, flow, status and official links. TVL is measured as stablecoin balances held by contracts, which needs no per-protocol adapter on a chain where value is denominated in USDC.",
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "Set to `csv` to receive a CSV file instead of JSON.",
            "schema": {
              "type": "string",
              "enum": [
                "csv"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Registry joined to measured TVL.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Protocols"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/protocols/unnamed": {
      "get": {
        "tags": [
          "Ecosystem"
        ],
        "operationId": "listUnnamedContracts",
        "summary": "Contracts holding balances nobody has claimed",
        "description": "Counted in the chain total and listed separately as unattributed. Hiding them would understate the chain; assigning them to a plausible protocol would invent data.",
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "Set to `csv` to receive a CSV file instead of JSON.",
            "schema": {
              "type": "string",
              "enum": [
                "csv"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Unattributed contracts.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnnamedContracts"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/protocols/{id}": {
      "get": {
        "tags": [
          "Ecosystem"
        ],
        "operationId": "getProtocol",
        "summary": "Protocol detail",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Registry id, as listed by `GET /v1/protocols`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Protocol detail, with per-contract balances and recent flow.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProtocolDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/tvl": {
      "get": {
        "tags": [
          "Ecosystem"
        ],
        "operationId": "getTvl",
        "summary": "Total value locked, chain-wide",
        "responses": {
          "200": {
            "description": "TVL totals, per token and per protocol.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tvl"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/tvl/history": {
      "get": {
        "tags": [
          "Ecosystem"
        ],
        "operationId": "getTvlHistory",
        "summary": "Daily TVL series",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 180,
              "default": 30
            }
          },
          {
            "name": "protocol",
            "in": "query",
            "required": false,
            "description": "Registry id, or `*` for the chain total.",
            "schema": {
              "type": "string",
              "default": "*"
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "Set to `csv` to receive a CSV file instead of JSON.",
            "schema": {
              "type": "string",
              "enum": [
                "csv"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Daily TVL series.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TvlHistory"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/rankings": {
      "get": {
        "tags": [
          "Ecosystem"
        ],
        "operationId": "getRankings",
        "summary": "Daily standings and digest",
        "description": "Daily protocol standings plus a ready-to-post text digest. Movements below the reporting threshold are omitted — a protocol drifting a fraction of a percent is not news.",
        "responses": {
          "200": {
            "description": "Daily rankings.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Rankings"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/search": {
      "get": {
        "tags": [
          "Ecosystem"
        ],
        "operationId": "search",
        "summary": "Search protocols, tokens and addresses",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Query, minimum 2 characters.",
            "schema": {
              "type": "string",
              "minLength": 2
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 25,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matches, grouped by kind.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResults"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/addresses/top": {
      "get": {
        "tags": [
          "Addresses"
        ],
        "operationId": "getTopAddresses",
        "summary": "Top addresses by volume",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ranked addresses.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TopAddresses"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/addresses/filtered": {
      "get": {
        "tags": [
          "Addresses"
        ],
        "operationId": "getFilteredAddresses",
        "summary": "Addresses excluded from adjusted volume",
        "description": "The address-level noise filter, published so it can be disagreed with rather than trusted. Addresses whose activity rate exceeds the thresholds are treated as infrastructure. A transfer is dropped from adjusted volume only when **both** of its ends are flagged — Visa/Allium drop on either end; the departure is deliberate and the reason is measured and stated on /methodology.",
        "responses": {
          "200": {
            "description": "Flagged addresses and the thresholds that flagged them.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FilteredAddresses"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/address/{address}": {
      "get": {
        "tags": [
          "Addresses"
        ],
        "operationId": "getAddress",
        "summary": "Per-address activity",
        "parameters": [
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^0x[0-9a-fA-F]{40}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Address totals over the retained window, plus recent transfers.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddressDetail"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/transfers/largest": {
      "get": {
        "tags": [
          "Addresses"
        ],
        "operationId": "getLargestTransfers",
        "summary": "Largest transfers over a stated window",
        "description": "The largest transfers of the last `days` days, from the retained per-day set (top 100 per day per token, kept 180 days). Previously read from the rolling raw-transfer table, which is row-capped — so at real throughput \"largest transfers\" meant \"largest of the last couple of minutes\", with nothing in the response saying so. The window is now an argument and it is echoed back.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Window in days, 1–180. Defaults to 7.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 180,
              "default": 7
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Largest transfers.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LargestTransfers"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/alerts": {
      "get": {
        "tags": [
          "Alerts"
        ],
        "operationId": "listAlerts",
        "summary": "List your alerts",
        "responses": {
          "200": {
            "description": "Alerts owned by this key.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlertList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      },
      "post": {
        "tags": [
          "Alerts"
        ],
        "operationId": "createAlert",
        "summary": "Create a webhook alert",
        "description": "Fires at most once per minute when a matching transfer is indexed. Free keys may hold 1, Pro keys 20.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AlertInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Alert created.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlertCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/AlertLimit"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/alerts/{id}": {
      "delete": {
        "tags": [
          "Alerts"
        ],
        "operationId": "deleteAlert",
        "summary": "Delete an alert",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    },
    "/v1/billing/order": {
      "get": {
        "tags": [
          "Billing"
        ],
        "operationId": "getOrder",
        "summary": "Latest order and current tier",
        "responses": {
          "200": {
            "description": "Latest order, tier and expiry.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      },
      "post": {
        "tags": [
          "Billing"
        ],
        "operationId": "createOrder",
        "summary": "Open a Pro order",
        "description": "Returns an address and an exact USDC amount. Send that amount and the key upgrades automatically once the payment is detected — no account, no card, no confirmation step. The amount is uniquified per order, so it must be sent exactly.",
        "responses": {
          "201": {
            "description": "Order opened.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/BillingDisabled"
          },
          "409": {
            "$ref": "#/components/responses/AlreadyPro"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Indexing"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "The preferred form. Get a key from `POST /v1/keys`."
      },
      "ApiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "key",
        "description": "Accepted for contexts that cannot set headers. Prefer the header: query strings end up in logs."
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Requests allowed per minute for this key's tier.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Requests left in the current minute.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid API key (`missing_api_key`, `invalid_api_key`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded (`rate_limited`). Free: 60 req/min, Pro: 600 req/min.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds to wait."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "KeyLimit": {
        "description": "Too many keys minted from this IP this hour (`key_limit_reached`). Reuse an existing key.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Indexing": {
        "description": "The index is still warming up and has nothing honest to serve yet (`indexing`). Retry shortly.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "FeesUnavailable": {
        "description": "Fee sampling has not collected a sample yet (`no_fee_samples`). Reported as absent rather than as zero.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BadRequest": {
        "description": "Invalid parameter (`bad_token`, `bad_address`, `bad_id`, `bad_min_amount`, `webhook_required`, `webhook_blocked`).",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "No such resource (`not_found`).",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "AlertLimit": {
        "description": "This key already holds its tier's maximum alerts (`alert_limit_reached`).",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BillingDisabled": {
        "description": "Pro billing is not open yet (`billing_disabled`).",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "AlreadyPro": {
        "description": "This key is already Pro (`already_pro`).",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Stable machine-readable code. Branch on this, not on `hint`."
          },
          "hint": {
            "type": "string",
            "description": "Human-readable remedy. Wording may change."
          }
        }
      },
      "ChainState": {
        "type": "object",
        "description": "What the chain itself is doing, reported separately from whether this API is healthy.",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "live",
              "halted",
              "unauthorized",
              "unreachable",
              "unknown"
            ],
            "description": "`live` — the head is advancing. `halted` — the RPC answers but the head has not moved. `unauthorized` — every endpoint answered and refused our credentials: our configuration to fix, not an outage. `unreachable` — nobody answered. `unknown` — not yet polled."
          },
          "head": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Last block seen."
          },
          "stalledMs": {
            "type": [
              "number",
              "null"
            ],
            "description": "How long the head has been frozen. Null while live, so nothing renders \"stalled for 0s\"."
          },
          "lastContactMs": {
            "type": [
              "number",
              "null"
            ],
            "description": "Time since an endpoint last answered."
          },
          "lastError": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Status": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "The index has data to serve."
          },
          "degraded": {
            "type": "boolean",
            "description": "True when the snapshot was rebuilt from stored history alone because the chain could not be read. Indexed figures are still served, labelled with when they were measured."
          },
          "chain": {
            "$ref": "#/components/schemas/ChainState"
          },
          "index": {
            "$ref": "#/components/schemas/IndexProgress"
          },
          "chainId": {
            "type": "integer",
            "examples": [
              5042002
            ]
          },
          "network": {
            "type": "string",
            "examples": [
              "testnet"
            ]
          },
          "windowEnd": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The instant every rolling window ends at (ms) — the newest measured minute."
          },
          "clockSkewSec": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How far the newest measured minute sits behind the wall clock. Near zero in normal operation. Bucket minutes are keyed by chain time, and every rolling window is anchored to it, so a large value here is what explains a 24h figure that looks impossibly quiet."
          },
          "block": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Last indexed block *as of the newest snapshot*. Frozen while a long catch-up runs — prefer `index.checkpoint`, which is read live."
          },
          "indexLag": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Blocks between the chain head and the last indexed block, from the snapshot. Frozen during a catch-up; `index.behind` is the live equivalent."
          },
          "updatedAt": {
            "type": [
              "number",
              "null"
            ],
            "description": "When this snapshot was assembled (ms)."
          },
          "dataAt": {
            "type": [
              "number",
              "null"
            ],
            "description": "When the newest indexed data was actually recorded (ms). On a frozen chain this and `updatedAt` are hours apart, and this is the one that says how stale the figures are."
          },
          "billingEnabled": {
            "type": "boolean"
          }
        }
      },
      "IndexProgress": {
        "type": "object",
        "description": "How far the indexer is from the head, read live rather than from the snapshot.\n\nEvery other figure in this response is assembled when a tick *completes*, so while the\nindexer replays a long stretch of history — after a restart against an old database, or\na chain returning from a multi-day outage — they all freeze, and the reported lag grows\nmore wrong the more progress is made. These four fields keep moving. Poll twice and the\nchange in `behind` gives you a rate, which is why no ETA is invented here.",
        "properties": {
          "checkpoint": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Last block written to storage."
          },
          "head": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Chain head as last observed."
          },
          "behind": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Blocks between the two. Shrinking means progress; unchanged across several polls means stuck."
          },
          "catchingUp": {
            "type": "boolean",
            "description": "Further behind than a single indexing pass can close, i.e. replaying history rather than merely trailing the head."
          }
        }
      },
      "NewKey": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "examples": [
              "sbd_0123456789abcdef0123456789abcdef"
            ]
          },
          "tier": {
            "type": "string",
            "enum": [
              "free",
              "pro"
            ]
          },
          "rpm": {
            "type": "integer",
            "examples": [
              60
            ]
          },
          "docs": {
            "type": "string"
          },
          "note": {
            "type": "string"
          }
        }
      },
      "Network": {
        "type": "object",
        "properties": {
          "block": {
            "type": [
              "integer",
              "null"
            ]
          },
          "blockTimeMs": {
            "type": [
              "number",
              "null"
            ],
            "description": "Mean block time over the sampled headers."
          },
          "tps": {
            "type": [
              "number",
              "null"
            ],
            "description": "Transactions per second."
          },
          "gasGwei": {
            "type": [
              "number",
              "null"
            ],
            "description": "Gas price. Absent rather than stale when the chain cannot be read."
          },
          "costPerTransferUsdc": {
            "type": [
              "number",
              "null"
            ],
            "description": "Cost of a 21k-gas transfer, in USDC."
          },
          "txPerDay": {
            "type": [
              "number",
              "null"
            ],
            "description": "Extrapolated from the sampled rate."
          },
          "indexLag": {
            "type": [
              "integer",
              "null"
            ]
          },
          "updatedAt": {
            "type": [
              "number",
              "null"
            ],
            "description": "ms"
          }
        }
      },
      "Fees": {
        "type": "object",
        "properties": {
          "currency": {
            "type": "string",
            "const": "USDC"
          },
          "perTransaction": {
            "type": [
              "number",
              "null"
            ],
            "description": "Mean fee per transaction, in USDC."
          },
          "perBlock": {
            "type": [
              "number",
              "null"
            ],
            "description": "Mean fee per block."
          },
          "perDay": {
            "type": [
              "number",
              "null"
            ],
            "description": "Extrapolated daily total."
          },
          "perMillionMoved": {
            "type": [
              "number",
              "null"
            ],
            "description": "The headline metric: what it costs the network to move $1M of real volume."
          },
          "inWindow": {
            "type": [
              "number",
              "null"
            ],
            "description": "Total fees over the effective window."
          },
          "windowSec": {
            "type": [
              "number",
              "null"
            ],
            "description": "Length of that window — the shorter of 24h and however much history is held, so a young index cannot pair a full day of fees with an hour of volume."
          },
          "avgGasPerTx": {
            "type": [
              "number",
              "null"
            ],
            "description": "Mean gas used per transaction."
          },
          "gasGwei": {
            "type": [
              "number",
              "null"
            ],
            "description": "Gas price at the last poll."
          },
          "sample": {
            "type": "object",
            "description": "What the extrapolation rests on. Exact for the blocks sampled; every derived rate carries this so an estimate is never read as a measured total.",
            "properties": {
              "blocks": {
                "type": "integer"
              },
              "transactions": {
                "type": "integer"
              },
              "coverage": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Sampled blocks ÷ blocks in the window, capped at 1."
              }
            }
          },
          "note": {
            "type": "string"
          },
          "updatedAt": {
            "type": [
              "number",
              "null"
            ],
            "description": "ms"
          }
        }
      },
      "TokenSummary": {
        "type": "object",
        "description": "The three volume measures, side by side. `volume` counts every Transfer event; `rvolume` is real (one largest transfer per transaction per token); `avolume` is adjusted (real, minus infrastructure-to-infrastructure). The top-level totals are face values added across every tracked token WITH NO currency conversion — dollars, euros and pesos in one sum. Kept for consumers already reading them; `byDenomination` inside this object carries the per-currency figures, and `byToken` the per-asset ones.",
        "properties": {
          "volume": {
            "type": "number"
          },
          "transfers": {
            "type": "integer"
          },
          "rvolume": {
            "type": "number"
          },
          "rtransfers": {
            "type": "integer"
          },
          "avolume": {
            "type": "number"
          },
          "atransfers": {
            "type": "integer"
          },
          "mint": {
            "type": "number"
          },
          "burn": {
            "type": "number"
          }
        }
      },
      "Summary24h": {
        "type": "object",
        "properties": {
          "byToken": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/TokenSummary"
            }
          },
          "volume": {
            "type": "number"
          },
          "transfers": {
            "type": "integer"
          },
          "rvolume": {
            "type": "number"
          },
          "rtransfers": {
            "type": "integer"
          },
          "avolume": {
            "type": "number"
          },
          "atransfers": {
            "type": "integer"
          }
        }
      },
      "TokenSupply": {
        "type": "object",
        "properties": {
          "supply": {
            "type": [
              "number",
              "null"
            ],
            "description": "Read with `totalSupply()`. Null — not zero — when the chain has never been reachable: zero is a measurement, null is the absence of one."
          },
          "denomination": {
            "type": [
              "string",
              "null"
            ],
            "description": "The currency this token is denominated in (ISO code), declared rather than inferred. Null when undeclared."
          },
          "dominance": {
            "type": [
              "number",
              "null"
            ],
            "description": "Share of the supply denominated in the same currency, 0–1 — not of a cross-currency sum."
          },
          "volShare": {
            "type": [
              "number",
              "null"
            ],
            "description": "Share of the real volume moved in the same currency, 0–1 — not of a cross-currency sum."
          },
          "velocity": {
            "type": [
              "number",
              "null"
            ],
            "description": "Real transfers per day ÷ supply."
          },
          "rvolume24h": {
            "type": "number"
          },
          "avolume24h": {
            "type": "number"
          }
        }
      },
      "Stablecoins": {
        "type": "object",
        "properties": {
          "supply": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/TokenSupply"
            },
            "description": "Keyed by symbol: USDC, EURC, USYC, USDT, QCAD, MXNB, cNGN, ZARU."
          },
          "totalSupply": {
            "type": [
              "number",
              "null"
            ],
            "description": "Face values added across every tracked token, with NO currency conversion — dollars and euros summed as if they were one unit. Kept for consumers already reading it; it is not a quantity of anything. Use byDenomination."
          },
          "byDenomination": {
            "type": "object",
            "description": "Supply grouped by the currency it is denominated in, and never summed across groups. Converting would need an exchange rate, and this API has no price feed and no oracle anywhere in it — the same rule that makes the fee figures directly measured.",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "supply": {
                  "type": "number",
                  "description": "Total supply in this denomination, in its own currency units."
                },
                "tokens": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "The symbols that make it up."
                }
              }
            }
          },
          "undeclaredSupply": {
            "type": [
              "object",
              "null"
            ],
            "description": "Tracked symbols whose denomination has not been declared, with their supply. Null in the ordinary case. Their supply is in no currency total — a denomination we have not declared is not evidence of zero dollars, so it is stated rather than dropped."
          },
          "summary24h": {
            "$ref": "#/components/schemas/Summary24h"
          },
          "coverage": {
            "type": "object",
            "description": "How much history actually backs `summary24h`. It sums everything since 24h ago, so on a young or freshly-restarted index the window is shorter than the name — read `minutes` before labelling these figures as a day.",
            "properties": {
              "fromMinute": {
                "type": "integer",
                "nullable": true,
                "description": "First measured minute, unix seconds."
              },
              "toMinute": {
                "type": "integer",
                "nullable": true,
                "description": "Last measured minute, unix seconds."
              },
              "minutes": {
                "type": "integer",
                "description": "Minutes of history held. 1440 is a full day."
              }
            }
          },
          "windowEnd": {
            "type": [
              "number",
              "null"
            ],
            "description": "ms — the instant the 24h window ends at, in chain time."
          },
          "updatedAt": {
            "type": [
              "number",
              "null"
            ],
            "description": "ms"
          }
        }
      },
      "History": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string"
          },
          "group": {
            "type": "integer",
            "description": "Bucket size in seconds."
          },
          "windowEnd": {
            "type": "integer",
            "description": "The instant the range ends at (ms) — the newest measured minute, which is chain time. Label the series with this rather than assuming it runs to the moment of the call."
          },
          "source": {
            "type": "string",
            "enum": [
              "minute",
              "daily"
            ],
            "description": "Which table answered. Per-minute aggregates are a rolling 7 days; anything longer is served from the per-day rollup, which is kept indefinitely."
          },
          "since": {
            "type": "integer",
            "description": "The instant the range asked for, unix seconds. 0 on `range=all`."
          },
          "recordBegan": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Where the answering table's history starts, unix seconds. When this is later than `since`, the series is bounded by how long the record has existed rather than by the range — a 90-day range drawn from a rollup that began last week is a one-week series wearing a 90-day label, and this is how you can tell."
          },
          "series": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "t": {
                  "type": "integer",
                  "description": "Bucket start, unix seconds — a minute boundary or a day boundary depending on `source`."
                },
                "volume": {
                  "type": "number"
                },
                "cnt": {
                  "type": "integer"
                },
                "rvolume": {
                  "type": "number"
                },
                "avolume": {
                  "type": "number"
                },
                "mint": {
                  "type": "number"
                },
                "burn": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "TokenDetail": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string"
          },
          "supply": {
            "$ref": "#/components/schemas/TokenSupply"
          },
          "summary24h": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TokenSummary"
              },
              {
                "type": "null"
              }
            ]
          },
          "netIssuance24h": {
            "type": [
              "number",
              "null"
            ],
            "description": "mint − burn over the rolling 24h."
          },
          "distribution": {
            "type": "object",
            "description": "Transfer-size histogram over the retained transfer window, published with the window it covers.",
            "properties": {
              "window": {
                "$ref": "#/components/schemas/TransferWindow"
              },
              "total": {
                "type": "integer"
              },
              "brackets": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "label": {
                      "type": "string",
                      "examples": [
                        "<100",
                        "100–1K",
                        "1K–10K",
                        "10K–100K",
                        "100K–1M",
                        "1M+"
                      ]
                    },
                    "min": {
                      "type": "number"
                    },
                    "max": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Null on the open-ended top bracket."
                    },
                    "count": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "updatedAt": {
            "type": [
              "number",
              "null"
            ],
            "description": "ms"
          }
        }
      },
      "Protocol": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "category": {
            "type": "string",
            "enum": [
              "issuer",
              "payments",
              "dex",
              "lending",
              "yield",
              "rwa",
              "bridge",
              "custody",
              "oracle",
              "infra"
            ]
          },
          "tvl": {
            "type": "number",
            "description": "Stablecoin balances held by this protocol's contracts."
          },
          "tvlByToken": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            }
          },
          "contracts": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "additionalProperties": true
      },
      "Protocols": {
        "type": "object",
        "properties": {
          "protocols": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Protocol"
            }
          },
          "registry": {
            "type": "object",
            "description": "Registry counts. `verified` separates entries confirmed against the chain from ones merely listed.",
            "properties": {
              "total": {
                "type": "integer"
              },
              "verified": {
                "type": "integer"
              },
              "unverified": {
                "type": "integer"
              },
              "contracts": {
                "type": "integer"
              },
              "byCategory": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                }
              }
            },
            "additionalProperties": true
          },
          "categories": {
            "type": "object",
            "description": "Category vocabulary, keyed by the id used in `Protocol.category`.",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string"
                },
                "desc": {
                  "type": "string"
                }
              }
            }
          },
          "totals": {
            "$ref": "#/components/schemas/TvlTotals"
          },
          "method": {
            "type": "string"
          },
          "updatedAt": {
            "type": [
              "number",
              "null"
            ],
            "description": "When the balance scan last completed (ms)."
          }
        }
      },
      "TvlTotals": {
        "type": "object",
        "properties": {
          "tvl": {
            "type": "number",
            "description": "Chain-wide total, attributed and unattributed together."
          },
          "byToken": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            }
          },
          "attributed": {
            "type": "number",
            "description": "Held by contracts a registry entry claims."
          },
          "unattributed": {
            "type": "number",
            "description": "Held by contracts nobody has named. Counted in `tvl` and reported separately — hiding it would understate the chain, and assigning it to a plausible protocol would invent data."
          },
          "attributedShare": {
            "type": "number",
            "description": "How much of the locked value the registry can actually name, 0–1. Reported next to the total, never instead of it."
          },
          "holders": {
            "type": "integer",
            "description": "Distinct contracts holding a balance."
          },
          "coverage": {
            "type": "object",
            "description": "How much of the chain the total covers. One pass reads balanceOf for every tracked asset against every target, so the target count is capped; targets are ordered by last-read balance, then by volume moved. Past the cap the total is a lower bound, and `atCap` is how you know.",
            "properties": {
              "scanned": {
                "type": "integer",
                "description": "Contracts read in the most recent pass."
              },
              "knownContracts": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "How many contracts exist to scan."
              },
              "cap": {
                "type": "integer",
                "description": "Ceiling on a single pass, not on what is ever measured."
              },
              "atCap": {
                "type": "boolean",
                "description": "True when one pass cannot cover every known contract. Since the scan rotates, this no longer means the remainder is never read — it means the total mixes readings from more than one pass. See cycleLength and oldestReadingMs."
              },
              "alwaysTop": {
                "type": "integer",
                "description": "Highest-value contracts re-read on every pass."
              },
              "rotatingSlice": {
                "type": "integer",
                "description": "Contracts taken from the rotation each pass."
              },
              "lanes": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "The rotation runs in two lanes with different staleness guarantees. fast: contracts holding a balance, never scanned, or active this week — the rows the total is made of. slow: contracts verified empty and quiet, re-confirmed on a longer cycle. Activity promotes a contract back to the fast lane on the next pass.",
                "properties": {
                  "fast": {
                    "type": "object",
                    "properties": {
                      "universe": {
                        "type": "integer"
                      },
                      "slice": {
                        "type": "integer"
                      },
                      "cycleLength": {
                        "type": "integer"
                      }
                    }
                  },
                  "slow": {
                    "type": "object",
                    "properties": {
                      "universe": {
                        "type": "integer"
                      },
                      "slice": {
                        "type": "integer"
                      },
                      "cycleLength": {
                        "type": "integer"
                      }
                    }
                  }
                }
              },
              "cycleLength": {
                "type": "integer",
                "description": "Passes to visit every known contract once — the slower lane decides it. See lanes for the split that means something."
              },
              "oldestReadingMs": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "When the oldest balance still counted in the total was read (ms). The honest cost of rotating: a figure mixing fresh and older readings has to say so."
              },
              "order": {
                "type": "string",
                "description": "How the per-pass budget is spent."
              }
            }
          },
          "warning": {
            "type": "string",
            "description": "Present only while coverage is truncated."
          }
        }
      },
      "UnnamedContracts": {
        "type": "object",
        "properties": {
          "candidates": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "address": {
                  "type": "string"
                },
                "tvl": {
                  "type": "number"
                },
                "byToken": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "number"
                  }
                },
                "selfName": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "What the contract answers to name(). Read from the contract, so it is a fact about the contract — not an attribution to an operator. These rows stay unattributed until a registry entry claims them."
                },
                "selfSymbol": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "What the contract answers to symbol()."
                },
                "kind": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Classification derived by the entity deriver, when it has looked at this address."
                },
                "codeSize": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Bytecode length. Identical sizes across addresses usually mean one contract deployed several times."
                }
              },
              "additionalProperties": true
            }
          },
          "unattributed": {
            "type": "number"
          },
          "note": {
            "type": "string"
          },
          "updatedAt": {
            "type": [
              "number",
              "null"
            ],
            "description": "ms"
          }
        }
      },
      "ProtocolDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "tvl": {
            "type": "number"
          },
          "contracts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "address": {
                  "type": "string"
                },
                "label": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "tvl": {
                  "type": "number"
                },
                "byToken": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "number"
                  }
                },
                "windowVolume": {
                  "type": "number"
                },
                "windowTransfers": {
                  "type": "integer"
                },
                "lastBlock": {
                  "type": [
                    "integer",
                    "null"
                  ]
                }
              }
            }
          },
          "recent": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Transfer"
            }
          }
        },
        "additionalProperties": true
      },
      "Tvl": {
        "allOf": [
          {
            "$ref": "#/components/schemas/TvlTotals"
          },
          {
            "type": "object",
            "properties": {
              "method": {
                "type": "string"
              },
              "series": {
                "$ref": "#/components/schemas/TvlSeries"
              },
              "protocols": {
                "type": "array",
                "description": "Protocols holding a non-zero balance.",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "tvl": {
                      "type": "number"
                    }
                  }
                }
              },
              "updatedAt": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "ms"
              }
            }
          }
        ]
      },
      "TvlSeries": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "day": {
              "type": "integer",
              "description": "Start of the day, unix seconds (a multiple of 86400)."
            },
            "tvl": {
              "type": "number"
            }
          }
        }
      },
      "TvlHistory": {
        "type": "object",
        "properties": {
          "protocol": {
            "type": "string"
          },
          "days": {
            "type": "integer"
          },
          "series": {
            "$ref": "#/components/schemas/TvlSeries"
          }
        }
      },
      "ChainUptime": {
        "type": "object",
        "description": "Availability over a window, with the observed share it was computed from.",
        "properties": {
          "window": {
            "type": "object",
            "properties": {
              "from": {
                "type": "integer"
              },
              "to": {
                "type": "integer"
              },
              "days": {
                "type": "integer"
              }
            }
          },
          "recordBegan": {
            "type": [
              "integer",
              "null"
            ],
            "description": "When the transition log starts (unix ms). A window reaching before this is only partly covered."
          },
          "windowMs": {
            "type": "integer"
          },
          "upMs": {
            "type": "integer",
            "description": "Observed time the head was advancing."
          },
          "downMs": {
            "type": "integer",
            "description": "Observed time the chain was halted or unreachable."
          },
          "observedMs": {
            "type": "integer",
            "description": "upMs + downMs — the only time any percentage here is computed over."
          },
          "unobservedMs": {
            "type": "integer",
            "description": "Time we were not watching, including while our own credentials were refused."
          },
          "byState": {
            "type": "object",
            "description": "Milliseconds per state, so the up/down boundary can be redrawn by the consumer.",
            "properties": {
              "live": {
                "type": "integer"
              },
              "halted": {
                "type": "integer"
              },
              "unreachable": {
                "type": "integer"
              },
              "unauthorized": {
                "type": "integer"
              },
              "unobserved": {
                "type": "integer"
              },
              "unknown": {
                "type": "integer"
              }
            }
          },
          "uptimePct": {
            "type": [
              "number",
              "null"
            ],
            "description": "upMs as a share of observedMs. Null when nothing was observed — never 100."
          },
          "coveragePct": {
            "type": [
              "number",
              "null"
            ],
            "description": "observedMs as a share of windowMs. Read this first."
          },
          "seenThrough": {
            "type": [
              "number",
              "null"
            ],
            "description": "How far the record extends (unix ms). Past this, nothing is claimed."
          },
          "incidents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChainIncident"
            }
          }
        }
      },
      "ChainIncident": {
        "type": "object",
        "description": "One non-live episode, most recent first.",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "halted",
              "unreachable",
              "unauthorized",
              "unobserved",
              "unknown"
            ]
          },
          "verdict": {
            "type": "string",
            "enum": [
              "down",
              "unobserved"
            ],
            "description": "Whether this episode counts against the chain."
          },
          "blame": {
            "type": "string",
            "enum": [
              "chain",
              "stabledesk",
              "unknown"
            ],
            "description": "Whose failure it was. `stabledesk` covers our own outages and rejected credentials, published rather than hidden."
          },
          "from": {
            "type": "integer"
          },
          "to": {
            "type": "integer"
          },
          "ms": {
            "type": "integer"
          },
          "ongoing": {
            "type": "boolean",
            "description": "The episode had not ended by the edge of what we observed."
          },
          "head": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Rankings": {
        "type": "object",
        "properties": {
          "digest": {
            "type": "string",
            "description": "Ready-to-post text summary of the day's movements."
          }
        },
        "additionalProperties": true
      },
      "SearchResults": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string"
          },
          "protocols": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "tokens": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "addresses": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "TopAddresses": {
        "type": "object",
        "properties": {
          "top": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "address": {
                  "type": "string"
                },
                "transfers": {
                  "type": "integer"
                },
                "volume": {
                  "type": "number"
                },
                "label": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Known-entity name, or null when unidentified."
                }
              }
            }
          }
        }
      },
      "FilteredAddresses": {
        "type": "object",
        "properties": {
          "flagged": {
            "type": "integer",
            "description": "Addresses actually in the flag set."
          },
          "qualifying": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How many addresses the published thresholds select, before the cap is applied. Equal to flagged in the ordinary case; larger when the cap is binding."
          },
          "cap": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Ceiling on the size of the flag set."
          },
          "atCap": {
            "type": "boolean",
            "description": "True when qualifying exceeds cap. Which addresses are flagged is then decided by the cap and a volume ordering rather than by the published thresholds, so adjusted volume is a lower bound on what the rule would exclude. Raw and real volume are unaffected."
          },
          "warning": {
            "type": "string",
            "description": "Present only while atCap is true, spelling out the consequence in prose."
          },
          "thresholds": {
            "type": "object",
            "description": "Per-day rates. Visa/Allium exclude an address exceeding 1,000 transactions or $10M of volume in a month; these are the same limits expressed daily, because the retained window is rolling rather than a calendar month.",
            "properties": {
              "transfersPerDay": {
                "type": "number"
              },
              "volumePerDay": {
                "type": "number"
              }
            }
          },
          "window": {
            "type": "object",
            "description": "How the per-day rates above become an absolute limit. Each address is measured over its *own* observed span — the first block it was seen in to the last — so there is no single window here: `windowDays`, `maxTransfers` and `maxVolume` ride on each address below, and those are the limits it was actually judged against. `blockMs` is the only input that converts a block span to days.",
            "properties": {
              "perAddress": {
                "type": "boolean",
                "description": "Always true. Present so a client written against the old chain-wide window fails loudly rather than reading undefined."
              },
              "minDays": {
                "type": "number",
                "description": "Floor on an address's window. Without it, an address first seen inside one block has a near-zero span and any activity is an infinite rate."
              },
              "blockMs": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Measured average block time, used to convert a block span into days."
              }
            }
          },
          "excludedVolume24h": {
            "type": [
              "number",
              "null"
            ],
            "description": "Real volume dropped from adjusted, over 24h."
          },
          "excludedShare": {
            "type": [
              "number",
              "null"
            ],
            "description": "That volume as a share of real volume, 0–1."
          },
          "addresses": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "address": {
                  "type": "string"
                },
                "transfers": {
                  "type": "integer"
                },
                "volume": {
                  "type": "number"
                },
                "label": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "windowDays": {
                  "type": "number",
                  "description": "This address's own observed span, in days, floored at one."
                },
                "maxTransfers": {
                  "type": "number",
                  "description": "The transfer limit derived from that span — what it was actually compared against."
                },
                "maxVolume": {
                  "type": "number",
                  "description": "The volume limit derived from that span."
                },
                "firstBlock": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "First block this address was seen in. Null on rows written before the span was recorded; those fall back to the one-day floor."
                },
                "lastBlock": {
                  "type": "integer"
                },
                "reason": {
                  "type": "string",
                  "enum": [
                    "volume",
                    "frequency"
                  ],
                  "description": "An address can breach both limits; this names the one it breaches hardest, relatively."
                }
              },
              "additionalProperties": true
            }
          },
          "note": {
            "type": "string"
          },
          "updatedAt": {
            "type": [
              "number",
              "null"
            ],
            "description": "ms"
          }
        }
      },
      "Transfer": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string"
          },
          "frm": {
            "type": "string",
            "description": "Sender. The zero address means a mint."
          },
          "too": {
            "type": "string",
            "description": "Recipient. The zero address means a burn."
          },
          "amount": {
            "type": "number"
          },
          "block": {
            "type": "integer"
          },
          "ts": {
            "type": "integer",
            "description": "Unix seconds, interpolated between the measured timestamps of its indexed range's first and last block."
          }
        }
      },
      "TransferWindow": {
        "type": "object",
        "description": "The span of the retained raw-transfer table — what the size distribution and the `recent` lists are describing. It is bounded by both a 24h clock and a row cap, so its actual length depends on how busy the chain is, which is why it is measured and published rather than stated once.",
        "properties": {
          "rows": {
            "type": "integer"
          },
          "fromTs": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Oldest retained transfer, unix seconds."
          },
          "toTs": {
            "type": [
              "integer",
              "null"
            ]
          },
          "spanSec": {
            "type": "integer"
          },
          "cap": {
            "type": "integer",
            "description": "Row ceiling."
          },
          "atCap": {
            "type": "boolean",
            "description": "True when the row cap rather than the 24h clock is deciding how far back the table reaches."
          }
        }
      },
      "LargestTransfers": {
        "type": "object",
        "description": "Largest transfers over an explicit window, from the retained per-day set rather than from the rolling raw table.",
        "properties": {
          "days": {
            "type": "integer",
            "description": "The window this answered over."
          },
          "window": {
            "type": "object",
            "properties": {
              "from": {
                "type": "integer"
              },
              "to": {
                "type": "integer"
              }
            }
          },
          "note": {
            "type": "string"
          },
          "transfers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Transfer"
            }
          }
        }
      },
      "AddressDetail": {
        "type": "object",
        "properties": {
          "address": {
            "type": "string"
          },
          "transfers": {
            "type": "integer"
          },
          "volume": {
            "type": "number"
          },
          "last_block": {
            "type": "integer"
          },
          "first_block": {
            "type": [
              "integer",
              "null"
            ],
            "description": "First block this address was seen in — the start of the span its activity rate is measured over."
          },
          "label": {
            "type": [
              "string",
              "null"
            ]
          },
          "transferWindow": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TransferWindow"
              },
              {
                "type": "null"
              }
            ]
          },
          "recent": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Transfer"
            },
            "description": "Up to 25 most recent transfers touching this address, within `transferWindow`."
          },
          "largest": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Transfer"
            },
            "description": "Largest transfers touching this address, from the retained per-day set — so they do not expire with the window above."
          }
        }
      },
      "AlertInput": {
        "type": "object",
        "required": [
          "webhook"
        ],
        "properties": {
          "webhook": {
            "type": "string",
            "format": "uri",
            "description": "Public https URL (Discord, Slack or Telegram). localhost and private IPs are rejected."
          },
          "token": {
            "type": "string",
            "enum": [
              "USDC",
              "EURC",
              "USYC",
              "USDT",
              "QCAD",
              "MXNB",
              "cNGN",
              "ZARU"
            ],
            "description": "Restrict to one token. Omit for all."
          },
          "address": {
            "type": "string",
            "pattern": "^0x[0-9a-fA-F]{40}$",
            "description": "Restrict to transfers touching this address. Omit for all."
          },
          "minAmount": {
            "type": "number",
            "minimum": 0,
            "default": 0
          }
        }
      },
      "AlertCreated": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "AlertList": {
        "type": "object",
        "properties": {
          "tier": {
            "type": "string",
            "enum": [
              "free",
              "pro"
            ]
          },
          "max": {
            "type": "integer"
          },
          "alerts": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "Order": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "amount": {
            "type": "string",
            "description": "Exact amount to send, uniquified per order so the payment can be matched. Send it exactly."
          },
          "currency": {
            "type": "string",
            "const": "USDC"
          },
          "chain": {
            "type": "string"
          },
          "chainId": {
            "type": "integer"
          },
          "tokenAddress": {
            "type": "string"
          },
          "payTo": {
            "type": "string"
          },
          "expiresInMinutes": {
            "type": "integer"
          },
          "note": {
            "type": "string"
          }
        }
      },
      "OrderStatus": {
        "type": "object",
        "properties": {
          "order": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "number"
                  },
                  "status": {
                    "type": "string"
                  },
                  "created": {
                    "type": "integer"
                  },
                  "paidAt": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "tier": {
            "type": "string",
            "enum": [
              "free",
              "pro"
            ]
          },
          "expiresAt": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      }
    }
  }
}