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://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

@degen-insurance/bot 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.

typescript
import { InsuranceClient } from '@degen-insurance/bot';

const insurance = new InsuranceClient({
  connection,
  programId: DEGEN_PROGRAM_ID,
  pool: DEGEN_POOL,
  partner: YOUR_PARTNER_ADDRESS,
  signer: botWallet,
}).withAttestationSource(attestor);

// After your swap confirms, before you reply to the user.
const result = await insurance.insureTrade({
  swapSignature,      // base58, exactly as your swap returned it
  tokenMint,
  tradeSize,          // lamports, as a bigint
  tier: 'standard',
});

if (result.status === 'insured') {
  reply += `\n🛡 Covered (${result.trade.slice(0, 8)}…)`;
} else {
  // Declines are results, not exceptions — most trades on a busy day are not
  // insurable, and every decline carries a sentence you can show a user.
  reply += `\n🛡 Not covered: ${result.detail}`;
}

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 { 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

FieldTypeNotes
statusok | degraded
asOfSlotstringHighest slot folded into this answer
asOfTsintegerServer time when the answer was produced, in unix seconds
eventsAppliedinteger
orphanedEventsinteger

GET/pool/:address

Pool state as of the latest indexed slot

Parameters

FieldTypeNotes
addressstringPath parameter

Response

FieldTypeNotes
asOfSlotstringHighest slot folded into this answer
asOfTsintegerServer time when the answer was produced, in unix seconds
addressstringA base58 Solana address
adminstringA base58 Solana address
vaultstringA base58 Solana address
pausedboolean
totalPremiumsstringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
totalLiabilitiesstringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
totalPaidstringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
totalContributedstringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
totalWithdrawnstringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
policiesIssuedinteger
tradesRegisteredinteger
claimsPaidinteger
claimsRejectedinteger
liveExposurestringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
expiryBacklogstring | nullAn amount in lamports, as a decimal string — u64 does not survive JSON numbers

GET/trades

Insured trades, filterable and paginated

Parameters

FieldTypeNotes
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

Response

FieldTypeNotes
asOfSlotstringHighest slot folded into this answer
asOfTsintegerServer time when the answer was produced, in unix seconds
tradesobject[]
totalinteger

GET/trades/:address

One insured trade

Parameters

FieldTypeNotes
addressstringPath parameter

Response

FieldTypeNotes
asOfSlotstringHighest slot folded into this answer
asOfTsintegerServer time when the answer was produced, in unix seconds
tradeobject
trade.addressstringA base58 Solana address
trade.poolstringA base58 Solana address
trade.policystringA base58 Solana address
trade.ownerstringA base58 Solana address
trade.tokenMintstringA base58 Solana address
trade.tierstring
trade.tradeSizestringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
trade.reservedLiabilitystringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
trade.windowStartinteger
trade.windowEndinteger
trade.statusregistered | claimed | paid | rejected | expired
trade.registeredSlotstring
trade.claimstring | nullA base58 Solana address
trade.payoutstringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
trade.rejectionReasonstring | null

GET/policies

Issued policies

Parameters

FieldTypeNotes
limit ?integerDefault 50, max 200
offset ?integerQuery parameter
owner ?stringA base58 Solana address

Response

FieldTypeNotes
asOfSlotstringHighest slot folded into this answer
asOfTsintegerServer time when the answer was produced, in unix seconds
policiesobject[]
totalinteger

GET/claims

Claims and their outcomes

Parameters

FieldTypeNotes
limit ?integerDefault 50, max 200
offset ?integerQuery parameter
status ?claimed | paid | rejectedQuery parameter

Response

FieldTypeNotes
asOfSlotstringHighest slot folded into this answer
asOfTsintegerServer time when the answer was produced, in unix seconds
claimsobject[]
totalinteger

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

FieldTypeNotes
asOfSlotstringHighest slot folded into this answer
asOfTsintegerServer time when the answer was produced, in unix seconds
poolsobject[]
eventsAppliedinteger
orphanedEventsinteger

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

FieldTypeNotes
poolstringThe pool to quote against
tokenMintstring
tradeSizestringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
tierinteger0 Basic, 1 Standard, 2 DegenMax
packSizeintegerTrades in the pack. Must be one of 1, 10, 20, 50, 100
marketCapMicroUsd ?stringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers

Response

FieldTypeNotes
eligiblebooleanWhether the program would accept this policy right now
issuesobject[]Empty when eligible
quoteobject | null
quote.premiumstringTotal the buyer pays
quote.protocolFeestringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
quote.partnerFeestringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
quote.underwriterFeestringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
quote.toReservestringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
quote.perTradeCapstringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
quote.payoutPerTradestringPaid if a covered trade rugs
quote.liabilityPerTradestringReserved against the pool per registered trade
availableCapacitystring | nullAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
marketCapobject | null
marketCap.microUsdstringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
marketCap.sourcestring`caller` when supplied in the request, otherwise the provider that answered
asOfTsinteger

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
partnerobject
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
rateLimitobject
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"
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 signed. Equals the pool’s trade_attestor
blockhashstring
lastValidBlockHeightintegerPast this block height the transaction is dead and a new attestation is needed
accountsobject
accounts.policystring
accounts.tradestringThe InsuredTrade this will create
accounts.swapCoverstringThe one-cover-per-swap marker (ADR-0006)
factsobject
facts.tokenMintstring
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.entryPriceobject
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

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.