# GOVY Protocol — Full API Reference > Tokenized rolling US Treasury bill protocol — 1 GOVY token = 1 T-Bill, continuously rolling at maturity. ## What is GOVY GOVY is a tokenized treasury protocol that wraps US Treasury bills into ERC-20 tokens. Each GOVY token is backed 1:1 by a short-term T-Bill. When the underlying bill matures, the protocol atomically "rolls forward" into the next bill, maintaining continuous yield exposure without manual intervention. ## Key Concepts - **T-Rep (Treasury Representation):** Accrual factor starting at 1.0000 that increases over time as treasury yield accrues. The GOVY price = T-Rep x Treasury Price. - **Treasury Roll:** Automated cycle where the backing T-Bill transitions: upcoming -> current -> previous. Each roll can include a new T-Rep value and initial price. - **Multi-chain:** GOVY tokens exist on Ethereum mainnet, Sepolia testnet, Hedera (HTS), and Stellar/Soroban. ## Authentication All write and admin endpoints require an `X-API-Key` header. Keys have scoped permissions: | Scope | Access | |-------|--------| | `read` | Event history, SSE stream | | `write` | All read + treasury CRUD, prices, T-Rep, contracts, roll-forward | | `admin` | All write + key management, audit log, webhook management | The bootstrap key (set via `ADMIN_API_KEY` environment variable) has full admin scope. Additional scoped keys can be created via `POST /api/keys`. ## Response Format All responses use a standard envelope: **Success:** ```json { "data": { ... }, "meta": { "timestamp": "2025-12-01T09:00:00.000Z", "apiVersion": "1.0.0" } } ``` **Error:** ```json { "error": { "code": "VALIDATION_ERROR", "message": "Human-readable description" }, "meta": { "timestamp": "2025-12-01T09:00:00.000Z", "apiVersion": "1.0.0" } } ``` ## Rate Limits | Tier | Limit | Applies to | |------|-------|------------| | General | 200 req / 15 min | All endpoints | | Write | 30 req / 15 min | POST/PATCH/DELETE mutations | | Proxy | 50 req / 15 min | Treasury info proxy, supply, holders | Rate limit headers (`RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`) are included in responses. ## Contract Addresses - **Ethereum Mainnet:** `0xe1bD7Ca5Df4650DA4724AB118a882eD6bDA001EA` - **Sepolia Testnet:** `0x2f459Ad5DB7B73B4dcEB5AeEd5d5c27217F5bfa9` - **Hedera:** Configured per deployment (HTS token ID format `0.0.XXXXX`) - **Stellar:** Configured per deployment (Soroban contract, 56-char uppercase) --- ## Endpoint Reference ### GET /api/health **Auth:** None **Description:** Returns server health status. **Response fields:** - `data.status` (string): Always `"ok"` - `data.timestamp` (string): Current server time (ISO 8601) ### GET /api/openapi.json **Auth:** None **Description:** Returns the OpenAPI 3.1 specification document. ### GET /api/protocol-data **Auth:** None **Description:** Returns the full protocol state. Response is enriched with resolved treasury objects. **Response fields:** - `data.tRepHistory` (array): T-Rep entries `{ date, value }` - `data.treasuries` (array): Treasury definitions `{ id, name, isin, maturityDate, ... }` - `data.prices` (object): Price history keyed by treasury ID `{ [id]: [{ date, price }] }` - `data.treasuryRoll` (object): `{ previousId, currentId, upcomingId }` - `data.contracts` (object): `{ sepolia, mainnet, stellar, hedera }` - `data.previousTreasury` (object): Resolved treasury for previousId - `data.currentTreasury` (object): Resolved treasury for currentId - `data.upcomingTreasury` (object): Resolved treasury for upcomingId ### GET /api/price **Auth:** None **Description:** Returns the computed GOVY token price (T-Rep x treasury price). **Response fields:** - `data.govyPrice` (number): Computed price in USD, 4 decimal places - `data.treasuryPrice` (number): Latest treasury price per 100 face value - `data.tRep` (number): Latest T-Rep accrual factor - `data.currency` (string): Always `"USD"` - `data.decimals` (integer): Always `4` - `data.timestamp` (string): Computation time (ISO 8601) - `data.treasury` (object): `{ name, isin, maturityDate }` of current backing treasury ### GET /api/fetch-treasury-info **Auth:** None **Parameters:** - `isin` (query, required): ISIN identifier, format `^[A-Z]{2}[A-Z0-9]{9}[0-9]$` **Description:** Proxies US Treasury Fiscal Data API lookup by ISIN. **Response fields:** - `data.found` (boolean): Whether auction data was found - `data.data` (object, if found): `{ cusip, securityType, securityTerm, auctionDate, issueDate, maturityDate, highRate, announcementPdf, resultsPdf }` - `data.message` (string, if not found): Explanation ### GET /api/networks **Auth:** None **Description:** Lists supported blockchain networks with contract addresses and explorer URLs. **Response fields:** - `data` (array): Network entries - `network` (string): `"mainnet"`, `"sepolia"`, `"hedera"`, `"stellar"` - `type` (string): `"evm"`, `"hedera"`, or `"stellar"` - `contractAddress` (string): Token contract address (empty if not configured) - `explorer` (string): Block explorer base URL - `rpcUrl` (string, optional): Public RPC URL (EVM chains only) ### GET /api/supply **Auth:** None (proxy rate limit) **Description:** Returns total GOVY supply across all networks with per-network breakdown. Results are cached for 5 minutes. **Response fields:** - `data.totalSupply` (string): Aggregate supply across all networks (as string for large numbers) - `data.networks` (array): Per-network entries - `network` (string): Network name - `supply` (string): Supply on this network - `error` (string, optional): Error message if data could not be fetched - `data.timestamp` (string): Cache/fetch time (ISO 8601) ### GET /api/holders **Auth:** None (proxy rate limit) **Description:** Returns holder counts across all networks with per-network breakdown. Results are cached for 5 minutes. **Response fields:** - `data.totalHolders` (number): Aggregate holder count - `data.networks` (array): Per-network entries - `network` (string): Network name - `holders` (number): Holder count on this network - `error` (string, optional): Error message if data could not be fetched - `data.timestamp` (string): Cache/fetch time (ISO 8601) ### POST /api/auth/verify **Auth:** X-API-Key header **Description:** Validates the API key without side effects. **Response fields:** - `data.authenticated` (boolean): true when valid - `data.keyId` (string): Key identifier - `data.scopes` (array): Key scopes ### POST /api/treasuries **Auth:** write or admin scope **Request body:** `{ id, name, isin, maturityDate, chainAddresses?, cusip? }` **Validation:** - `id`: required, string - `name`: required, string, pattern `^[a-zA-Z0-9\s\-.]+$` - `isin`: required, string, pattern `^[A-Z]{2}[A-Z0-9]{9}[0-9]$` - `maturityDate`: required, string (YYYY-MM-DD) **Response:** Created treasury object (201) **Errors:** 400 (validation), 409 (duplicate ID) ### PATCH /api/treasuries/:id **Auth:** write or admin scope **Parameters:** `id` (path) — Treasury ID **Request body:** Partial treasury fields to update (id field cannot be changed) **Response:** Updated treasury object **Errors:** 404 (not found) ### DELETE /api/treasuries/:id **Auth:** write or admin scope **Parameters:** `id` (path) — Treasury ID **Description:** Removes treasury and its associated price history. **Response:** `{ deleted: true }` **Errors:** 404 (not found) ### POST /api/prices/:treasuryId **Auth:** write or admin scope **Parameters:** `treasuryId` (path) — Treasury ID **Request body:** `{ date: string, price: number }` **Response:** Created price entry (201) **Errors:** 404 (treasury not found), 400 (validation) ### POST /api/trep **Auth:** write or admin scope **Request body:** `{ date: string, value: number }` **Response:** Created T-Rep entry (201) **Errors:** 400 (validation) ### PATCH /api/contracts **Auth:** write or admin scope **Request body:** Partial `{ sepolia?, mainnet?, stellar?, hedera? }` — only provided fields updated **Validation:** EVM addresses must match `^0x[0-9a-fA-F]{40}$`, Hedera `^0\.0\.\d+$`, Stellar `^[A-Z0-9]{56}$` **Response:** Full contracts object with all fields ### POST /api/roll-forward **Auth:** write or admin scope **Request body:** `{ newTRep?: { date, value }, newPrice?: { date, price } }` **Description:** Executes atomic roll: upcoming -> current, current -> previous, upcoming cleared. **Response:** `{ treasuryRoll: { previousId, currentId, upcomingId } }` **Errors:** 400 (no upcoming treasury configured) ### GET /api/events **Auth:** read, write, or admin scope **Parameters:** - `since` (query, optional): ISO 8601 timestamp — return events after this time - `limit` (query, optional): Max events (default 50, max 200) **Response:** Array of protocol events `{ id, type, timestamp, data, keyId }` ### GET /api/events/stream **Auth:** read, write, or admin scope **Description:** Server-Sent Events stream. Connection stays open and pushes events as they occur. **Content-Type:** `text/event-stream` ### GET /api/keys **Auth:** admin scope **Response:** Array of API key metadata `{ id, name, prefix, scopes, createdAt, expiresAt }` ### POST /api/keys **Auth:** admin scope **Request body:** `{ name: string, scopes: string[], expiresAt?: string | null }` **Valid scopes:** `"read"`, `"write"`, `"admin"` **Response:** `{ id, name, key, prefix, scopes, createdAt, expiresAt }` (201) — key shown only once **Errors:** 400 (validation) ### DELETE /api/keys/:id **Auth:** admin scope **Parameters:** `id` (path) — Key ID to revoke **Response:** `{ deleted: true }` **Errors:** 400 (cannot delete bootstrap key), 404 (not found) ### GET /api/audit **Auth:** admin scope **Parameters:** `limit` (query, optional): Max entries (default 100, max 500) **Response:** Array of audit entries `{ timestamp, requestId, method, path, keyId, keyName, ip, statusCode, durationMs, action }` ### GET /api/webhooks **Auth:** admin scope **Response:** Array of webhook subscriptions `{ id, url, events, createdAt }` ### POST /api/webhooks **Auth:** admin scope **Request body:** `{ url: string, events: string[] }` — use `["*"]` for all events **Response:** `{ id, url, events, secret, createdAt }` (201) — secret shown only once **Webhook delivery:** POST to registered URL with headers `X-Govy-Signature` (HMAC-SHA256), `X-Govy-Event` **Errors:** 400 (validation) ### DELETE /api/webhooks/:id **Auth:** admin scope **Parameters:** `id` (path) — Webhook ID **Response:** `{ deleted: true }` **Errors:** 404 (not found) --- ## Error Codes | Code | HTTP | Meaning | |------|------|---------| | `RATE_LIMITED` | 429 | Too many requests | | `UNAUTHORIZED` | 401 | Missing or invalid API key | | `INSUFFICIENT_SCOPE` | 403 | Key lacks required scope | | `VALIDATION_ERROR` | 400 | Invalid request body or parameters | | `NOT_FOUND` | 404 | Resource not found | | `CONFLICT` | 409 | Resource already exists | | `CANNOT_DELETE` | 400 | Resource cannot be deleted (e.g., bootstrap key) | | `READ_FAILED` | 500 | Failed to read data | | `WRITE_FAILED` | 500 | Failed to write data | | `COMPUTE_FAILED` | 500 | Failed to compute derived value | | `PROXY_FAILED` | 500 | External API call failed | | `CHAIN_ERROR` | 500 | Blockchain data fetch failed | | `SPEC_NOT_FOUND` | 404 | OpenAPI spec file not found | | `API_KEY_NOT_CONFIGURED` | 503 | No API key configured on server | | `INVALID_API_KEY` | 401 | Provided API key is invalid | | `MISSING_ISIN` | 400 | ISIN parameter not provided | | `INVALID_ISIN` | 400 | ISIN format is invalid | | `NO_CURRENT_TREASURY` | 404 | No current treasury in roll configuration | | `TREASURY_NOT_FOUND` | 404 | Referenced treasury not in treasury list | | `NO_PRICE_DATA` | 404 | No price data for current treasury | | `NO_TREP_DATA` | 404 | No T-Rep history available | ## Event Types | Type | Trigger | Payload | |------|---------|---------| | `protocol_data.updated` | POST /api/protocol-data | `{ method: "bulk" }` | | `treasury.created` | POST /api/treasuries | `{ treasuryId }` | | `treasury.updated` | PATCH /api/treasuries/:id | `{ treasuryId }` | | `treasury.deleted` | DELETE /api/treasuries/:id | `{ treasuryId }` | | `price.added` | POST /api/prices/:treasuryId | `{ treasuryId, date, price }` | | `trep.added` | POST /api/trep | `{ date, value }` | | `roll.forward` | POST /api/roll-forward | `{ from, to, retired }` | | `contracts.updated` | PATCH /api/contracts | Updated contract fields | ## Webhook Delivery When events match a webhook subscription: 1. The server sends a POST request to the webhook URL 2. Request body: `{ id, type, timestamp, data, keyId }` 3. Headers include: - `X-Govy-Event`: Event type string - `X-Govy-Signature`: HMAC-SHA256 hex digest of the body using the webhook's shared secret 4. Verify the signature: `crypto.createHmac('sha256', secret).update(body).digest('hex')` ## Links - Website: https://govy.finance - API Spec: https://api.govy.finance/api/openapi.json - Concise Reference: /llms.txt