API and SDK
Canary's public API serves everything the app shows: rungs, bounties, markets, vaults, prices and the status feed, all indexed from the chain at finalized commitment. It is read-only. Anything that changes state is a Solana transaction your wallet signs; the API's RPC relay can carry it to the network for you.
No key or account is needed.
Conventions
| Format | JSON over HTTPS |
| Envelope | { "data": …, "meta": … }, except the status feed, which is served as is |
| Lists | { "items": [...], "nextCursor": "…" or null }; pass limit (1–100, default 50) and cursor |
| Amounts | { "asset": { "symbol": "USDC", "decimals": 6, "mint": "…", … }, "amountAtomic": "…" }: atomic units as a decimal string, so 1 USDC is "1000000" |
| Slots and large integers | Decimal strings |
| Times | ISO 8601, UTC |
| Rate limit | 1,200 requests per minute per client |
meta.freshness says how current the answer is: the slot it was indexed at, how far behind the chain head it is, and a state of fresh, indexer-behind or rpc-unreachable. While the deployment is still being initialized, data routes answer CONFIGURATION_REQUIRED and /v1/app-config reports state: "waiting".
Endpoints
| Endpoint | Returns |
|---|---|
GET /v1/app-config | The deployment: cluster, program ids, USDC mint, rungs with their public parameters, the RPC relay URL, and whether the service is ready |
GET /v1/programs | Program ids, upgrade authorities, the protocol constants and the buyback: its vault, the CANARY mint, the USDC spent and the CANARY burned |
GET /v1/rungs | Every rung with its state, parameters, verification method, bounty and open interest |
GET /v1/rungs/{rungId} | One rung; rungId is Q-160, Q-192, Q-224, C-160, T-256k or T-255e |
GET /v1/rungs/{rungId}/events | The rung's fall, once it has fallen |
GET /v1/rungs/{rungId}/claim-jobs | Staged claims in flight on the rung |
GET /v1/claim-jobs/{jobId} | One claim job and its progress |
GET /v1/transcripts/{hash} | A rung's parameter transcript, byte for byte |
GET /v1/bounties | Every bounty vault: balance, totals funded and paid, fees received, sponsors |
GET /v1/markets | Every market; rungId filters by target or control rung |
GET /v1/markets/{marketId} | One market: outcome, open interest and cap, fees, latest price |
GET /v1/markets/{marketId}/prices | Price history, read from Jupiter's price API once a minute |
GET /v1/q-date | The date the markets imply for each rung's fall |
GET /v1/totals | Totals across bounties, markets and vaults |
GET /v1/wallets/{wallet}/receipts | A wallet's sponsor receipts and their reclaim status |
GET /v1/wallets/{wallet}/positions | A wallet's YES and NO balances |
GET /v1/wallets/{wallet}/vaults | A wallet's Q-Vaults |
GET /v1/vaults/{vaultId} | One Q-Vault: mode, trigger, budget, balances, key status |
GET /v1/vaults/{vaultId}/queued-spends | The vault's queued Ed25519 spends and when each unlocks |
GET /v1/eligibility | Whether markets are available to you |
GET /v1/status-feed | Every rung's state in one document, canary.status.v1 |
POST /v1/rpc | The RPC relay |
For example:
curl -s "https://API_ORIGIN/v1/markets?rungId=Q-160&limit=10"
Errors
Errors use the same envelope with an error object:
{ "error": { "code": "NOT_FOUND", "message": "…", "requestId": "…", "retryable": false, "details": {} }, "meta": { … } }
| Code | HTTP | Meaning |
|---|---|---|
VALIDATION_FAILED | 400 | A parameter is missing or malformed; details.issues says which |
NOT_FOUND | 404 | No such rung, market, vault or job |
RATE_LIMITED | 429 | Too many requests; details.retryAfterSeconds says when to retry |
FORBIDDEN | 403 | The route is not available on this deployment |
UPSTREAM_UNAVAILABLE | 503 | The database or the chain could not be read; retry |
CONFIGURATION_REQUIRED | 503 | The deployment has not configured something the route needs |
INTERNAL_ERROR | 500 | Retry; report it if it persists |
The RPC relay
POST https://API_ORIGIN/v1/rpc accepts Solana JSON-RPC and forwards it to the deployment's RPC provider. It is how the app reads accounts and sends your signed transactions without publishing an RPC key. You can point any Solana client at it:
import { Connection } from '@solana/web3.js'
const connection = new Connection('https://API_ORIGIN/v1/rpc', 'finalized')
| Allowed | |
|---|---|
| Reads | getAccountInfo, getMultipleAccounts (up to 100), getBalance, getTokenAccountBalance, getTokenAccountsByOwner, getTokenSupply, getMinimumBalanceForRentExemption, getSignaturesForAddress (limit up to 20), getTransaction, getSignatureStatuses (up to 32) |
| Chain | getVersion, getGenesisHash, getSlot, getBlockHeight, getEpochInfo, getLatestBlockhash, isBlockhashValid, getFeeForMessage |
| Transactions | simulateTransaction, sendTransaction: signed, base64, at most 1,232 bytes |
| Limits | 1 to 5 requests per batch; 600 requests and 60 transactions per minute per client |
The relay holds no key and cannot sign anything; it only forwards what a wallet has already signed. It has no WebSocket endpoint, so confirm transactions by polling getSignatureStatuses. For heavy use, run your own RPC endpoint; nothing in Canary depends on the relay.
Building transactions
Every Canary action is an ordinary Solana transaction against the three programs. The app builds them with TypeScript clients that prepare transactions without signing or sending them:
| Client | Prepares |
|---|---|
RungsClient | Funding a bounty; a claim from your secret scalar, which never leaves your machine |
MarketsClient | Minting, merging and redeeming pairs |
VaultTransactionClient | Creating a Q-Vault and registering its key, deposits, Ed25519 spends and queues, executing queues, Falcon spends and vetoes, tripping |
| Falcon SDK | Generating a Falcon-512 key, saving and importing the key file, and signing vault actions offline |
createCanaryClient | Typed calls to every endpoint above |
Each prepared transaction has an explicit compute limit, your wallet as fee payer, a recent blockhash and a distinct memo, and stays within 1,082 bytes so your wallet can add its own instructions. Send them in order and wait for each to confirm. The program ids and every account layout are in the whitepaper's appendices.
Market eligibility
Markets exclude US persons in v0. GET /v1/eligibility returns whether market actions are available:
{ "data": { "markets": { "allowed": true, "reason": "eligible-region", "countryCode": "DE" }, "source": "self-attestation" } }
Where the deployment has no location source, pass your country and an attestation: ?countryCode=DE&attestNonUs=true. This is an interface policy; the programs themselves don't check location.