One call adds cover to a trade your bot was already making. The reference below is generated from the same schemas the API validates its own responses against — if it says a field exists, the service is checked against that claim on every request.
Base URL
https://apecover.io/api
Auth
Authorization: Bearer <keyId>.<secret>
Amounts
Decimal strings — neither u64 nor u256 survives a JSON number
Freshness
Projection reads carry asOfSlot; live routes carry asOfTs or neither
Start here
Keys are issued from your account page: sign in, register your integration, and the key is shown once. Go to your account — then the two calls below are enough to confirm everything is wired up.
bash
# 1. Get a key at apecover.io/app, then check it works
curl -H "Authorization: Bearer $APECOVER_KEY" \
https://apecover.io/api/v1/whoami
# 2. Price a policy before you commit to anything
curl -X POST https://apecover.io/api/quote \
-H 'content-type: application/json' \
-d '{"pool":"'"$POOL"'","tokenMint":"'"$TOKEN_MINT"'","tradeSize":"300000000","tier":1,"packSize":10}'
A key authenticates as soon as it is issued, before your application is reviewed, so you can build against the API while you wait. What it cannot do until an operator registers you on chain is earn — see what approval does and does not mean.
The SDK
@apecover/sdk collapses buy-a-policy-if-needed and register-the-trade into one call. It reuses a prepaid pack while it has credits and buys a new one when it runs out, so your code never handles a policy.
import { ApeCover } from '@apecover/sdk';
import { Connection, Keypair } from '@solana/web3.js';
const ape = new ApeCover({
apiKey: process.env.APECOVER_API_KEY, // issued at /app, shown once
pool: process.env.APECOVER_POOL, // required — which pool you insure against
connection: new Connection(process.env.RPC_URL, 'confirmed'),
signer: Keypair.fromSecretKey(secret), // never transmitted
});
// After your swap confirms, before you reply to the user.
const result = await ape.insureTrade({
swapSignature, // base58, exactly as your swap returned it
tokenMint,
tradeSize: fill.lamportsSpent, // pass it: a policy bought without one is refused
tier: 'standard',
});
if (result.status === 'insured') console.log('covered', result.trade);
else if (result.status === 'declined') console.log('no cover:', result.reason);
else console.log('sent, outcome pending', result.signature);
The API key authenticates to ApeCover, never to a chain. It cannot move funds and is not a wallet — your own key signs, locally, and the SDK never transmits it. What the key buys is the attestation: registering a trade needs the pool attestor to co-sign, because entry price and market cap are not computable on chain and a self-reported entry price is the fraud that co-signature exists to prevent. The client refuses to construct in a browser, where a key would be readable by every visitor.
Amounts are bigint throughout. A JavaScript number silently rounds past 253, and sizing is the one thing a bot must not get wrong.
Approved is not the same as earning
Two things have to be true before a revenue share accrues, and it is worth knowing which is which, because the first happens in seconds and the second involves a human.
Your application is accepted. Your key already worked before this; acceptance is a decision, not a capability.
An operator runs register_partner. That instruction is admin-signed, so it cannot be self-served. Until the transaction lands there is no Partner account for policy.partner to point at, and nothing accrues.
/v1/whoami answers both questions in one field: earning is true only when the on-chain account exists. Your account page reads “Approved — not yet on chain” for the state in between rather than rounding it up.
Webhooks
We POST trade.insured, claim.paid and claim.rejected to your endpoint, signed with your API secret. Verify the signature and the replay guard: a signature proves a delivery came from us, not that it is new, and a replayed claim.paid is worth money to anyone whose handler credits an account on receipt.
typescript
import { createHmac, timingSafeEqual } from 'node:crypto';
const REPLAY_WINDOW_SECONDS = 300;
/** rawBody must be the bytes as delivered — a re-serialised object hashes differently. */
export function verifyDelivery(rawBody, signatureHeader, secret, seen) {
const parts = new Map(
signatureHeader.split(',').map((p) => p.split('=').map((s) => s.trim())),
);
const timestamp = Number(parts.get('t'));
const provided = parts.get('v1');
if (!Number.isFinite(timestamp) || provided === undefined) return 'malformed-signature';
const expected = createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`, 'utf8')
.digest('hex');
const a = Buffer.from(provided);
const b = Buffer.from(expected);
if (a.length !== b.length || !timingSafeEqual(a, b)) return 'bad-signature';
// Freshness, then replay. Both are required: a signature stays valid forever without them.
const age = Math.floor(Date.now() / 1000) - timestamp;
if (age > REPLAY_WINDOW_SECONDS) return 'too-old';
if (seen.has(provided)) return 'replayed';
seen.add(provided); // back this with Redis, and expire after the window
return 'ok';
}
Integrating a trading bot
Custodial trading bots — the bot holds a per-user wallet, builds and sends the swap from it, and replies in chat — integrate the same way, and it is one call.insureTrade slots in after the swap confirms and before you reply. Everything below follows from that position.
The user’s wallet is the signer. The trade’s owner is the wallet that made the swap, and so is the payout address. You already hold it; the SDK countersigns locally and never transmits a key.
The attestation is not your problem.insureTrade obtains the platform-signed registration transaction. You cannot build one yourself — the attestor’s signature is what the program checks.
Declines are outcomes, not errors.insured / declined / unknown are three different chat replies. Keep partner-exposure-cap and partner-rate-limit distinct from insufficient-pool-capacity: the first two mean “not right now, your traffic”, the last means the pool is busy. A bot that conflates them reports an outage while the protocol is serving everyone else.
The claim path is chain-only and survives us.Protocol.submitClaim needs the proof digest from GET /trades/:address/proof; everything after that is a program instruction you sign yourself.
Your revenue share is on chain. It accrues against your Partner account on every policy your key originates, and you claim it whenever you like — see what approval does and does not mean.
That loop is a working program, not a sketch: the reference adapter is executed against a live validator and a real API server on every CI run. An integration is that file with your own names in it. Ask us for it — and for the bot-specific specification, which pins the hook point, the key model and the decline mapping against your surface — and we will send both.
The screens
Six destinations in the header. Two of them — Stats and this page — are readable without an account. The rest are about your integration, so they ask you to sign in rather than showing you an empty version of themselves.
Screen
What it answers
Access
/stats
Live pool state — reserves, liabilities and utilisation. Exactly one figure, liabilities, is reconciled against an independent derivation, and it is the only one that can show you a disagreement.
Open
/positions
Cover by address: chain, trade, token, amount insured and status. Underwriter stake is on the same card.
Sign in
/claims
Every claim against an address — trade, size, window, rug proof and where the claim itself has got to.
Sign in
/dashboard
Your own totals, keyed on your API keys: what each has insured, what it has earned, and what you have staked.
Sign in
/docs
This page.
Open
/app
Register as a partner, then issue and revoke API keys. A key is shown once, when it is issued.
Sign in
The chain toggle sits in the page’s heading row, on every screen where the selection changes what is shown. It writes ?chain= into the address, so the view you are looking at is the view a shared link opens. It is deliberately absent from this page — the reference documents both chains at once, so there would be nothing for it to change. Captured against a stubbed API — the layout and copy are the product, the account is not a real one.
1 · Signing in
An account holds your keys and your partner registration. It is not what holds your cover — cover belongs to the wallet that bought it, and the protocol has never heard of your email address. Nothing you do here can move money.
There is no password. Enter your email, and a six-digit code arrives; the code signs you in and the session is a cookie on this site. That is the whole of it, and it is the reason an account is safe to make before you have decided anything: it can be thrown away and it holds nothing you cannot re-issue.
The sign-in card, at /login or from any gated link in the header. The line under it is the deployment’s own: this build names its cluster and says that its cover is not a regulated insurance product, wherever it appears.
2 · Registering as a partner
A partner is an integration that routes trades to the pool — a bot, a terminal, a wallet — and earns a share of the premium on every policy its key originates. Registering is a form on your account, and it is the only step in this walkthrough that somebody else has to act on.
The share itself is partner_fee_bps on the pool, and the form reads it from the pool account and prints it rather than quoting a number this page decided. If the chain cannot be read the sentence loses the figure instead of inventing one — a rate in a document that the program does not pay is worse than no rate at all.
The application. Five fields, of which two are addresses and only one of them is permanent. The percentage in the first paragraph is read from the pool at the moment the page loads; the rest of this figure is a fixture.
The two addresses are not the same thing, and the difference is the one part of this form worth slowing down for. The payout address is where your revenue share is paid, and it can be changed. The signing wallet is the wallet that will claim those fees, and your on-chain partner account is derived from it — so it cannot be changed later, and an address with no private key behind it would leave everything you earn unclaimable for ever. Both forms that collect it check the shape before it is stored; neither can check that you hold the key.
Keeping them apart is worth doing even though they may be the same address: it means the wallet holding the money never signs anything.
What happens next, in three states
Submitting gives you a working API key immediately. It does not give you a revenue share, and the account page is careful about the difference because it is the one thing here that looks live and is not.
Badge
What is true
What is not
In review
Your key authenticates. Quotes, reads and attestations all work.
Nothing is accruing.
Approved — not yet on chain
The application was accepted. Your key still works, exactly as before.
Still nothing is accruing. The revenue share starts when an operator registers your partner account on chain, which is a transaction somebody sends.
Earning
Approved and registered on chain. Your share accrues on every policy your key originates, and your dashboard can show it.
Your keys do not. A key authenticates the SDK to this deployment and works wherever the deployment serves; a registration lives on one chain’s registry, and the account page says which rather than guessing.
The application above is a Solana registration — both of its addresses are Solana addresses. Every other chain keeps its own registry on its own pool contract, keyed by an address there, and nothing links the two: your Solana authority does not derive an EVM address and no amount of configuration makes it. So switch the chain selector and tell us two things: the address you trade from on that chain, and where its pool should pay your share. Both are stored as a declaration — it does not register you, which is a transaction on the pool contract sent by its admin — and between them they do three things: this page can look you up in that registry without being asked, an operator can see that you are waiting, and the operator has the two arguments the pool’s registerPartner takes. Your Solana payout address is not one of them and cannot be made into one, which is why it is asked for here rather than derived.
The payout address may be left empty, and then the card says so plainly: the address alone is enough for the lookup, and not enough for anybody to register you. That row shows in the operator’s queue as waiting on you rather than on them.
The account page with the chain switcher on Robinhood Chain. The address is replaceable — it is a key into somebody else’s registry rather than a seed, so a typo is fixable, unlike the signing wallet above. Everything below it is the pool contract’s own answer about that address, and these figures are fixtures.
3 · Keys and webhooks
Registering issues your first key. The account page issues the rest, up to the limit it prints, and the secret is shown once — this service stores a hash of it and cannot serve it again. Losing one is not a disaster; revoke it and make another.
The one time the secret exists on a screen. The dialog will not be dismissed until it has been copied — clicking outside it does not discard it — because “copy this now” and a scrim that closes on a stray click are a bad pair.
A key never signs on a chain. Your own key does that, locally, and the SDK never transmits it. What the API key buys is the attestor’s co-signature on an entry price, which is what makes a self-reported one impossible — so a leaked API key can spend your rate limit and attribute trades to you, and cannot move your money. Send it as Authorization: Bearer <key>, from your server.
The account once it is live. A revoked key stays in the list rather than vanishing — it is what an old log line refers to, and a key that disappears when it stops working leaves nothing to recognise. The badge at the top is the three-state distinction from the previous step.
The webhook is the other half of the same card, and it is how you find out about a trade without polling. We POST to your URL on trade.insured, claim.paid and claim.rejected, signed with a secret shown once when you save it. Replacing the URL rotates that secret. HTTPS only.
One endpoint per account. Webhooks has the payloads, the signature scheme and the retry policy.
4 · Getting cover on a trade
Not from this site. Your bot calls the SDK, which prices a policy, buys one if the prepaid pack has run out of credits, and registers the trade — in a single call, from wherever your trading code already runs. Nothing here needs a wallet, because the wallet is wherever that code is.
Two consequences are worth holding on to. Cover runs from registration, not from purchase — a pack bought in advance insures nothing until a trade is registered against it. And the API key never signs on a chain: your own key does, locally, and the SDK never transmits it. What the key buys is the attestor’s co-signature, which is what makes a self-reported entry price impossible.
If you would rather watch the sequence happen once before wiring it in, ask us — the same quote, attest and register calls can be driven a single trade at a time against your own pool, and watching one go through is the fastest way to see where your integration should hook in.
5 · Following a position
Positions takes a wallet address — paste the one your bot trades from. The data is public, so you do not have to own an address to look one up, and the page answers for whichever chain the toggle shows.
One address, three tables. Every row is a fixture; every column is the product’s.
Three things on that screen are easy to misread.
Size is not cover. The Cover column is what the pool set aside for the trade — the size times the tier’s ratio, capped at the pool’s per-trade ceiling. A column that printed the size instead overstated a Standard trader’s cover by exactly two times, which is why the share is printed beside it.
Credits left is not cover available later. A policy has a lifetime on chain that these events do not carry, so a pack with credits remaining may still be past it. Reading the account is what the SDK is for.
The stamp is the point of the badge. Every figure comes from the indexed projection rather than from the chain, and the slot beside the heading says exactly how fresh it is. Read it before concluding a trade is missing.
The same page on the other chain. Rows carry their own chain rather than inheriting the one you asked for, and sizes are in that chain’s own unit. These rows are fixtures.
6 · When a rug happens
A claim is filed by the wallet that owns the trade. On chain nothing else can, and the program accepts it only until a short grace period after cover ends — so it is filed with the keys the trade was made from, through the SDK or the bot that placed it. There is no form on the claims page and nothing there signs.
So it is somewhere to look rather than somewhere to act: which trades were judged rugs, the proof each judgement rests on, and where each claim has got to.
The two halves of the page. Above, cover that could still claim — with the window, whether a rug proof exists, and how long the program will still accept a claim. Below, what has already been decided. Fixtures, in the product’s own layout.
A claim that has not appeared is not one you forgot to file. Either the rug has not been confirmed, or the trade was not covered when it happened — and Window is the column to check first, because cover that had already ended is the common answer. Where no rug check could run at all, the page says so above the table rather than leaving a row blank: nothing there has been examined and found clean, it has not been examined.
7 · Staking capital, and what it earns
The other side of the product. An underwriter puts capital behind the pool so it can carry cover, and earns a share of the premiums that cover pays. It is a different thing from a partner’s revenue share — that is paid for routing trades and is covered in approved is not the same as earning. Staking is paid for bearing the risk, and needs no approval from anyone.
The panel is on Stats and on Positions, and what it offers depends on how the deployment is configured. By default it hands you the SDK calls against this deployment’s own program and pool, because the standing direction is that nobody signs from a browser here. Where an operator has turned wallet writes on, the same three actions are buttons instead. Either way it publishes the two numbers the program refuses on, which no client could read until it did — the first-stake floor and the lockup.
The underwriting section of the positions card. The floor and the lockup in the three tiles are the program’s own constants, not fixtures — everything to the left of them is one.
There are exactly three writes, and one position per wallet per pool — the stake account is derived from the two, so staking a second time tops up what you already have rather than opening anything new.
underwrite(amount) — opens or tops up. Only the first stake is held to the floor; a top-up of any size is accepted.
withdrawStake(amount) — refused for the published lockup after every deposit, not just the first, so a top-up restarts the clock on the whole position. Refused at any time for an amount that would leave open cover unbacked or drop the pool below its reserve floor.
claimRewards() — pays the premium share the position has earned. Never gated on the lockup, and not gated on a pause either: yield was never part of what backs cover, so withholding it would protect nothing.
Position value is not what you deposited. It is your shares times the pool’s capital, so it moves with the book: it rises as premiums arrive and falls when a claim is paid out of capital, and the P&L beside it is that difference stated rather than floored at zero. Underwriting risk is exactly this number being negative, and a panel that hid it would be hiding the product.
A refusal here is a stated fact, not an error. Locked capital comes back with a countdown; an exit that would unback live cover says so and names the amount that would not. The SDK renders all three as answers rather than throwing, so your own code can branch on them — and a pause, when one is on, stops the exit alone.
8 · Your dashboard
Positions answers about an address. Dashboard answers about your account: what your keys have insured, what it has earned, and what you have staked.
The whole page. Every figure on it is a fixture; the sources beside them are not.
It draws on three sources and labels each one where it is used, which is the only reason nothing here can quietly disagree with the protocol. Per-key attribution comes from this service, because the chain does not hold it — on chain, every key a partner owns shares one account. Revenue share, exposure and stake are read from the chain directly, because they are money. When the two disagree, the chain is the one that is true.
Two of the tiles are the limits that decline a trade, and both free up rather than run out. Exposure headroom is a share of the pool’s ceiling, not of the capacity left in it — so you can be full while the pool is quiet — and it frees as open cover settles. Rate limit is a fixed window, so the whole allowance returns when it rolls. The right response to either is to retry, not to disable the integration.
The Staked card reads the position held by your payout address, and says so. This site has no wallet, so that is the only Solana address it knows you control — if you underwrite from a different one, look that address up on Positions instead. Its stake will not appear here, and the card saying “none” is not the same claim as “you have not staked”.
Two chains, one protocol
The same contracts, deployed twice. Tiers, premiums, the fee split, the claim rules and the entry gate are one implementation — @apecover/common’s arithmetic, which the Solana program and the Solidity pool both mirror — so the same economic trade prices identically on either. What differs is everything about acting on that price, and the next section is the list.
Every chain-scoped endpoint takes ?chain=, and the reference below badges the ones that do. Three answers, not two:
The table below is read from CHAIN_REGISTRY — the same value the chain switcher offers and the API refuses on — rather than written out here.
Absent means solana. That is a compatibility promise, not a convention — every link and every integration written before there was a second chain keeps working unchanged.
A value no chain answers to is a 400. It is not defaulted, because a typo silently answered from Solana is worse than a refusal.
A real chain this deployment does not serve is a 503 chain_not_served — a statement about this deployment, not about the chain. /health publishes which surfaces each served chain can answer, so a client can ask before it requests.
chain
Chain id
Offered here
Notes
solana
—
Enabled
Solana
robinhood
4663
Not enabled
nothing is deployed on 4663 yet, and the mainnet gate is unmet — see docs/MAINNET_GATE.md
robinhood-testnet
46630
Enabled
Robinhood Chain testnet (46630)
The row that matters most is the one that is off. robinhood is mainnet 4663 and robinhood-testnet is 46630, and they are different chains rather than two names for one. A sample naming the first is a sample a reader cannot run.
What differs, and what does not
Every row below is read from the constants the code computes with, so this table cannot disagree with the client you install.
Solana
Robinhood Chain
Why it matters
Address shape
base58
hex20
Base58 and 0x-hex are disjoint sets, so an address for the wrong chain is a 400 rather than a lookup that finds nothing. That is deliberate: a miss and a mismatch are different answers.
How an object is named
base58 PDA
uint256, as a decimal string
One contract is one pool here, so a policy, a trade and a claim are mapping keys — /trades/42?chain=robinhood-testnet is valid and /trades/42?chain=solana is malformed.
Native unit
SOL · 9 dp · lamports
ETH · 18 dp · wei
The chain’s own unit, and not necessarily the pool’s — see the paragraph below the table. Nine decimals against eighteen is a factor of a billion in the wrong direction.
Block time
0.4 s
0.1008 s
Anything expressed per block — a window, a lockup, a rate — is a different duration on each. Convert through the seconds, never by copying the block count.
Settled after
0 s
843 s
How long a write stays reorg-able. Zero on Solana because a confirmed read needs a supermajority fork to undo; the EVM figure is a measured peak, and budgeting on the average waits too little half the time.
Approvals
no
yes
An ERC-20 pool needs an allowance before it can take a premium, which makes buying two signatures instead of one. Ask capabilities rather than catching the failure.
Rent
yes
no
A Solana policy pays rent for the accounts it opens, refundable when they close. An EVM policy pays gas, which is gone — so a quote carries a gas ceiling where the other carries a rent line.
Priority fees
yes
no
There is a fee market on both; on 4663 nothing is bidding into it, so a client that budgets for one is provisioning against something that is not the constraint. Nonces are.
The settlement asset is a property of the pool, not of the chain. This is the single most likely integration mistake, because the chain’s native unit is the obvious thing to reach for and it is right only some of the time. A pool may settle natively — 18 decimals of the chain’s own unit, paid as value on the call — or in an ERC-20, whose decimals are the token’s and are commonly six. POST /quote answers with evm.settlementAsset, evm.settlementSymbol and evm.settlementDecimals; read the width from there rather than assuming one. A client that assumes 9, as it would on Solana, is out by a factor of a billion.
The other consequence of that field is procedural. A native pool takes the premium with the call, so buying is one signature. An ERC-20 pool cannot, so it is two: an approval, then the purchase. evm.approvalRequired says which before you build anything — a bot that discovers it afterwards has already lost the race it was in.
And what does not differ is worth stating plainly, because it is most of the product: the three tiers and their percentages, the premium and payout arithmetic, the fee split between reserves, protocol, partner and underwriter, the per-trade cap, the entry market-cap gate, the utilisation ceiling, what counts as a rug, and the rule that cover runs from registration rather than from purchase. None of those is chain-specific, and none of them is implemented twice.
Robinhood Chain, today
Deployed and readable on testnet 46630; nothing on mainnet 4663. What follows is what this deployment can do there right now, split into the half that works and the half that does not, because the difference is not a matter of degree.
What works
Every read: positions and exposure, policies, claims, the partner registry, protocol statistics and an owner’s own figures, all served from the indexed projection of the pool’s logs. POST /quote answers the evm block — chain id, settlement asset, whether an approval is needed, measured gas ceilings and a live gas price — every field of it read from the pool contract. And a pack can be bought: createPolicy takes no attestation, so a wallet and a price are the whole of what it needs.
/quote prices the pool too, from the contract’s terms. The API reads the configured pool’s tier table and limits from the contract, so quote carries the premium, the cover and the fee split, computed by @apecover/core — the same basis-point arithmetic the contract runs and the same code the Solana path prices with — and issues carries the same eligibility checks a Solana quote runs: a paused pool, a size outside the pool’s bounds, too little capacity, a flat premium below the floor. This page’s own trading panel computes its preview in the browser with the same code, from terms it reads off the pool. Two answers carry no price. A deployment that does not read this chain returns quote: null with a chain-not-priced issue, a statement about the deployment rather than about your pool. One that does read it answers unknown-pool for any pool but the one it is configured for, and for a read of that pool that failed; a failed read is held for a second at most, so ask again.
What does not, and exactly why
Registering a trade needs the pool’s trade attestor.registerTrade(uint256, TradeAttestation, bytes) takes the trade attestor’s signature as its last argument; the pool recovers it and compares the recovered address against its own tradeAttestor. That is ADR-0003’s rule in EVM form and it is not optional — an entry price the client chose is precisely the fraud a co-signature exists to prevent, so a self-signed attestation is refused by the contract rather than by us.
The service side exists. POST /v1/attest/evm measures the swap from the chain rather than taking the caller’s word for it, reads the entry price from the v4 pool bound to the token, and returns the EIP-712 attestation with a secp256k1 signature; the SDK’s insureTrade turns that into registerTrade calldata. A deployment mounts the route only when it holds an EVM attestor key and the settings beside it. Its Solana attestor’s ed25519 key cannot stand in: that is a different curve, not the same key in another encoding.
What is left is on the pool. Its admin points tradeAttestor at this deployment’s address with updatePoolParams and setTradeAttestor, which is an on-chain transaction and somebody’s decision; a pool that has adopted its attestor registry takes registerTradeQuorum co-signatures instead, which this deployment does not produce. /health says where a deployment stands: evmAttestor.status reads attestor only when the pool names its key. The trading page has no registration flow of its own on this chain.
The consequence is worth stating in the buyer’s terms rather than the protocol’s: cover runs from registration, so a pack bought on 46630 insures nothing until a trade is registered against it. The trading surface says the same thing above its own buy form, before the purchase rather than after it.
Claims, and the same distinction
submitClaim is on the pool and the SDK builds it, so the shape of a claim is there. Whether one can be filed is a question about the watcher and the proof pipeline rather than about the chain, and /health answers it per link: claimPath collapses it to a word, and proofChain names each link and the environment variable that fixes it. Read that before concluding a claim is missing.
SDK reference
34 functions across the client, the platform half and the pure helpers. Every example below is code to copy into your own project — nothing on this page executes, and nothing on it will ever ask you for a private key.
ApeCover
new ApeCover(options)
new ApeCover(options: ApeCoverOptions): ApeCover
The high-level client. apiKey and pool are both required — pool is checked at construction and throws, naming it, if it is missing or malformed. `chain` is optional and absent means solana, so every integration written before it existed keeps working. A signer is needed for anything that touches the chain; the read-only methods work without one.
typescript
import { ApeCover } from '@apecover/sdk';
import { Connection, Keypair } from '@solana/web3.js';
const ape = new ApeCover({
apiKey: process.env.APECOVER_API_KEY!, // dgn_….dgnsk_…, issued at /app
pool: '3J31LrGG5Ko7iECH4o4QCdh7vXfShUHc2Npww2pCQPaH', // required: there is no "the" pool to default to
connection: new Connection(process.env.RPC_URL!, 'confirmed'),
signer: Keypair.fromSecretKey(secret), // never transmitted
});
// Omitting `pool` throws here, at construction, rather than 404-ing on
// GET /pool/undefined one round trip later. A deployment may index more than
// one pool, and only you know which one you are insuring against.
new ApeCover({ chain: "robinhood-testnet" })
new ApeCover(options: ApeCoverOptions & { chain: 'robinhood-testnet' }): ApeCover
The same client on Robinhood Chain. The options literal is identical except for the chain and the signer: an EVM signer replaces the Keypair, and `evmAccess` replaces the Connection. Passing an ed25519 keypair here throws at construction rather than at broadcast — a wrong-family key signs perfectly well and is refused by the chain. Testnet 46630, because that is where a pool is deployed: `robinhood` is mainnet 4663, and nothing is on it yet.
typescript
import { ApeCover } from '@apecover/sdk';
const ape = new ApeCover({
chain: 'robinhood-testnet', // omit for solana — that is the default
apiKey: process.env.APECOVER_API_KEY!,
pool: '0x932e54A4929f156154dABa61d30abe5172542370',
signer: wallet, // { address, sendTransaction } — any library
evmAccess, // allowance / send / confirmed / registration
});
// Ask what the chain can do rather than catching an error that says it cannot.
if (ape.capabilities.approvals) {
// ERC-20 pools need one; insureTrade tops it up only when the standing
// allowance does not already cover the premium.
}
console.log(ape.capabilities.settlementLagSeconds); // 843 here, 0 on Solana
capabilities
readonly capabilities: ChainCapabilities
What this chain can do that the other cannot: approvals, permit2, composed transactions, priority fees, rent, atomic insure, and how long a write stays reorg-able. Reading it costs no round trip — every field is a property of the chain.
typescript
const ape = new ApeCover({ chain: 'robinhood', apiKey, pool, signer, evmAccess });
console.log(ape.chainName); // 'robinhood'
console.log(ape.capabilities.approvals); // true — false on Solana
console.log(ape.capabilities.rent); // false — true on Solana
console.log(ape.capabilities.settlementLagSeconds); // 843 — ADR-0013's measured peak
// Budget on the peak rather than an average: the safe tag steps rather than lags,
// so a client that waits the average waits too little half the time.
whoami
whoami(): Promise<Whoami>
Verify a key and see what it is attached to. Read `earning`, not `status` — approval is a decision, earning additionally requires an on-chain Partner account to exist.
typescript
const me = await ape.whoami();
if (!me.partner.earning) {
// Approved is not the same as earning: an operator still has to run
// register_partner before policy.partner has anything to point at.
console.log('no revenue share yet:', me.partner.status);
}
pool
pool(): Promise<Record<string, unknown>>
The pool’s live state as the API projects it — reserves, exposure, counters and the pause flag. Every amount is in the pool’s settlement asset, not necessarily lamports.
typescript
const pool = await ape.pool();
console.log(pool.paused, pool.totalLiabilities, pool.expiryBacklog);
// Amounts are base units of pool.settlementMint. Scale by that, not by 1e9.
What a trade of this size costs at this tier, and whether the pool would take it. Takes a params object because tokenMint is required — the entry gate is priced against the token, so the same size quotes differently for two mints.
typescript
const quote = await ape.quote({
tier: 'standard',
tradeSize: 300_000_000n, // lamports per covered trade
tokenMint, // required: the entry gate is priced against it
packSize: 10, // optional, defaults to a single trade
});
console.log(quote.eligible, quote.quote?.premium);
// marketCapMicroUsd is optional and is micro-USD, not lamports (1_000_000 = $1).
// Omit it and the deployment looks it up; if it cannot, the gate does not run and
// marketCap comes back null rather than silently passing.
Reuse a policy with credits or buy one, attest the swap, countersign and send — in one call. A refusal comes back as a result, not an exception: “this token is too large to insure” is an ordinary Tuesday and should not need a string match on an error.
typescript
// After your swap confirms, before you reply to the user.
const result = await ape.insureTrade({
swapSignature, // base58, exactly as your swap returned it
tokenMint,
tradeSize: fill.lamportsSpent, // pass it: a policy bought without one is refused
tier: 'standard',
});
if (result.status === 'insured') console.log('covered', result.trade);
else if (result.status === 'declined') console.log('no cover:', result.reason);
else console.log('sent, outcome pending', result.signature);
Buy a pack outright instead of letting insureTrade buy one when it runs out of credits. Useful for pre-funding before a burst.
typescript
const result = await ape.buyPolicy('standard', 300_000_000n);
if (result.status === 'bought') console.log('pack bought', result.policy, result.index);
else if (result.status === 'declined') console.log('no pack:', result.reason);
else console.log('sent, outcome pending', result.signature);
chain
chain(): Promise<InsuranceClient>
The lower-level client, with the platform’s advertised deployment already resolved. Reach for it when you want the address derivations or the policy walk directly.
The write surface: submit a claim, stake or withdraw as an underwriter, crank an expiry, claim partner fees. Pool state is read separately, with fetchPool.
typescript
const protocol = await ape.protocol();
// Crank an expired trade's reservation back into the pool — permissionless.
const result = await protocol.expireTrade({ trade, policy, owner });
console.log(result.signature);
Protocol
underwrite
underwrite(amount: bigint): Promise<CallResult>
Stake capital behind the pool. One position per wallet per pool, so staking again adds to it rather than opening a second — and every top-up re-arms the lockup on the whole position. The first stake must be at least 100000000 base units (0 SOL on this pool); top-ups are not held to it.
typescript
const protocol = await ape.protocol();
// 0.5 SOL, in lamports. Refused under the minimum with UnderwriterStakeTooSmall.
const result = await protocol.underwrite(500_000_000n);
if (result.status !== 'confirmed') throw new Error(result.detail);
Take staked capital back out. Refused for 60 seconds after every deposit (UnderwriterLocked), and refused at any time for an amount that would leave open cover unbacked or the pool below its reserve floor. A refusal is an answer, not a failure — the position is intact either way.
typescript
const protocol = await ape.protocol();
const result = await protocol.withdrawStake(100_000_000n);
// 'failed' with UnderwriterLocked means the lockup, not an error to retry blindly.
console.log(result.status, result.status === 'failed' ? result.detail : '');
claimRewards
claimRewards(): Promise<CallResult>
Collect the premium share this position has earned. Never gated on the lockup, and not gated on a pause either: yield was never part of what backs cover, so it is payable while principal is not. Takes no amount — it pays everything owed.
typescript
const protocol = await ape.protocol();
// Everything owed. There is no partial claim.
const result = await protocol.claimRewards();
console.log(result.status);
underwriterAddress
underwriterAddress(owner?: PublicKey): PublicKey
Where a wallet’s position lives — the PDA of ["underwriter", pool, owner]. Derived, not fetched, so it answers before a position exists. Read it with fetchPosition, or watch it on /positions without a wallet.
typescript
const protocol = await ape.protocol();
// The signer's own position by default; pass a public key for anybody else's.
console.log(protocol.underwriterAddress().toBase58());
PlatformClient
whoami
whoami(): Promise<Whoami>
The same call ApeCover.whoami wraps, for a caller that wants the platform without the chain. Constructing PlatformClient in a browser throws by design.
typescript
import { PlatformClient } from '@apecover/sdk';
const platform = new PlatformClient({ apiKey: process.env.APECOVER_API_KEY! });
const me = await platform.whoami();
A pool’s projected state. The address is required: the API serves /pool/{address} and no route that answers which address, so there is nothing to default to.
typescript
const pool = await platform.pool('3J31LrGG5Ko7iECH4o4QCdh7vXfShUHc2Npww2pCQPaH');
Ask the attestor to vouch for a confirmed swap and hand back a register_trade transaction missing only the owner’s signature. Check `facts` before you countersign — the signature covers them.
typescript
const result = await platform.attest({
owner, pool, policyIndex: 0n, tradeIndex: 3n, tokenMint, swapSignature,
});
if (result.status === 'refused') return result.reason; // 200, not an error
// Deserialise, sign, send. Do not rebuild it — any change voids the attestor's signature.
A trade under a policy. The index is not a free choice — it is the policy’s trades_used, and it is a PDA seed, so the number you send has to be the one the program will derive.
findWalletExposure(programId, pool, owner): [PublicKey, number]
The per-wallet exposure aggregate register_trade requires. init_if_needed, so a bot’s first insured trade creates it and the rest reuse it.
typescript
const [exposure] = findWalletExposure(
new PublicKey(programId), new PublicKey(pool), owner.publicKey,
);
findSwapCover
findSwapCover(programId, pool, swapSignature: Uint8Array): [PublicKey, number]
The one-cover-per-swap marker. The 64-byte signature is split across two seeds rather than hashed, because a seed caps at 32 bytes and a split is a bijection — so this address cannot be resolved from the IDL and every caller must pass it explicitly.
typescript
import { findSwapCover } from '@apecover/core/solana';
import { swapSignatureBytes } from '@apecover/common';
// swapSignatureBytes, not a bare base58 decode: it is the same decoder the program's seeds
// are derived from, and two implementations of "is this a signature" is how one swap ended
// up with two cover markers (DEG-155).
const [cover] = findSwapCover(
new PublicKey(programId),
new PublicKey(pool),
swapSignatureBytes(swapSignature),
);
The premium and payout for one trade, with the same arithmetic the program uses. Takes a PoolQuoteContext — the pool’s parameters resolved for one tier — not a pool: the tier is baked into the context rather than passed here.
What share actually routes to a partner: the pool’s partnerFeeBps when an active Partner is named, and 0n otherwise. create_policy folds the unrouted share into reserves, so a quote that assumes the fee is always charged overstates the partner’s cut and understates the pool’s.
Every reason the program would refuse this cover, computed locally against the same checks register_trade makes. An empty list means it would be accepted at those figures.
typescript
const result = checkEligibility(
ctx,
300_000_000n, // this trade
9_500_000_000n, // market cap in micro-USD
300_000_000n, // what the policy already covers per trade
);
if (!result.eligible) console.log('would be refused:', result.reasons);
utilizationBps
utilizationBps(view: Pick<PoolView, "totalLiabilities" | "vaultReserves">): number
How much of the balance sitting in the vault is already committed, in basis points. Not the figure capacity refusals turn on — see backingUtilizationBps for that.
typescript
const pool = await fetchPool(connection, programId);
const used = utilizationBps(pool!); // 4200 = 42%
backingUtilizationBps
backingUtilizationBps(view: Pick<PoolView, "totalLiabilities" | "vaultReserves" | "deployedLamports" | "unclaimedUnderwriterRewards">): number
Utilisation over the capital register_trade sizes its ceiling on: deployed capital counts, because it can be recalled, and unclaimed underwriter yield does not, because it is owed. This is the one to gate on.
typescript
const pool = await fetchPool(connection, programId);
// Headroom the program would agree with, in basis points.
const headroom = Math.max(0, pool!.maxUtilizationBps - backingUtilizationBps(pool!));
Read and decode pool state from the chain. Note it takes the **program id** and derives the pool itself — settlementMint defaults to native SOL. Returns null when the program has not been initialised on this cluster, which is a normal state and not an error.
typescript
import { Connection } from '@solana/web3.js';
import { fetchPool } from '@apecover/core/solana';
const connection = new Connection(process.env.RPC_URL!, 'confirmed');
const pool = await fetchPool(connection, process.env.APECOVER_PROGRAM_ID!);
if (pool === null) throw new Error('no pool on this cluster');
Helpers
declineFor
declineFor(error: unknown): Declined | null
Turn a thrown program error into a decline with a reason, or null when the error is something the caller has to fix rather than something the protocol refused.
typescript
try {
await ape.insureTrade(params);
} catch (error) {
const declined = declineFor(error);
if (declined === null) throw error; // a real fault — do not swallow it
console.log('refused:', declined.reason, declined.detail);
}
sourceVariant
sourceVariant(kind: PriceSourceKind): string
The IDL’s own spelling of a price source. Throws on an unknown kind rather than defaulting — a wrong-but-accepted source once shipped a quote no production pool takes.
typescript
import { PriceSourceKind, sourceVariant } from '@apecover/sdk';
const variant = sourceVariant(PriceSourceKind.Pyth); // → the IDL variant name
Chain adapters
EvmChainAdapter
new EvmChainAdapter(options: EvmAdapterOptions): EvmChainAdapter
The EVM half of the chain seam: contract calldata and pool ids, with no Solana runtime in the import graph. `addressFor` returns a decimal id for a policy, a trade or a claim, because on this chain one contract is one pool and those are mapping keys.
typescript
import { EvmChainAdapter } from '@apecover/core/evm';
import { evmAddress } from '@apecover/common';
const adapter = new EvmChainAdapter({
chain: 'robinhood-testnet',
pool: evmAddress('0x932e54A4929f156154dABa61d30abe5172542370'),
});
const call = adapter.buildCreatePolicy({
owner: evmAddress('0x00000000000000000000000000000000000000a1'),
tier: 1, packSize: 10, coveredTradeSize: 1_000n,
premium: 30_000_000_000_000_000n, // what the quote said; see below
});
// { kind, to, data, value } — sign it with whatever wallet you already have.
//
// `premium` is required on a native pool and is the *value* of the call, not an
// argument in it: createPolicy is payable, the contract recomputes the premium
// from its own tier table, and it reverts NativeValueMismatch() on any other
// amount — zero included. On an ERC-20 pool the allowance pays and value is 0.
adapter.addressFor({ kind: 'policy', index: 3n }); // '3', not an address
SolanaChainAdapter
new SolanaChainAdapter(options: SolanaAdapterOptions): SolanaChainAdapter
The Solana half of the same interface, over the PDA and instruction builders. Returns TransactionInstructions where the EVM one returns calls — the two are not the same object, and the adapter is generic in its call type rather than flattening them.
typescript
import { SolanaChainAdapter } from '@apecover/core/solana';
const adapter = new SolanaChainAdapter({
programId: '4xhLjuNsJPE4XssmJTYhL7d5VHhHTs4S8yKSVXSbxnU8',
pool: '3J31LrGG5Ko7iECH4o4QCdh7vXfShUHc2Npww2pCQPaH',
});
adapter.addressFor({ kind: 'vault' }); // a base58 PDA, not an id
Endpoint reference
15 endpoints, generated from the service’s own route table. A field marked ? is optional.
These are the endpoints an API key can call, and they are the whole of what an integration touches.
https://apecover.io/api/openapi.json generates from the same route table as this page, and is wider than it: the document is the deployment’s own inventory, so it also carries routes that are served without being part of the integration contract.
GET/health
Liveness and projection freshness
Always 200 while the process is up. The body says how far the projection has got, which is the question that actually matters.
Response
Field
Type
Notes
status
ok | degraded
degraded when anything in degradedReasons is present: an incomplete projection, or a claim path this deployment cannot serve. The process is up either way — this is not a liveness signal
Why status is degraded, as a closed set of machine-readable causes. Empty exactly when status is ok. Read this rather than status when you care about one condition: orphaned-events is what status alone meant before the claim path joined it
asOfSlot
string
Highest slot folded into this answer
asOfTs
integer
Server time when the answer was produced, in unix seconds
eventsApplied
integer
Chain events folded into the projection since it was built
orphanedEvents
integer
Trade mutations arriving for a trade the projection never registered. Non-zero means the backfill started too late and every count here undercounts. Zero is not a completeness guarantee: this counts that one class only, and pool, keeper-registry and attestor-registry rows are created as empty skeletons on first mention and counted as applied
chains
string[]
The chains this deployment answers for. A chain not listed here returns 503
chainSurfaces
object
Per chain, which API surfaces it can answer. A false here is the 503 that route would give, published before the request rather than after it
chainContracts
object
Per chain, the single contract this deployment’s pool lives at, or null where a pool is not a contract (Solana, where it is a PDA the client derives)
cosign
object
The posture of the attestor-to-attestor co-sign edge
cosign.mode
open | token-required
open — any caller may ask this deployment to co-sign. token-required — a bearer token is checked. Neither is wrong; being in the one you did not choose is
evmAttestor
object | null
Whether trades on the EVM chain can be attested here
attestor — this deployment holds the key the pool names, the pool has not adopted its attestor registry, and registerTrade will accept what it signs. no-key — none is configured. not-the-attestor — a key is held and the pool names another address. quorum-adopted — the pool has adopted its attestor registry, so registerTrade refuses any single attestor’s signature and registration takes registerTradeQuorum co-signatures this deployment does not produce. unread — the pool could not be asked, which is not evidence either way
evmAttestor.address
string | null
The address this deployment’s key implies, or null when it holds none
evmAttestor.poolAttestor
string | null
What the pool says its trade attestor is, or null when it was not read
claimPath
available | unconfigured | unreachable
available when a watcher proof surface is configured and answering; unconfigured when none is wired, in which case every /trades/:address/proof answers 503; unreachable when one is wired and does not answer, in which case they answer 502. No claim can be filed on this deployment in either of the last two states
proofChain ?
object
The EVM proof chain, link by link (RH5-05). Present only when this deployment indexes an EVM chain. A trader who cannot get a proof is told which link is down, rather than seeing an empty page
proofChain.healthy
boolean
True only when every link below is ok
proofChain.broken
string[]
Names of the links that are not ok, in declaration order
proofChain.links
object[]
Every link in the proof chain, present and absent alike
GET/pool/:addressChain-scoped
Pool state as of the latest indexed slot
Parameters
Field
Type
Notes
chain ?
solana | robinhood | robinhood-testnet
Which chain the identifiers in this request belong to. Absent means `solana`, which is a compatibility promise: every call written before this parameter existed keeps its meaning. A present but unrecognised value is a 400 rather than a default. A chain this deployment does not index is a 503, which is a statement about the deployment and not about whether the resource exists.
address
string
Path parameter
Response
Field
Type
Notes
asOfSlot
string
Highest slot folded into this answer
asOfTs
integer
Server time when the answer was produced, in unix seconds
address
string
A base58 Solana address, or a 0x-prefixed EVM address
admin
string
A base58 Solana address, or a 0x-prefixed EVM address
vault
string
A base58 Solana address, or a 0x-prefixed EVM address
settlementMint
string | null
A base58 Solana address, or a 0x-prefixed EVM address
paused
boolean
Whether the pool is halted. While true the program refuses new policies, trade registrations, claim filings, and withdrawals of underwriter stake or protocol fees, and no filed claim is paid or rejected until the pool is unpaused. Keeper votes are still recorded, and the time spent paused is added back to the claim deadline of every trade, so a pause voids no cover already sold
totalPremiums
string
A native amount in the chain’s base unit, as a decimal string — lamports on Solana, wei on an EVM chain. A decimal string because u64 and u256 do not survive JSON numbers. See `nativeUnit` on the response for which unit this is
totalLiabilities
string
A native amount in the chain’s base unit, as a decimal string — lamports on Solana, wei on an EVM chain. A decimal string because u64 and u256 do not survive JSON numbers. See `nativeUnit` on the response for which unit this is
totalPaid
string
A native amount in the chain’s base unit, as a decimal string — lamports on Solana, wei on an EVM chain. A decimal string because u64 and u256 do not survive JSON numbers. See `nativeUnit` on the response for which unit this is
totalContributed
string
A native amount in the chain’s base unit, as a decimal string — lamports on Solana, wei on an EVM chain. A decimal string because u64 and u256 do not survive JSON numbers. See `nativeUnit` on the response for which unit this is
totalWithdrawn
string
A native amount in the chain’s base unit, as a decimal string — lamports on Solana, wei on an EVM chain. A decimal string because u64 and u256 do not survive JSON numbers. See `nativeUnit` on the response for which unit this is
policiesIssued
integer
Policies created against this pool, over all time
tradesRegistered
integer
Trades ever covered by this pool, over all time — not the live count
claimsPaid
integer
Claims that settled in the trader’s favour, over all time
claimsRejected
integer
Claims a verifier refused, over all time. A rejection forfeits the claimant’s bond
liveExposure
string
A native amount in the chain’s base unit, as a decimal string — lamports on Solana, wei on an EVM chain. A decimal string because u64 and u256 do not survive JSON numbers. See `nativeUnit` on the response for which unit this is
expiryBacklog
string | null
A native amount in the chain’s base unit, as a decimal string — lamports on Solana, wei on an EVM chain. A decimal string because u64 and u256 do not survive JSON numbers. See `nativeUnit` on the response for which unit this is
reserveBufferBps
integer | null
Cushion held above open liability before an admin may withdraw anything, in bps. The program enforces a floor of 3000 (30%). null when the pool parameters could not be read from chain — not a zero cushion
totalUnderwritten
string
Underwriter principal currently staked, in the settlement asset’s base units
underwriterShares
string
Shares outstanding, as a decimal string — u128 on chain. totalUnderwritten divided by this is the share price; a socialised loss moves the numerator alone
unclaimedUnderwriterRewards
string
Yield accrued to underwriters and not yet withdrawn — read from the pool account where this deployment has read it, otherwise summed from the events that credit it, which is exact only on a log that holds every one of them
underwriterLossesSocialised
string
Payout ever borne by underwriter capital, over all time. Non-zero means claims have exceeded the pool’s own reserves and every position was repriced
GET/pool/:address/termsChain-scoped
A pool contract’s own terms, read live from the chain
For a chain whose pool is a **contract**. Its own path rather than a second shape for `/pool/:address`, because that document is a Solana pool *account* — vault, settlement mint, parameter block — and a contract has none of those fields. Asking here about a Solana pool is refused with a pointer to the route that answers. These are live reads rather than projected state, so `asOfTs` is the age of the answer, not of an index.
Parameters
Field
Type
Notes
chain ?
solana | robinhood | robinhood-testnet
Which chain the identifiers in this request belong to. Absent means `solana`, which is a compatibility promise: every call written before this parameter existed keeps its meaning. A present but unrecognised value is a 400 rather than a default. A chain this deployment does not index is a 503, which is a statement about the deployment and not about whether the resource exists.
address
string
Path parameter
Response
Field
Type
Notes
chain
solana | robinhood | robinhood-testnet
The chain this pool contract is deployed on
pool
string
The contract these terms were read from
asOfTs
integer
When the read landed. These are live contract reads, not projected state, so this is the age of the answer rather than of an index
paused
boolean
Whether the pool is selling. A paused pool also stops every coverage clock
settlementAsset
string
The zero address for a pool that settles natively (ADR-0015)
The three coverage tiers, in `Types.CoverageTier` order
perTradeCap
string
The hard per-trade payout ceiling, in the settlement asset’s base units
minTradeSize
string
The smallest trade this pool will cover, in base units
maxTradeSize
string
The largest trade this pool will cover, in base units
protocolFeeBps
integer
The protocol’s share of a premium
partnerFeeBps
integer
An integrating partner’s share of a premium it referred
underwriterFeeBps
integer
The underwriters’ yield share
maxUtilizationBps
integer
How much of the reserves may back live cover before sales stop
totalLiabilities
string
What the pool would owe if every live trade rugged, in base units
reserves
string
The accounted treasury figure solvency is measured against (ADR-0015 §5)
GET/tradesChain-scoped
Insured trades, filterable and paginated
Parameters
Field
Type
Notes
chain ?
solana | robinhood | robinhood-testnet
Which chain the identifiers in this request belong to. Absent means `solana`, which is a compatibility promise: every call written before this parameter existed keeps its meaning. A present but unrecognised value is a 400 rather than a default. A chain this deployment does not index is a 503, which is a statement about the deployment and not about whether the resource exists.
limit ?
integer
Default 50, max 200
offset ?
integer
Query parameter
owner ?
string
Filter to one trader
status ?
registered | claimed | paid | rejected | expired
Filter by lifecycle status
tokenMint ?
string
A base58 Solana address, or a 0x-prefixed EVM address
Response
Field
Type
Notes
asOfSlot
string
Highest slot folded into this answer
asOfTs
integer
Server time when the answer was produced, in unix seconds
trades
object[]
This page of trades, newest first. Page with limit and offset
total
integer
Trades matching the filter across every page, not the length of this one
GET/trades/:addressChain-scoped
One insured trade
Parameters
Field
Type
Notes
chain ?
solana | robinhood | robinhood-testnet
Which chain the identifiers in this request belong to. Absent means `solana`, which is a compatibility promise: every call written before this parameter existed keeps its meaning. A present but unrecognised value is a 400 rather than a default. A chain this deployment does not index is a 503, which is a statement about the deployment and not about whether the resource exists.
address
string
Path parameter
Response
Field
Type
Notes
asOfSlot
string
Highest slot folded into this answer
asOfTs
integer
Server time when the answer was produced, in unix seconds
trade
object
The trade at the requested address
trade.chain ?
solana | robinhood | robinhood-testnet
Which chain the identifiers in this request belong to. Absent means solana.
trade.settled ?
boolean
Whether this row is final, or still reorg-able. Always true on Solana, where the watcher reads at confirmed and a rollback needs a supermajority fork. On an EVM chain a row above the settlement depth can still be retracted, and false is the ordinary state there rather than an incident — treat a provisional row as provisional
trade.address
string
A Solana account address, an EVM contract address, or an EVM uint256 id as a decimal string
trade.pool
string
A Solana account address, an EVM contract address, or an EVM uint256 id as a decimal string
trade.policy
string
A Solana account address, an EVM contract address, or an EVM uint256 id as a decimal string
trade.owner
string
A base58 Solana address, or a 0x-prefixed EVM address
trade.tokenMint
string
A base58 Solana address, or a 0x-prefixed EVM address
trade.tier
string
The cover tier bought for this trade. Tier names and their windows are pool parameters, so they are read from the pool rather than fixed by this API
trade.tradeSize
string
A native amount in the chain’s base unit, as a decimal string — lamports on Solana, wei on an EVM chain. A decimal string because u64 and u256 do not survive JSON numbers. See `nativeUnit` on the response for which unit this is
trade.reservedLiability
string
A native amount in the chain’s base unit, as a decimal string — lamports on Solana, wei on an EVM chain. A decimal string because u64 and u256 do not survive JSON numbers. See `nativeUnit` on the response for which unit this is
trade.windowStart
integer
Unix second the cover window opens — the trade’s registration time
trade.windowEnd
integer
Unix second the cover window closes. Half-open: a collapse observed exactly at windowEnd is outside the cover, so "10 minutes" means 10 minutes
trade.status
registered | claimed | paid | rejected | expired
Lifecycle. registered → claimed once a claim is filed, then paid or rejected; expired means the window closed with no claim and the liability was released
trade.registeredSlot
string
Height at registration — a slot on Solana, a block number on an EVM chain. Orders trades that share a timestamp, which windowStart alone cannot. The two are different clocks and must not be compared across chains
trade.claim
string | null
A Solana account address, an EVM contract address, or an EVM uint256 id as a decimal string
trade.claimDeadline
integer | null
Unix second after which `submit_claim` refuses this trade — `windowEnd + graceSeconds`, plus any extension a pool pause added (DEG-97). Served rather than left to the caller because the pause term needs the pool’s paused_seconds_at(now) and the trade’s own paused_seconds_at_registration, and a client that computes windowEnd + graceSeconds reports a trade as too late to file while the program would still accept the claim. null when this deployment cannot read the pool’s grace period, and on a chain whose projection does not model the extension — null says the deadline is unknown, which is not the same as a deadline that has passed
trade.payout
string
A native amount in the chain’s base unit, as a decimal string — lamports on Solana, wei on an EVM chain. A decimal string because u64 and u256 do not survive JSON numbers. See `nativeUnit` on the response for which unit this is
trade.rejectionReason
string | null
Why a verifier refused the claim. Non-null only when status is rejected
trade.entryPrice
object | null
The entry quote the chain attested to when this trade was registered — the price every collapse is measured against, and the authority for it rather than an off-chain reading. null on a chain whose registration event does not carry it: on 4663 the attested price travels in an EIP-712 message rather than in the log, so the projection genuinely does not have it and null says so rather than a zero that would read as a total collapse
trade.entryPrice.price
string
Mantissa, paired with expo. A decimal string: u64 does not survive JSON numbers
trade.entryPrice.expo
integer
Decimal exponent. The price is price × 10^expo
trade.entryPrice.conf
string
The source's own confidence interval, bounded on chain by max_price_conf_bps
trade.entryPrice.publishTs
integer
When the oracle published, in unix seconds — not when the attestor read it
trade.entryPrice.source
string
Mock, Pyth, Jupiter or DexPoolReserves. A production pool refuses Mock
trade.entryPrice.quoteMint
string | null
The asset this price is quoted in, or null for a source quoting an absolute unit
trade.swapSignature
string | null
The swap this cover attaches to, base58. null for a trade registered before the field existed — never an empty string, which would hash as cleanly as a real one in a proof bundle naming a swap that does not exist
GET/trades/:address/proofChain-scoped
The claim-submission kit for a rugged trade
The proof digest `submit_claim` takes, the CID the sealed evidence bundle is retrievable at, and the deadline the claim must be filed by. Relayed from the watcher that built the proof: 404 while no proof is held for the trade (not yet ruled, not a rug, or retention passed), 502 when the watcher cannot be reached, 503 on a deployment that has no watcher configured.
Parameters
Field
Type
Notes
chain ?
solana | robinhood | robinhood-testnet
Which chain the identifiers in this request belong to. Absent means `solana`, which is a compatibility promise: every call written before this parameter existed keeps its meaning. A present but unrecognised value is a 400 rather than a default. A chain this deployment does not index is a 503, which is a statement about the deployment and not about whether the resource exists.
address
string
Path parameter
Response
Field
Type
Notes
trade
string
A base58 Solana address, or a 0x-prefixed EVM address
tokenMint
string
A base58 Solana address, or a 0x-prefixed EVM address
digest
string
Lowercase hex of the sha2-256 digest the on-chain claim commits to. Pass digestBytes, not this, to submit_claim
digestBytes
integer[]
The same digest as the 32-byte array submit_claim takes
cid
string | null
Content address of the pinned evidence bundle, fetchable from any IPFS gateway. null for a proof built but not yet pinned — the digest is still valid
retainUntil
integer
Last unix second submit_claim will accept a claim on this trade. Past it the proof is released and the claim can no longer be filed
collapseBps
integer
How far the price fell from entry, in basis points — 10000 is a fall to zero. 0 when the exit price met or beat entry, so there is nothing to claim
windowStart
integer
Unix second the covered window opened, copied from the trade
windowEnd
integer
Unix second the covered window closed, copied from the trade
builtAt
integer
Unix second the watcher sealed this bundle. Not a freshness signal for the projection — this route is relayed live and carries no asOfSlot
settleable
boolean | null
true when this proof’s collapse reaches the pool’s rug threshold, so a claim on it can be voted on and paid. false when it cannot — filing anyway forfeits the claim bond. null when the pool threshold could not be read
GET/policiesChain-scoped
Issued policies
Parameters
Field
Type
Notes
chain ?
solana | robinhood | robinhood-testnet
Which chain the identifiers in this request belong to. Absent means `solana`, which is a compatibility promise: every call written before this parameter existed keeps its meaning. A present but unrecognised value is a 400 rather than a default. A chain this deployment does not index is a 503, which is a statement about the deployment and not about whether the resource exists.
limit ?
integer
Default 50, max 200
offset ?
integer
Query parameter
owner ?
string
A base58 Solana address, or a 0x-prefixed EVM address
Response
Field
Type
Notes
asOfSlot
string
Highest slot folded into this answer
asOfTs
integer
Server time when the answer was produced, in unix seconds
policies
object[]
This page of policies. Page with limit and offset
total
integer
Policies matching the filter across every page, not the length of this one
GET/claimsChain-scoped
Claims and their outcomes
Parameters
Field
Type
Notes
chain ?
solana | robinhood | robinhood-testnet
Which chain the identifiers in this request belong to. Absent means `solana`, which is a compatibility promise: every call written before this parameter existed keeps its meaning. A present but unrecognised value is a 400 rather than a default. A chain this deployment does not index is a 503, which is a statement about the deployment and not about whether the resource exists.
limit ?
integer
Default 50, max 200
offset ?
integer
Query parameter
status ?
claimed | paid | rejected | expired
Query parameter
owner ?
string
Filter to one trader
Response
Field
Type
Notes
asOfSlot
string
Highest slot folded into this answer
asOfTs
integer
Server time when the answer was produced, in unix seconds
claims
object[]
This page of claims. Page with limit and offset
total
integer
Claims matching the filter across every page, not the length of this one
GET/partnersChain-scoped
On-chain partner registrations and revenue share
The chain’s answer, not this API’s: who is registered on the pool contract, their limits, and what the premium split has accrued to them. `/partner/me` is the other thing — this deployment’s own account record for the caller. A partner can have an approved application and no on-chain registration, which looks live and earns nothing.
Parameters
Field
Type
Notes
chain ?
solana | robinhood | robinhood-testnet
Which chain the identifiers in this request belong to. Absent means `solana`, which is a compatibility promise: every call written before this parameter existed keeps its meaning. A present but unrecognised value is a 400 rather than a default. A chain this deployment does not index is a 503, which is a statement about the deployment and not about whether the resource exists.
limit ?
integer
Default 50, max 200
offset ?
integer
Query parameter
active ?
boolean
Filter to partners currently accepting sales
address ?
string
Filter to one partner. What a caller asking “am I registered on this chain” wants — paging a registry to find one row is a different request that happens to contain the answer, and on a busy pool it is one the default page size would not contain at all
Response
Field
Type
Notes
asOfSlot
string
Highest slot folded into this answer
asOfTs
integer
Server time when the answer was produced, in unix seconds
partners
object[]
This page of partners. Page with limit and offset
total
integer
Partners matching the filter across every page, not the length of this one
GET/statsChain-scoped
Protocol-wide figures for one chain
Parameters
Field
Type
Notes
chain ?
solana | robinhood | robinhood-testnet
Which chain the identifiers in this request belong to. Absent means `solana`, which is a compatibility promise: every call written before this parameter existed keeps its meaning. A present but unrecognised value is a 400 rather than a default. A chain this deployment does not index is a 503, which is a statement about the deployment and not about whether the resource exists.
Response
Field
Type
Notes
asOfSlot
string
Highest slot folded into this answer
asOfTs
integer
Server time when the answer was produced, in unix seconds
chain
string
Which chain these figures are about
pools
integer
How many pools this answer covers. Exactly one on an EVM chain — a deployment has one pool contract. Several on Solana, where a pool is an account anyone may create
pausedPools
integer
How many of them are halted. A count rather than a flag: “paused” would have to mean any or all, and both are wrong on a chain with a different number of pools
totalPremiums
string
A native amount in the chain’s base unit, as a decimal string — lamports on Solana, wei on an EVM chain. A decimal string because u64 and u256 do not survive JSON numbers. See `nativeUnit` on the response for which unit this is
totalLiabilities
string
Derived here by summing cover that still holds liability
reportedLiabilities
string | null
What the chain itself last stated, as of reportedAtHeight. Null before it has said anything
reportedAtHeight
string | null
Slot or block reportedLiabilities is as of — the same clock as asOfSlot
reserves
string | null
Backing the pool, where an event reports it. Null is “no event has stated it” and never a guess at the contract balance — a plain transfer in moves the balance and emits nothing
reservesAtHeight
string | null
Slot or block reserves is as of. Null under the same condition reserves is
policiesCreated
integer
Policies ever bought, from the chain’s own lifetime counters
tradesRegistered
integer
Trades ever registered against those policies, lifetime
activeCover
integer
Trades whose window is still open
holdingLiability
integer
Trades still reserving liability, open or under claim
claimsSubmitted
integer
Claims ever filed
claimsPaid
integer
Claims settled in the claimant’s favour
claimsRejected
integer
Claims a verifier refused. The bond is forfeited on this path
claimsExpired
integer | null
Claims that lapsed with nobody ruling, counted off ClaimExpired. Null where the chain does not fold that ending, since a 0 there would assert something its projection does not support
totalPaid
string
A native amount in the chain’s base unit, as a decimal string — lamports on Solana, wei on an EVM chain. A decimal string because u64 and u256 do not survive JSON numbers. See `nativeUnit` on the response for which unit this is
bondsForfeited
string | null
Claim bonds forfeited to reserves by a rejection. Income, but not premium. Null where no event carries it
GET/account/:ownerChain-scoped
One account’s policies, cover and claims
Zeros for an address with nothing on it, never a 404. Every address is a valid subject here — an account is not a thing that has to have been created — so “never seen” and “holds nothing” are the same answer to the only question being asked.
Parameters
Field
Type
Notes
chain ?
solana | robinhood | robinhood-testnet
Which chain the identifiers in this request belong to. Absent means `solana`, which is a compatibility promise: every call written before this parameter existed keeps its meaning. A present but unrecognised value is a 400 rather than a default. A chain this deployment does not index is a 503, which is a statement about the deployment and not about whether the resource exists.
owner
string
Path parameter
Response
Field
Type
Notes
asOfSlot
string
Highest slot folded into this answer
asOfTs
integer
Server time when the answer was produced, in unix seconds
chain
string
Which chain these figures are about
owner
string
A base58 Solana address, or a 0x-prefixed EVM address
policies
integer
Policy packs this account has bought
tradesAvailable
integer | null
Credits bought across those policies. Null where no policy’s pack size is known — a zero would read as “no credits left”, which is a different thing
tradesRegistered
integer
Credits this account has spent — trades registered against its packs
activeCover
integer
This account’s trades whose coverage window is still open
holdingLiability
integer
This account’s trades still reserving liability against the pool, open or under claim
reservedLiability
string
What the pool is holding against this account’s live cover
premiumsPaid
string
What this account has spent on packs, in total
claimsSubmitted
integer
Claims this account has ever filed, expired ones included
claimsPaid
integer
Of those, the ones settled in its favour
claimsRejected
integer
Of those, the ones a verifier refused
totalPaid
string
What this account has been paid out, in total
POST/quote
Price a policy and pre-check whether the program would accept it
Reads pool parameters from chain rather than from the indexed projection, because a quote is a number the caller is about to act on. Amounts are decimal strings: a u64 served as a JSON number loses precision past 2^53. The split prices an unrouted purchase: `partnerFee` is zero and the pool’s partner share is included in `toReserve`, matching what `create_policy` charges when no `Partner` account is named. Send `flatPremium` to price the flat-per-trade mode instead of the tier percentage; `quote.floor` is the tier price either way, and the program will not sell below it.
Request body
Field
Type
Notes
chain ?
solana | robinhood | robinhood-testnet
Which chain the identifiers in this request belong to. Absent means solana.
pool
string
The pool to quote against
tokenMint
string
The token being traded. Priced against the pool’s entry gate, so two mints can quote differently at the same trade size
tradeSize
string
Notional per covered trade, in the pool’s settlement base unit as a decimal string. This is the size each trade in the pack may cover, not the total across the pack
tier
integer
0 Basic, 1 Standard, 2 DegenMax
packSize
integer
Trades in the pack. Must be one of 1, 10, 20, 50, 100
marketCapMicroUsd ?
string
Current market cap in micro-USD (1_000_000 = $1), as a decimal string — not lamports. Feeds the entry gate. Omit it and this deployment looks the figure up; if it cannot, the gate does not run and marketCap comes back null
flatPremium ?
string
Price this as `PremiumMode::Flat`: a fixed premium **per trade**, in the pool’s settlement base unit, so the pack costs packSize × this. Omit for the tier percentage. The program refuses a pack total below the tier price for the same size — `quote.floor` is that number, and `flat-premium-below-floor` is the issue when it binds
Response
Field
Type
Notes
eligible
boolean
Whether the program would accept this policy right now
issues
object[]
Empty when eligible
quote
object | null
The price, present whenever the pool is known — even when eligible is false, because a caller deserves the number they were refused at. null only for a pool this deployment cannot read
quote.premium
string
Total the buyer pays
quote.protocolFee
string
An amount in the pool’s settlement base unit, as a decimal string — lamports on Solana, wei on a native EVM pool, the token’s own base unit on an ERC-20 one. A string because neither u64 nor u256 survives a JSON number; see `evm.settlementDecimals` for which
quote.partnerFee
string
Zero unless the policy is created naming an active on-chain `Partner` account. This endpoint prices an unrouted purchase, which is what `create_policy` charges when no partner is passed — the pool's `partnerFeeBps` share goes to reserves.
quote.underwriterFee
string
An amount in the pool’s settlement base unit, as a decimal string — lamports on Solana, wei on a native EVM pool, the token’s own base unit on an ERC-20 one. A string because neither u64 nor u256 survives a JSON number; see `evm.settlementDecimals` for which
quote.toReserve
string
An amount in the pool’s settlement base unit, as a decimal string — lamports on Solana, wei on a native EVM pool, the token’s own base unit on an ERC-20 one. A string because neither u64 nor u256 survives a JSON number; see `evm.settlementDecimals` for which
quote.perTradeCap
string
An amount in the pool’s settlement base unit, as a decimal string — lamports on Solana, wei on a native EVM pool, the token’s own base unit on an ERC-20 one. A string because neither u64 nor u256 survives a JSON number; see `evm.settlementDecimals` for which
quote.payoutPerTrade
string
Paid if a covered trade rugs
quote.liabilityPerTrade
string
Reserved against the pool per registered trade
quote.floor
string
The tier price for this pack — what a percentage-priced policy costs, and the minimum a flat one may pay. Equal to `premium` unless `flatPremium` was sent
availableCapacity
string | null
An amount in the pool’s settlement base unit, as a decimal string — lamports on Solana, wei on a native EVM pool, the token’s own base unit on an ERC-20 one. A string because neither u64 nor u256 survives a JSON number; see `evm.settlementDecimals` for which
marketCap
object | null
The market cap the entry gate was checked against, and where it came from. null means the gate did not run at all — materially different from a figure that passed, and not something to round up to "eligible"
marketCap.microUsd
string
An amount in the pool’s settlement base unit, as a decimal string — lamports on Solana, wei on a native EVM pool, the token’s own base unit on an ERC-20 one. A string because neither u64 nor u256 survives a JSON number; see `evm.settlementDecimals` for which
marketCap.source
string
`caller` when supplied in the request, otherwise the provider that answered
chain ?
solana | robinhood | robinhood-testnet
The chain this quote is for
evm
object | null
What it costs to act on this quote and what has to happen first — gas, whether an approval is required, and the settlement asset. null on Solana, where the questions do not arise; the economics above are identical on both chains
evm.chainId
integer
EIP-155 chain id — 4663 mainnet, 46630 testnet
evm.settlementAsset
string
What the pool settles in: the zero address for native ETH (ADR-0001’s counterpart), otherwise the ERC-20. Every amount in this quote is in this asset’s base units
evm.settlementSymbol
string
For display only. "ERC-20" when the token has no readable symbol(). Do not key logic on it
evm.settlementDecimals
integer
Decimals of the settlement asset. 18 for native ETH; for an ERC-20, read from the token and never assumed: the whole evm block is null when they cannot be read. A client that assumes 9, as it would on Solana, is wrong by a factor of a billion
evm.approvalRequired
boolean
Whether the buyer must approve before createPolicy can take the premium. False for a native-settled pool, which takes value with the call. A bot that discovers this after building its transaction has already lost the race
evm.approvalTarget
string | null
The spender to approve — the pool — or null when no approval is needed
evm.gas
object
Gas ceilings, measured against the contracts and rounded up
evm.gas.createPolicy
string
Gas ceiling for createPolicy, measured against the contracts and rounded up
evm.gas.registerTrade
string
Gas ceiling for registerTrade, measured against the contracts and rounded up
evm.gas.approve
string | null
null when no approval is required
evm.gas.totalLimit
string
createPolicy + registerTrade + approve
evm.gasPriceWei
string
The price this estimate was costed at, in wei. Read live — a compiled-in price fails silently in the expensive direction, producing a transaction that will not mine
evm.estimatedFeeWei
string
totalLimit × gasPriceWei. A ceiling on the fee, not a prediction of it
asOfTs
integer
Server time this quote was produced, in unix seconds. Prices move; re-quote rather than caching this
GET/v1/whoamiAPI key
Check an API key and see what it is attached to
The first call to make with a new key. Send it as `Authorization: Bearer <keyId>.<secret>`, or in `X-API-Key`. Answers 401 for a key that is not valid and 403 for one belonging to a suspended or rejected partner — the distinction matters, because a new key fixes the first and not the second.
Response
Field
Type
Notes
partner
object
The integration this key belongs to. Read earning, not status, to decide whether revenue share is actually accruing
partner.id
string
Your partner id. Stable, and safe to log
partner.label
string
The integration name you registered
partner.status
pending | approved | rejected | suspended
pending, approved, rejected or suspended
partner.onchainPartner
string | null
Your on-chain Partner account, or null if an operator has not registered one yet
partner.earning
boolean
Whether revenue share is accruing. Approved alone is not enough — the on-chain account has to exist, because attribution is the policy.partner field
keyId
string
The key that authenticated this request
rateLimit
object
Budget for this key as of this response. A 429 carries the same figures in headers
rateLimit.remaining
integer
Requests left in the current window
rateLimit.resetsAt
integer
Unix seconds when the window rolls
POST/v1/attestAPI key
Attest a swap and get back a signed register_trade transaction
The attestor holds the pool’s trade_attestor key (ADR-0003) and builds the instruction itself. It does not co-sign a transaction you supply — that would be blind-signing with the key the protocol’s entry prices rest on. Send the swap; the price, the market cap, the token and the size are all measured here, and a size larger than the swap actually spent is refused. Authenticate with an API key, or with a session cookie from the dApp.
Request body
Field
Type
Notes
owner
string
The wallet that signed the swap. It will own the cover and must sign the returned transaction — an attestation is issued for one wallet and is useless to any other
pool
string
The pool to register against
policyIndex
string
Which of this wallet’s policies in this pool to spend a credit from
tradeIndex
string
Position within that policy. Also a PDA seed, so it cannot be reused
tokenMint
string
The token bought
swapSignature
string
The swap being insured. Read from chain — its size, its token and its signer are measured, not taken from this request
tradeSize ?
string
Lamports to insure. Defaults to everything the swap spent, and may not exceed it
Response
Field
Type
Notes
status
"signed"
Always "signed" on this shape. A refusal is also 200, with status "refused" — branch on this field, not on the HTTP status
transaction
string
The register_trade transaction, base64, signed by the attestor and missing only the owner’s signature. Deserialise, sign, send — do not rebuild it, because any change invalidates the attestor’s signature
attestor
string
The key that authored this attestation. On a legacy pool it equals the pool’s trade_attestor and has already co-signed the transaction; on a quorum pool it is the proposing attestor, whose signature rides in the ed25519 instruction instead
coSigners ?
string[]
Quorum pools only: every attestor whose detached signature is packed into the transaction’s ed25519 verify instruction, proposer included, in the order packed
blockhash
string
The blockhash the transaction was built against. Sign and send promptly — it is what gives the attestation its lifetime
lastValidBlockHeight
integer
Past this block height the transaction is dead and a new attestation is needed
accounts
object
The addresses this transaction will touch, derived so a caller can watch for them without decoding the transaction
accounts.policy
string
The Policy the credit is spent from — existing or about to be created
accounts.trade
string
The InsuredTrade this will create
accounts.swapCover
string
The one-cover-per-swap marker (ADR-0006)
facts
object
Everything the attestor put its name to, echoed so a caller can see what they are about to countersign. Check these before signing — the signature covers them
facts.tokenMint
string
The token bought, as read from the swap on chain rather than as the caller named it
facts.tradeSize
string
Lamports insured
facts.swapLamportsSpent
string
What the swap actually spent, as measured on chain
facts.swapSlot
string
The slot the swap landed in, on the chain it was made on
facts.swapBlockTime
integer
When the swap’s block was produced, unix seconds
facts.swapAgeSeconds
integer
How old the swap was when it was attested. Bounded, because cover attaches at entry
facts.entryPrice
object
The entry the cover is measured from, as a mantissa and exponent rather than a float. Every later collapse is computed against this, so it is the single number a payout turns on
facts.entryPrice.price
string
Mantissa
facts.entryPrice.expo
integer
Base-10 exponent, so the price is price × 10^expo
facts.entryPrice.conf
string
The source’s confidence band, in the same units as the mantissa
facts.entryPrice.publishTs
integer
When the source published the price, unix seconds
facts.entryPrice.source
string
The PriceSourceKind variant recorded on chain
facts.entryPrice.quoteMint
string | null
What the price is denominated in, or null for a source quoting USD
facts.marketCapMicroUsd
string
Market cap at entry, checked against the tier’s limit
facts.attestedSlot
string
The slot the attestor observed at. Bounds how stale this may get
facts.liability
string
What the pool will reserve against this trade if it registers
swapChain
string
Which cluster the swap was read from — not necessarily the cover’s own
POST/v1/attest/cosign
Re-verify a proposed attestation and return a detached co-signature
The attestor-to-attestor half of the quorum (ADR-0009). The proposing attestor sends the attestation it intends to sign; this deployment re-reads the swap from chain and compares the price and market cap against its own sources within its own tolerances, and only then returns an ed25519 signature over the canonical digest. Not a caller endpoint: traders and bots use POST /v1/attest, which aggregates these signatures. Authentication is the operator’s choice, not an API key: with COSIGN_TOKEN set it requires Authorization: Bearer <token>, and without it the route is open to anyone who can reach it — rate-limited, and refusing every pool that is not a quorum pool.
Request body
Field
Type
Notes
pool
string
The pool the registration is for
owner
string
The wallet that will own the cover. Bound into the signed digest, so a signature collected for one wallet cannot be replayed onto another’s registration
attestations
object[]
One entry for a single registration. A batch is one digest over every entry in order, so the whole batch is co-signed or none of it is
Response
Field
Type
Notes
status
"signed"
Always "signed" on this shape. A refusal is also 200, with status "refused" — branch on this field, not on the HTTP status
attestor
string
The key that signed. Counts only while it holds an active registry seat
signature
string
64 bytes of ed25519 over the canonical digest, base64
digest
string
The sha256 digest that was signed, hex. An aggregator whose own digest differs has a canonical-encoding disagreement to fix, not a signature to pack
What this API is not
Every read here comes from the indexed projection of the event log, not from the chain directly, so it can be a few seconds behind — which is why a projection read carries a freshness stamp rather than leaving you to assume it is current. It is the right source for a dashboard and the wrong one for a decision that moves money. The keeper re-reads the chain before finalising anything, and so should you.
Read that stamp in the unit the chain counts in. On Solana it is asOfSlot; on Robinhood Chain the projection folds blocks, and a slot number and a block number are two unrelated clocks — comparing one to the other is a category error rather than a small inaccuracy. And not every route has one: the fact strip at the top of this page says which do.
/quote is the exception: it reads pool parameters from chain, because a quote is a number the caller is about to act on. On Solana. The reader it uses is a Solana account reader and there is no EVM one, which is the whole of why the price is missing from a Robinhood quote.