Menu

Developers

Build on ApeCover

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.

bash
npm install @apecover/sdk @apecover/core @apecover/common
typescript
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.

  1. Your application is accepted. Your key already worked before this; acceptance is a decision, not a capability.
  2. 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.

ScreenWhat it answersAccess
/statsLive 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
/positionsCover by address: chain, trade, token, amount insured and status. Underwriter stake is on the same card.Sign in
/claimsEvery claim against an address — trade, size, window, rug proof and where the claim itself has got to.Sign in
/dashboardYour own totals, keyed on your API keys: what each has insured, what it has earned, and what you have staked.Sign in
/docsThis page.Open
/appRegister as a partner, then issue and revoke API keys. A key is shown once, when it is issued.Sign in
The ApeCover header with a Solana and Robinhood toggle beside the page heading
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 ApeCover sign-in card asking for an email address
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 partner application form: integration name, payout address, signing wallet, website, contact email and notes
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.

BadgeWhat is trueWhat is not
In reviewYour key authenticates. Quotes, reads and attestations all work.Nothing is accruing.
Approved — not yet on chainThe 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.
EarningApproved and registered on chain. Your share accrues on every policy your key originates, and your dashboard can show it.

Approved is not the same as earning is the same distinction from the integrator’s side, with the API responses that go with it.

Registration belongs to one chain

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 on Robinhood Chain, showing a stated address and the pool registry's answer about it
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.

A dialog showing a newly issued API key with a Copy button, refusing to close until it is copied
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 keys card: status badge, payout address, on-chain account, and a table of live and revoked keys
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.

The webhook card showing a saved endpoint with Replace and Remove buttons
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.

The positions card: three insured trades in three states, one policy with credits left, and one paid claim
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 positions page showing two insured trades on Robinhood Chain testnet
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 claims page: one trade with a rug proof and a filing countdown, and one settled claim
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: a valued position with P&L, claimable yield and a lockup countdown, above the three SDK calls
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 dashboard: volume insured and revenue share, exposure headroom and rate limit, a usage chart, per-key rows and a staked card
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.
chainChain idOffered hereNotes
solanaEnabledSolana
robinhood4663Not enablednothing is deployed on 4663 yet, and the mainnet gate is unmet — see docs/MAINNET_GATE.md
robinhood-testnet46630EnabledRobinhood 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.

SolanaRobinhood ChainWhy it matters
Address shapebase58hex20Base58 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 namedbase58 PDAuint256, as a decimal stringOne 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 unitSOL · 9 dp · lamportsETH · 18 dp · weiThe 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 time0.4 s0.1008 sAnything 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 after0 s843 sHow 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.
ApprovalsnoyesAn 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.
RentyesnoA 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 feesyesnoThere 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.

quote

quote(params: QuoteParams): Promise<Record<string, unknown>>

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.

insureTrade

insureTrade(params: InsureParams): Promise<InsureOutcome>

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);

buyPolicy

buyPolicy(tier: TierName, coveredTradeSize: bigint)

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.

typescript
const chain = await ape.chain();
const vault = chain.vaultAddress();

protocol

protocol(): Promise<Protocol>

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);

withdrawStake

withdrawStake(amount: bigint): Promise<CallResult>

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();

pool

pool(address: string): Promise<Record<string, unknown>>

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');

attest

attest(params: AttestParams): Promise<AttestResult>

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.

Addresses

findPool

findPool(programId: PublicKey, settlementMint?: PublicKey): [PublicKey, number]

The pool for a settlement asset. Defaults to native SOL, which is the pool this deployment serves.

typescript
import { findPool } from '@apecover/core/solana';
import { PublicKey } from '@solana/web3.js';

const [pool] = findPool(new PublicKey(programId));

findVault

findVault(programId: PublicKey, pool: PublicKey): [PublicKey, number]

The pool’s vault — where premiums land and payouts are drawn from.

typescript
const [vault] = findVault(new PublicKey(programId), new PublicKey(pool));

findPolicy

findPolicy(programId, pool, owner, policyIndex: bigint): [PublicKey, number]

One wallet’s policy at an index. Indices are dense, so the first index with no account is the end of that wallet’s policies.

typescript
const [policy] = findPolicy(
  new PublicKey(programId), new PublicKey(pool), owner.publicKey, 0n,
);

findTrade

findTrade(programId, policy, tradeIndex: bigint): [PublicKey, number]

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.

typescript
const [trade] = findTrade(new PublicKey(programId), policy, 3n);

findWalletExposure

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),
);

findClaim

findClaim(programId: PublicKey, trade: PublicKey): [PublicKey, number]

The claim account for a trade. One per trade, created by submit_claim.

typescript
const [claim] = findClaim(new PublicKey(programId), trade);

Pricing

quoteSingleTrade

quoteSingleTrade(ctx: PoolQuoteContext, tradeSize: bigint): { premium, payout }

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.

typescript
import { quoteSingleTrade } from '@apecover/core';
import { fetchPool } from '@apecover/core/solana';

const pool = await fetchPool(connection, programId);
if (pool === null) throw new Error('no pool on this cluster');

const tier = pool.tiers[1]!;                 // 0 basic, 1 standard, 2 degen-max
const ctx = {
  premiumBps: BigInt(tier.premiumBps),
  payoutRatioBps: BigInt(tier.payoutRatioBps),
  maxEntryMarketCap: BigInt(tier.maxEntryMarketCap),
  perTradeCap: BigInt(pool.perTradeCap),
  minTradeSize: BigInt(pool.minTradeSize),
  maxTradeSize: BigInt(pool.maxTradeSize),
  protocolFeeBps: BigInt(pool.protocolFeeBps),
  partnerFeeBps: BigInt(pool.partnerFeeBps),
  underwriterFeeBps: BigInt(pool.underwriterFeeBps),
  maxUtilizationBps: BigInt(pool.maxUtilizationBps),
  totalLiabilities: BigInt(pool.totalLiabilities),
  vaultLamports: BigInt(pool.vaultReserves),
  deployedLamports: BigInt(pool.deployedLamports),
  unclaimedUnderwriterRewards: BigInt(pool.unclaimedUnderwriterRewards),
  rentExemptMinimum: BigInt(await connection.getMinimumBalanceForRentExemption(0)),
  paused: pool.paused,
};

const { premium, payout } = quoteSingleTrade(ctx, 300_000_000n);

effectivePartnerFeeBps

effectivePartnerFeeBps(ctx: Pick<PoolQuoteContext, "partnerFeeBps">, partner?): bigint

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.

typescript
effectivePartnerFeeBps(ctx);                    // no partner → 0n
effectivePartnerFeeBps(ctx, { active: true });  // → ctx.partnerFeeBps

checkEligibility

checkEligibility(ctx, tradeSize, marketCapMicroUsd, policyCoveredTradeSize): EligibilityResult

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!));

fetchPool

fetchPool(connection, programId: string, settlementMint?: PublicKey): Promise<PoolView | null>

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

FieldTypeNotes
statusok | degradeddegraded 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
degradedReasonsorphaned-events | claim-path-unconfigured | claim-path-unreachable[]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
asOfSlotstringHighest slot folded into this answer
asOfTsintegerServer time when the answer was produced, in unix seconds
eventsAppliedintegerChain events folded into the projection since it was built
orphanedEventsintegerTrade 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
chainsstring[]The chains this deployment answers for. A chain not listed here returns 503
chainSurfacesobjectPer 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
chainContractsobjectPer 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)
cosignobjectThe posture of the attestor-to-attestor co-sign edge
cosign.modeopen | token-requiredopen — 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
evmAttestorobject | nullWhether trades on the EVM chain can be attested here
evmAttestor.chainsolana | robinhood | robinhood-testnetThe EVM chain this standing is about
evmAttestor.statusno-key | attestor | not-the-attestor | quorum-adopted | unreadattestor — 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.addressstring | nullThe address this deployment’s key implies, or null when it holds none
evmAttestor.poolAttestorstring | nullWhat the pool says its trade attestor is, or null when it was not read
claimPathavailable | unconfigured | unreachableavailable 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 ?objectThe 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.healthybooleanTrue only when every link below is ok
proofChain.brokenstring[]Names of the links that are not ok, in declaration order
proofChain.linksobject[]Every link in the proof chain, present and absent alike

GET/pool/:addressChain-scoped

Pool state as of the latest indexed slot

Parameters

FieldTypeNotes
chain ?solana | robinhood | robinhood-testnetWhich 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.
addressstringPath parameter

Response

FieldTypeNotes
asOfSlotstringHighest slot folded into this answer
asOfTsintegerServer time when the answer was produced, in unix seconds
addressstringA base58 Solana address, or a 0x-prefixed EVM address
adminstringA base58 Solana address, or a 0x-prefixed EVM address
vaultstringA base58 Solana address, or a 0x-prefixed EVM address
settlementMintstring | nullA base58 Solana address, or a 0x-prefixed EVM address
pausedbooleanWhether 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
totalPremiumsstringA 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
totalLiabilitiesstringA 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
totalPaidstringA 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
totalContributedstringA 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
totalWithdrawnstringA 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
policiesIssuedintegerPolicies created against this pool, over all time
tradesRegisteredintegerTrades ever covered by this pool, over all time — not the live count
claimsPaidintegerClaims that settled in the trader’s favour, over all time
claimsRejectedintegerClaims a verifier refused, over all time. A rejection forfeits the claimant’s bond
liveExposurestringA 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
expiryBacklogstring | nullA 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
reserveBufferBpsinteger | nullCushion 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
totalUnderwrittenstringUnderwriter principal currently staked, in the settlement asset’s base units
underwriterSharesstringShares outstanding, as a decimal string — u128 on chain. totalUnderwritten divided by this is the share price; a socialised loss moves the numerator alone
unclaimedUnderwriterRewardsstringYield 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
underwriterLossesSocialisedstringPayout 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

FieldTypeNotes
chain ?solana | robinhood | robinhood-testnetWhich 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.
addressstringPath parameter

Response

FieldTypeNotes
chainsolana | robinhood | robinhood-testnetThe chain this pool contract is deployed on
poolstringThe contract these terms were read from
asOfTsintegerWhen the read landed. These are live contract reads, not projected state, so this is the age of the answer rather than of an index
pausedbooleanWhether the pool is selling. A paused pool also stops every coverage clock
settlementAssetstringThe zero address for a pool that settles natively (ADR-0015)
tradeAttestorstringWhose signature `registerTrade` requires (ADR-0003)
tiersobject[]The three coverage tiers, in `Types.CoverageTier` order
perTradeCapstringThe hard per-trade payout ceiling, in the settlement asset’s base units
minTradeSizestringThe smallest trade this pool will cover, in base units
maxTradeSizestringThe largest trade this pool will cover, in base units
protocolFeeBpsintegerThe protocol’s share of a premium
partnerFeeBpsintegerAn integrating partner’s share of a premium it referred
underwriterFeeBpsintegerThe underwriters’ yield share
maxUtilizationBpsintegerHow much of the reserves may back live cover before sales stop
totalLiabilitiesstringWhat the pool would owe if every live trade rugged, in base units
reservesstringThe accounted treasury figure solvency is measured against (ADR-0015 §5)

GET/tradesChain-scoped

Insured trades, filterable and paginated

Parameters

FieldTypeNotes
chain ?solana | robinhood | robinhood-testnetWhich 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 ?integerDefault 50, max 200
offset ?integerQuery parameter
owner ?stringFilter to one trader
status ?registered | claimed | paid | rejected | expiredFilter by lifecycle status
tokenMint ?stringA base58 Solana address, or a 0x-prefixed EVM address

Response

FieldTypeNotes
asOfSlotstringHighest slot folded into this answer
asOfTsintegerServer time when the answer was produced, in unix seconds
tradesobject[]This page of trades, newest first. Page with limit and offset
totalintegerTrades matching the filter across every page, not the length of this one

GET/trades/:addressChain-scoped

One insured trade

Parameters

FieldTypeNotes
chain ?solana | robinhood | robinhood-testnetWhich 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.
addressstringPath parameter

Response

FieldTypeNotes
asOfSlotstringHighest slot folded into this answer
asOfTsintegerServer time when the answer was produced, in unix seconds
tradeobjectThe trade at the requested address
trade.chain ?solana | robinhood | robinhood-testnetWhich chain the identifiers in this request belong to. Absent means solana.
trade.settled ?booleanWhether 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.addressstringA Solana account address, an EVM contract address, or an EVM uint256 id as a decimal string
trade.poolstringA Solana account address, an EVM contract address, or an EVM uint256 id as a decimal string
trade.policystringA Solana account address, an EVM contract address, or an EVM uint256 id as a decimal string
trade.ownerstringA base58 Solana address, or a 0x-prefixed EVM address
trade.tokenMintstringA base58 Solana address, or a 0x-prefixed EVM address
trade.tierstringThe 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.tradeSizestringA 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.reservedLiabilitystringA 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.windowStartintegerUnix second the cover window opens — the trade’s registration time
trade.windowEndintegerUnix second the cover window closes. Half-open: a collapse observed exactly at windowEnd is outside the cover, so "10 minutes" means 10 minutes
trade.statusregistered | claimed | paid | rejected | expiredLifecycle. 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.registeredSlotstringHeight 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.claimstring | nullA Solana account address, an EVM contract address, or an EVM uint256 id as a decimal string
trade.claimDeadlineinteger | nullUnix 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.payoutstringA 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.rejectionReasonstring | nullWhy a verifier refused the claim. Non-null only when status is rejected
trade.entryPriceobject | nullThe 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.pricestringMantissa, paired with expo. A decimal string: u64 does not survive JSON numbers
trade.entryPrice.expointegerDecimal exponent. The price is price × 10^expo
trade.entryPrice.confstringThe source's own confidence interval, bounded on chain by max_price_conf_bps
trade.entryPrice.publishTsintegerWhen the oracle published, in unix seconds — not when the attestor read it
trade.entryPrice.sourcestringMock, Pyth, Jupiter or DexPoolReserves. A production pool refuses Mock
trade.entryPrice.quoteMintstring | nullThe asset this price is quoted in, or null for a source quoting an absolute unit
trade.swapSignaturestring | nullThe 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

FieldTypeNotes
chain ?solana | robinhood | robinhood-testnetWhich 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.
addressstringPath parameter

Response

FieldTypeNotes
tradestringA base58 Solana address, or a 0x-prefixed EVM address
tokenMintstringA base58 Solana address, or a 0x-prefixed EVM address
digeststringLowercase hex of the sha2-256 digest the on-chain claim commits to. Pass digestBytes, not this, to submit_claim
digestBytesinteger[]The same digest as the 32-byte array submit_claim takes
cidstring | nullContent 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
retainUntilintegerLast 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
collapseBpsintegerHow 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
windowStartintegerUnix second the covered window opened, copied from the trade
windowEndintegerUnix second the covered window closed, copied from the trade
builtAtintegerUnix second the watcher sealed this bundle. Not a freshness signal for the projection — this route is relayed live and carries no asOfSlot
settleableboolean | nulltrue 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

FieldTypeNotes
chain ?solana | robinhood | robinhood-testnetWhich 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 ?integerDefault 50, max 200
offset ?integerQuery parameter
owner ?stringA base58 Solana address, or a 0x-prefixed EVM address

Response

FieldTypeNotes
asOfSlotstringHighest slot folded into this answer
asOfTsintegerServer time when the answer was produced, in unix seconds
policiesobject[]This page of policies. Page with limit and offset
totalintegerPolicies matching the filter across every page, not the length of this one

GET/claimsChain-scoped

Claims and their outcomes

Parameters

FieldTypeNotes
chain ?solana | robinhood | robinhood-testnetWhich 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 ?integerDefault 50, max 200
offset ?integerQuery parameter
status ?claimed | paid | rejected | expiredQuery parameter
owner ?stringFilter to one trader

Response

FieldTypeNotes
asOfSlotstringHighest slot folded into this answer
asOfTsintegerServer time when the answer was produced, in unix seconds
claimsobject[]This page of claims. Page with limit and offset
totalintegerClaims 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

FieldTypeNotes
chain ?solana | robinhood | robinhood-testnetWhich 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 ?integerDefault 50, max 200
offset ?integerQuery parameter
active ?booleanFilter to partners currently accepting sales
address ?stringFilter 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

FieldTypeNotes
asOfSlotstringHighest slot folded into this answer
asOfTsintegerServer time when the answer was produced, in unix seconds
partnersobject[]This page of partners. Page with limit and offset
totalintegerPartners matching the filter across every page, not the length of this one

GET/statsChain-scoped

Protocol-wide figures for one chain

Parameters

FieldTypeNotes
chain ?solana | robinhood | robinhood-testnetWhich 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

FieldTypeNotes
asOfSlotstringHighest slot folded into this answer
asOfTsintegerServer time when the answer was produced, in unix seconds
chainstringWhich chain these figures are about
poolsintegerHow 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
pausedPoolsintegerHow 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
totalPremiumsstringA 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
totalLiabilitiesstringDerived here by summing cover that still holds liability
reportedLiabilitiesstring | nullWhat the chain itself last stated, as of reportedAtHeight. Null before it has said anything
reportedAtHeightstring | nullSlot or block reportedLiabilities is as of — the same clock as asOfSlot
reservesstring | nullBacking 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
reservesAtHeightstring | nullSlot or block reserves is as of. Null under the same condition reserves is
policiesCreatedintegerPolicies ever bought, from the chain’s own lifetime counters
tradesRegisteredintegerTrades ever registered against those policies, lifetime
activeCoverintegerTrades whose window is still open
holdingLiabilityintegerTrades still reserving liability, open or under claim
claimsSubmittedintegerClaims ever filed
claimsPaidintegerClaims settled in the claimant’s favour
claimsRejectedintegerClaims a verifier refused. The bond is forfeited on this path
claimsExpiredinteger | nullClaims 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
totalPaidstringA 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
bondsForfeitedstring | nullClaim 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

FieldTypeNotes
chain ?solana | robinhood | robinhood-testnetWhich 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.
ownerstringPath parameter

Response

FieldTypeNotes
asOfSlotstringHighest slot folded into this answer
asOfTsintegerServer time when the answer was produced, in unix seconds
chainstringWhich chain these figures are about
ownerstringA base58 Solana address, or a 0x-prefixed EVM address
policiesintegerPolicy packs this account has bought
tradesAvailableinteger | nullCredits 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
tradesRegisteredintegerCredits this account has spent — trades registered against its packs
activeCoverintegerThis account’s trades whose coverage window is still open
holdingLiabilityintegerThis account’s trades still reserving liability against the pool, open or under claim
reservedLiabilitystringWhat the pool is holding against this account’s live cover
premiumsPaidstringWhat this account has spent on packs, in total
claimsSubmittedintegerClaims this account has ever filed, expired ones included
claimsPaidintegerOf those, the ones settled in its favour
claimsRejectedintegerOf those, the ones a verifier refused
totalPaidstringWhat 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

FieldTypeNotes
chain ?solana | robinhood | robinhood-testnetWhich chain the identifiers in this request belong to. Absent means solana.
poolstringThe pool to quote against
tokenMintstringThe token being traded. Priced against the pool’s entry gate, so two mints can quote differently at the same trade size
tradeSizestringNotional 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
tierinteger0 Basic, 1 Standard, 2 DegenMax
packSizeintegerTrades in the pack. Must be one of 1, 10, 20, 50, 100
marketCapMicroUsd ?stringCurrent 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 ?stringPrice 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

FieldTypeNotes
eligiblebooleanWhether the program would accept this policy right now
issuesobject[]Empty when eligible
quoteobject | nullThe 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.premiumstringTotal the buyer pays
quote.protocolFeestringAn 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.partnerFeestringZero 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.underwriterFeestringAn 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.toReservestringAn 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.perTradeCapstringAn 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.payoutPerTradestringPaid if a covered trade rugs
quote.liabilityPerTradestringReserved against the pool per registered trade
quote.floorstringThe 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
availableCapacitystring | nullAn 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
marketCapobject | nullThe 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.microUsdstringAn 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.sourcestring`caller` when supplied in the request, otherwise the provider that answered
chain ?solana | robinhood | robinhood-testnetThe chain this quote is for
evmobject | nullWhat 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.chainIdintegerEIP-155 chain id — 4663 mainnet, 46630 testnet
evm.settlementAssetstringWhat 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.settlementSymbolstringFor display only. "ERC-20" when the token has no readable symbol(). Do not key logic on it
evm.settlementDecimalsintegerDecimals 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.approvalRequiredbooleanWhether 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.approvalTargetstring | nullThe spender to approve — the pool — or null when no approval is needed
evm.gasobjectGas ceilings, measured against the contracts and rounded up
evm.gas.createPolicystringGas ceiling for createPolicy, measured against the contracts and rounded up
evm.gas.registerTradestringGas ceiling for registerTrade, measured against the contracts and rounded up
evm.gas.approvestring | nullnull when no approval is required
evm.gas.totalLimitstringcreatePolicy + registerTrade + approve
evm.gasPriceWeistringThe 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.estimatedFeeWeistringtotalLimit × gasPriceWei. A ceiling on the fee, not a prediction of it
asOfTsintegerServer 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

FieldTypeNotes
partnerobjectThe integration this key belongs to. Read earning, not status, to decide whether revenue share is actually accruing
partner.idstringYour partner id. Stable, and safe to log
partner.labelstringThe integration name you registered
partner.statuspending | approved | rejected | suspendedpending, approved, rejected or suspended
partner.onchainPartnerstring | nullYour on-chain Partner account, or null if an operator has not registered one yet
partner.earningbooleanWhether revenue share is accruing. Approved alone is not enough — the on-chain account has to exist, because attribution is the policy.partner field
keyIdstringThe key that authenticated this request
rateLimitobjectBudget for this key as of this response. A 429 carries the same figures in headers
rateLimit.remainingintegerRequests left in the current window
rateLimit.resetsAtintegerUnix 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

FieldTypeNotes
ownerstringThe 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
poolstringThe pool to register against
policyIndexstringWhich of this wallet’s policies in this pool to spend a credit from
tradeIndexstringPosition within that policy. Also a PDA seed, so it cannot be reused
tokenMintstringThe token bought
swapSignaturestringThe swap being insured. Read from chain — its size, its token and its signer are measured, not taken from this request
tradeSize ?stringLamports to insure. Defaults to everything the swap spent, and may not exceed it

Response

FieldTypeNotes
status"signed"Always "signed" on this shape. A refusal is also 200, with status "refused" — branch on this field, not on the HTTP status
transactionstringThe 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
attestorstringThe 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
blockhashstringThe blockhash the transaction was built against. Sign and send promptly — it is what gives the attestation its lifetime
lastValidBlockHeightintegerPast this block height the transaction is dead and a new attestation is needed
accountsobjectThe addresses this transaction will touch, derived so a caller can watch for them without decoding the transaction
accounts.policystringThe Policy the credit is spent from — existing or about to be created
accounts.tradestringThe InsuredTrade this will create
accounts.swapCoverstringThe one-cover-per-swap marker (ADR-0006)
factsobjectEverything 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.tokenMintstringThe token bought, as read from the swap on chain rather than as the caller named it
facts.tradeSizestringLamports insured
facts.swapLamportsSpentstringWhat the swap actually spent, as measured on chain
facts.swapSlotstringThe slot the swap landed in, on the chain it was made on
facts.swapBlockTimeintegerWhen the swap’s block was produced, unix seconds
facts.swapAgeSecondsintegerHow old the swap was when it was attested. Bounded, because cover attaches at entry
facts.entryPriceobjectThe 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.pricestringMantissa
facts.entryPrice.expointegerBase-10 exponent, so the price is price × 10^expo
facts.entryPrice.confstringThe source’s confidence band, in the same units as the mantissa
facts.entryPrice.publishTsintegerWhen the source published the price, unix seconds
facts.entryPrice.sourcestringThe PriceSourceKind variant recorded on chain
facts.entryPrice.quoteMintstring | nullWhat the price is denominated in, or null for a source quoting USD
facts.marketCapMicroUsdstringMarket cap at entry, checked against the tier’s limit
facts.attestedSlotstringThe slot the attestor observed at. Bounds how stale this may get
facts.liabilitystringWhat the pool will reserve against this trade if it registers
swapChainstringWhich 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

FieldTypeNotes
poolstringThe pool the registration is for
ownerstringThe 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
attestationsobject[]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

FieldTypeNotes
status"signed"Always "signed" on this shape. A refusal is also 200, with status "refused" — branch on this field, not on the HTTP status
attestorstringThe key that signed. Counts only while it holds an active registry seat
signaturestring64 bytes of ed25519 over the canonical digest, base64
digeststringThe 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.