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://volumex.insure/api
Auth
Authorization: Bearer <keyId>.<secret>
Amounts
Decimal strings — u64 does not survive JSON numbers
Freshness
Every response carries asOfSlot
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 volumex.insure/app, then check it works
curl -H "Authorization: Bearer $APECOVER_KEY" \
https://volumex.insure/api/v1/whoami
# 2. Price a policy before you commit to anything
curl -X POST https://volumex.insure/api/quote \
-H 'content-type: application/json' \
-d '{"pool":"$POOL","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
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
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: 300_000_000n, // lamports, omit to insure it all
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 Solana. It cannot move lamports and is not a wallet — your keypair signs, locally, and the SDK never transmits it. What the key buys is the attestation: registering a trade needs the pool attestor to co-sign, because entry price and market cap are not computable on chain and a self-reported entry price is the fraud that co-signature exists to prevent. The client refuses to construct in a browser, where a key would be readable by every visitor.
Amounts are bigint throughout. A JavaScript number silently rounds past 253, and sizing is the one thing a bot must not get wrong.
Approved is not the same as earning
Two things have to be true before a revenue share accrues, and it is worth knowing which is which, because the first happens in seconds and the second involves a human.
Your application is accepted. Your key already worked before this; acceptance is a decision, not a capability.
An operator runs register_partner. That instruction is admin-signed, so it cannot be self-served. Until the transaction lands there is no Partner account for policy.partner to point at, and nothing accrues.
/v1/whoami answers both questions in one field: earning is true only when the on-chain account exists. Your account page reads “Approved — not yet on chain” for the state in between rather than rounding it up.
Webhooks
We POST trade.insured, claim.paid and claim.rejected to your endpoint, signed with your API secret. Verify the signature and the replay guard: a signature proves a delivery came from us, not that it is new, and a replayed claim.paid is worth money to anyone whose handler credits an account on receipt.
typescript
import { verifyWebhook, InMemoryNonceStore } from '@degen-insurance/api';
const nonces = new InMemoryNonceStore(); // back this with Redis in production
const result = verifyWebhook(
rawBody, // raw bytes, not a re-serialised object
request.headers['x-degen-signature'],
YOUR_API_SECRET,
{ nonces, nowTs: Math.floor(Date.now() / 1000) },
);
if (!result.ok) return reply.code(400).send(result.reason);
Endpoint reference
10 endpoints, generated from the service’s own route table. The machine-readable version is at https://volumex.insure/api/openapi.json. A field marked ? is optional.
GET/health
Liveness and projection freshness
Always 200 while the process is up. The body says how far the projection has got, which is the question that actually matters.
Response
Field
Type
Notes
status
ok | degraded
—
asOfSlot
string
Highest slot folded into this answer
asOfTs
integer
Server time when the answer was produced, in unix seconds
eventsApplied
integer
—
orphanedEvents
integer
—
GET/pool/:address
Pool state as of the latest indexed slot
Parameters
Field
Type
Notes
address
string
Path parameter
Response
Field
Type
Notes
asOfSlot
string
Highest slot folded into this answer
asOfTs
integer
Server time when the answer was produced, in unix seconds
address
string
A base58 Solana address
admin
string
A base58 Solana address
vault
string
A base58 Solana address
paused
boolean
—
totalPremiums
string
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
totalLiabilities
string
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
totalPaid
string
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
totalContributed
string
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
totalWithdrawn
string
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
policiesIssued
integer
—
tradesRegistered
integer
—
claimsPaid
integer
—
claimsRejected
integer
—
liveExposure
string
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
expiryBacklog
string | null
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
GET/trades
Insured trades, filterable and paginated
Parameters
Field
Type
Notes
limit ?
integer
Default 50, max 200
offset ?
integer
Query parameter
owner ?
string
Filter to one trader
status ?
registered | claimed | paid | rejected | expired
Filter by lifecycle status
tokenMint ?
string
A base58 Solana address
Response
Field
Type
Notes
asOfSlot
string
Highest slot folded into this answer
asOfTs
integer
Server time when the answer was produced, in unix seconds
trades
object[]
—
total
integer
—
GET/trades/:address
One insured trade
Parameters
Field
Type
Notes
address
string
Path parameter
Response
Field
Type
Notes
asOfSlot
string
Highest slot folded into this answer
asOfTs
integer
Server time when the answer was produced, in unix seconds
trade
object
—
trade.address
string
A base58 Solana address
trade.pool
string
A base58 Solana address
trade.policy
string
A base58 Solana address
trade.owner
string
A base58 Solana address
trade.tokenMint
string
A base58 Solana address
trade.tier
string
—
trade.tradeSize
string
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
trade.reservedLiability
string
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
trade.windowStart
integer
—
trade.windowEnd
integer
—
trade.status
registered | claimed | paid | rejected | expired
—
trade.registeredSlot
string
—
trade.claim
string | null
A base58 Solana address
trade.payout
string
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
trade.rejectionReason
string | null
—
GET/policies
Issued policies
Parameters
Field
Type
Notes
limit ?
integer
Default 50, max 200
offset ?
integer
Query parameter
owner ?
string
A base58 Solana address
Response
Field
Type
Notes
asOfSlot
string
Highest slot folded into this answer
asOfTs
integer
Server time when the answer was produced, in unix seconds
policies
object[]
—
total
integer
—
GET/claims
Claims and their outcomes
Parameters
Field
Type
Notes
limit ?
integer
Default 50, max 200
offset ?
integer
Query parameter
status ?
claimed | paid | rejected
Query parameter
Response
Field
Type
Notes
asOfSlot
string
Highest slot folded into this answer
asOfTs
integer
Server time when the answer was produced, in unix seconds
claims
object[]
—
total
integer
—
GET/admin/stateAdmin token
Operational detail: backlog, drift, and pause state
Admin-only. Carries the expiry backlog breakdown from M2-10, which is the signal that the pool is losing capacity to un-cranked trades.
Response
Field
Type
Notes
asOfSlot
string
Highest slot folded into this answer
asOfTs
integer
Server time when the answer was produced, in unix seconds
pools
object[]
—
eventsApplied
integer
—
orphanedEvents
integer
—
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.
Request body
Field
Type
Notes
pool
string
The pool to quote against
tokenMint
string
—
tradeSize
string
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
tier
integer
0 Basic, 1 Standard, 2 DegenMax
packSize
integer
Trades in the pack. Must be one of 1, 10, 20, 50, 100
marketCapMicroUsd ?
string
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
Response
Field
Type
Notes
eligible
boolean
Whether the program would accept this policy right now
issues
object[]
Empty when eligible
quote
object | null
—
quote.premium
string
Total the buyer pays
quote.protocolFee
string
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
quote.partnerFee
string
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
quote.underwriterFee
string
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
quote.toReserve
string
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
quote.perTradeCap
string
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
quote.payoutPerTrade
string
Paid if a covered trade rugs
quote.liabilityPerTrade
string
Reserved against the pool per registered trade
availableCapacity
string | null
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
marketCap
object | null
—
marketCap.microUsd
string
An amount in lamports, as a decimal string — u64 does not survive JSON numbers
marketCap.source
string
`caller` when supplied in the request, otherwise the provider that answered
asOfTs
integer
—
GET/v1/whoamiAPI key
Check an API key and see what it is attached to
The first call to make with a new key. Send it as `Authorization: Bearer <keyId>.<secret>`, or in `X-API-Key`. Answers 401 for a key that is not valid and 403 for one belonging to a suspended or rejected partner — the distinction matters, because a new key fixes the first and not the second.
Response
Field
Type
Notes
partner
object
—
partner.id
string
Your partner id. Stable, and safe to log
partner.label
string
The integration name you registered
partner.status
pending | approved | rejected | suspended
pending, approved, rejected or suspended
partner.onchainPartner
string | null
Your on-chain Partner account, or null if an operator has not registered one yet
partner.earning
boolean
Whether revenue share is accruing. Approved alone is not enough — the on-chain account has to exist, because attribution is the policy.partner field
keyId
string
The key that authenticated this request
rateLimit
object
—
rateLimit.remaining
integer
Requests left in the current window
rateLimit.resetsAt
integer
Unix seconds when the window rolls
POST/v1/attestAPI key
Attest a swap and get back a signed register_trade transaction
The attestor holds the pool’s trade_attestor key (ADR-0003) and builds the instruction itself. It does not co-sign a transaction you supply — that would be blind-signing with the key the protocol’s entry prices rest on. Send the swap; the price, the market cap, the token and the size are all measured here, and a size larger than the swap actually spent is refused. Authenticate with an API key, or with a session cookie from the dApp.
Request body
Field
Type
Notes
owner
string
The wallet that signed the swap. It will own the cover and must sign the returned transaction — an attestation is issued for one wallet and is useless to any other
pool
string
The pool to register against
policyIndex
string
Which of this wallet’s policies in this pool to spend a credit from
tradeIndex
string
Position within that policy. Also a PDA seed, so it cannot be reused
tokenMint
string
The token bought
swapSignature
string
The swap being insured. Read from chain — its size, its token and its signer are measured, not taken from this request
tradeSize ?
string
Lamports to insure. Defaults to everything the swap spent, and may not exceed it
Response
Field
Type
Notes
status
"signed"
—
transaction
string
The register_trade transaction, base64, signed by the attestor and missing only the owner’s signature. Deserialise, sign, send — do not rebuild it, because any change invalidates the attestor’s signature
attestor
string
The key that signed. Equals the pool’s trade_attestor
blockhash
string
—
lastValidBlockHeight
integer
Past this block height the transaction is dead and a new attestation is needed
accounts
object
—
accounts.policy
string
—
accounts.trade
string
The InsuredTrade this will create
accounts.swapCover
string
The one-cover-per-swap marker (ADR-0006)
facts
object
—
facts.tokenMint
string
—
facts.tradeSize
string
Lamports insured
facts.swapLamportsSpent
string
What the swap actually spent, as measured on chain
facts.swapSlot
string
The slot the swap landed in, on the chain it was made on
facts.swapBlockTime
integer
When the swap’s block was produced, unix seconds
facts.swapAgeSeconds
integer
How old the swap was when it was attested. Bounded, because cover attaches at entry
facts.entryPrice
object
—
facts.entryPrice.price
string
Mantissa
facts.entryPrice.expo
integer
Base-10 exponent, so the price is price × 10^expo
facts.entryPrice.conf
string
The source’s confidence band, in the same units as the mantissa
facts.entryPrice.publishTs
integer
When the source published the price, unix seconds
facts.entryPrice.source
string
The PriceSourceKind variant recorded on chain
facts.entryPrice.quoteMint
string | null
What the price is denominated in, or null for a source quoting USD
facts.marketCapMicroUsd
string
Market cap at entry, checked against the tier’s limit
facts.attestedSlot
string
The slot the attestor observed at. Bounds how stale this may get
facts.liability
string
What the pool will reserve against this trade if it registers
swapChain
string
Which cluster the swap was read from — not necessarily the cover’s own
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 every response carries asOfSlot 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.
/quote is the exception: it reads pool parameters from chain, because a quote is a number the caller is about to act on.