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

Try it

Pick a function, fill the fields and run it. The address derivations and the pricing helpers are arithmetic — they need no key and no account, so they work before you have either. whoami and pool authenticate, and want a key from your account page.

Your key is held in this tab and posted to this site, which is the one that issued it. It is not written to the output and the runner does not log it. Nothing here signs a transaction, so nothing here ever asks for a private key — and you should not paste one into this page or any other.

API key

Kept in this tab only and sent to this site, which issued it. Never needed for the address or pricing functions.

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. A signer and a connection are needed for anything that touches the chain; the read-only methods work without them.

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.

Not runnable here. Constructing a client has no result to show. Every method below builds one for you.

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

SDK reference

25 functions across the client, the platform half and the pure helpers. Everything marked runnable can be executed from the terminal above with the same arguments shown here.

ApeCover

new ApeCover(options)example

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. A signer and a connection are needed for anything that touches the chain; the read-only methods work without them.

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.

whoamirunnable

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

poolrunnable

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.

quoterunnable

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.

insureTradeexample

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

buyPolicyexample

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 { policy, signature } = await ape.buyPolicy('standard', 300_000_000n);
console.log('pack bought', policy.toBase58(), signature);

chainexample

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

protocolexample

protocol(): Promise<Protocol>

The protocol-level view: pool state, tiers and the parameters a quote is computed from.

typescript
const protocol = await ape.protocol();
const state = await protocol.poolState();

PlatformClient

whoamirunnable

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

poolrunnable

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

attestexample

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

findPoolrunnable

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';
import { PublicKey } from '@solana/web3.js';

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

findVaultrunnable

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

findPolicyrunnable

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

findTraderunnable

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

findWalletExposurerunnable

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

findSwapCoverrunnable

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

findClaimrunnable

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

quoteSingleTradeexample

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 { fetchPool, quoteSingleTrade } from '@apecover/core';
import { quoteContext } from './quote';   // builds the context for one tier

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

const rent = BigInt(await connection.getMinimumBalanceForRentExemption(0));
const ctx = quoteContext(pool, 1 /* Standard */, rent);

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

effectivePartnerFeeBpsexample

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

checkEligibilityexample

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

utilizationBpsrunnable

utilizationBps(view: Pick<PoolView, "totalLiabilities" | "vaultReserves">): number

How much of the pool’s capital is already committed, in basis points. Rises as cover is written and is what capacity refusals turn on.

typescript
const pool = await fetchPool(connection, programId);
const used = utilizationBps(pool!);   // 4200 = 42%

fetchPoolrunnable

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

const connection = new Connection(process.env.RPC_URL!, 'confirmed');
const pool = await fetchPool(connection, '${programId}');
if (pool === null) throw new Error('no pool on this cluster');

Helpers

declineForrunnable

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

sourceVariantrunnable

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
const variant = sourceVariant('Pyth');   // → the IDL variant name

Endpoint reference

11 endpoints, generated from the service’s own route table. A field marked ? is optional.

These are the endpoints an API key can call. The operator surface — pool parameters, the pause switch, treasury withdrawals — needs the admin bearer token and is not listed here, because an entry you cannot call is worse than no entry when it sits between two you can. It is still described in full by https://volumex.insure/api/openapi.json, which stays a complete account of what the deployment serves.

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 exactly when orphanedEvents is non-zero — the answers below are incomplete. The process is up either way; this is not a liveness signal, and ok is scoped exactly as narrowly as orphanedEvents is
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

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
settlementMintstring | nullA base58 Solana address
pausedbooleanWhether the pool is halted. While true the program refuses new policies and trade registrations; claims already filed still settle
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
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
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[]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/: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
tradeobjectThe trade at the requested address
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.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.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.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.registeredSlotstringSlot at registration. Orders trades that share a timestamp, which windowStart alone cannot
trade.claimstring | nullA base58 Solana address
trade.payoutstringAn amount in lamports, as a decimal string — u64 does not survive JSON numbers
trade.rejectionReasonstring | nullWhy a verifier refused the claim. Non-null only when status is rejected

GET/trades/:address/proof

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
addressstringPath parameter

Response

FieldTypeNotes
tradestringA base58 Solana address
tokenMintstringA base58 Solana 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

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[]This page of policies. Page with limit and offset
totalintegerPolicies matching the filter across every page, not the length of this one

GET/claims

Claims and their outcomes

Parameters

FieldTypeNotes
limit ?integerDefault 50, max 200
offset ?integerQuery parameter
status ?claimed | paid | rejectedQuery 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

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.

Request body

FieldTypeNotes
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 lamports 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

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 lamports, as a decimal string — u64 does not survive JSON numbers
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 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 | 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 lamports, as a decimal string — u64 does not survive JSON numbers
marketCap.sourcestring`caller` when supplied in the request, otherwise the provider that answered
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/cosignAPI key

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.

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