Documentation Index
Fetch the complete documentation index at: https://docs.lumina-org.com/llms.txt
Use this file to discover all available pages before exploring further.
When something goes wrong, the API returns a JSON envelope:
{ "error": "machine_code", "message": "human-readable explanation", "code": "machine_code" }
The SDK maps non-2xx HTTP responses into LuminaError with .status,
.code, .message. Use the code field for branching; the message
is for logs / UI surfacing.
HTTP envelope
| Code | Meaning | Typical action |
|---|
| 400 | Invalid request shape | Check the request body against the OpenAPI schema |
| 401 | Missing / invalid API key | Verify x-api-key header is present and valid (lk_...) |
| 403 | Forbidden — rate limit, sandbox cap, or 2FA required | Reduce request rate; check x-ratelimit-* headers |
| 404 | Resource not found | Verify the productId / policyId / epochId exists |
| 409 | Idempotency conflict | Use a fresh Idempotency-Key for new requests |
| 422 | Validation error | Check coverageAmount is within product limits |
| 429 | Rate limit | Honour the Retry-After header |
| 500 | Server error | Retry with exponential backoff |
| 503 | Service unavailable (RPC down, oracle stale, sequencer down) | Retry after 30s |
Application-level codes
The code field on the JSON envelope is one of:
| code | HTTP | Cause | Resolution |
|---|
invalid_product | 404 | productId not in catalogue | Use GET /products to list valid IDs |
product_inactive | 422 | productActive[pid] === false on PolicyManagerV2 | Wait for re-activation or choose another product |
coverage_below_minimum | 422 | coverageAmount < 100e6 ($100) | Minimum is 100000000 base units USDC |
coverage_above_maximum | 422 | Coverage exceeds BondVault available capacity | Reduce coverage or wait for capacity |
sandbox_disabled | 503 | SANDBOX_WALLET not configured on the API server | Use full onboarding path |
sandbox_cap_exceeded | 403 | $100 sandbox cap exhausted for the caller IP | Wait for the hourly window to reset (10/h/IP) or onboard |
rate_limit | 429 | Caller exceeded the per-key RPM allowance | Slow down; honour Retry-After |
idempotency_conflict | 409 | Same Idempotency-Key with a different body | Generate a fresh UUIDv4 for genuinely new requests |
health_contracts_incomplete | 500 | /health.contracts missing a required key | Server-side; retry or report |
chain_not_found | 503 | RPC provider returned no result | Transient — retry with backoff |
not_authorized_relayer | 403 | Caller is not in the relayer allowlist | Direct callers should use purchasePolicy, not purchasePolicyFor |
On-chain reverts (surfaced verbatim from the contracts)
These come back inside the message field when a transaction reverts.
The SDK preserves the raw revert reason; below is the catalogue of
strings you may see and what they mean.
CoverRouterV2
| Reason | Cause | Resolution |
|---|
ContractPaused | CoverRouterV2.paused == true OR GlobalPauseRegistry.isPaused() | Wait for unpause |
SEQUENCER_DOWN | Base L2 sequencer uptime feed reports down or within 1h grace period | Wait for sequencer recovery + grace |
ProductNotConfigured(productId) | Product not registered in CoverRouter | Use a productId from GET /products |
ProductInactive(productId) | products[pid].active == false | Choose a different product |
InvalidCoverage(amount) | coverageAmount < 100_000_000 (less than $100 in 6-dec USDC) | Min cover is $100 base units |
NotAuthorizedRelayer(addr) | Caller is not in the relayer allowlist | Direct path purchasePolicy doesn’t need allowlist |
BaseFlashShield / FlashBTC/ETHShield
| Reason | Cause | Resolution |
|---|
NOT_ROUTER | msg.sender is not the FlashShieldAdapter wired into this shield | Always go through the adapter (PolicyManagerV2 routes for you) |
SEQUENCER_DOWN | Base L2 sequencer down OR in grace period | Wait + retry |
ORACLE_INVALID | Chainlink answer <= 0 | Transient oracle outage; retry |
ORACLE_STALE | Chainlink block.timestamp - updatedAt > MAX_PRICE_STALENESS (1h) | Wait for next oracle update |
POLICY_EXISTS | Adapter tried to create a policy with an already-used id | Bug — open an issue |
INVALID_WINDOW | expiresAt <= startTimestamp | Bug — open an issue |
WINDOW_MISMATCH | Adapter passed duration != shield._window() | Bug — open an issue |
POLICY_NOT_FOUND | verifyAndCalculate called for an id that doesn’t exist | Bug or stale policyId |
ALREADY_FINALIZED | Trigger was already evaluated for this policy | A policy can only fire (or not) once |
WINDOW_EXPIRED | block.timestamp > policy.expiresAt on trigger attempt | Policy expired without firing — premium stays burned |
BondVault
| Reason | Cause | Resolution |
|---|
Exceeds capacity | totalCommittedUSD + payoutUSD > vault max | Capacity full; wait or scale down coverage |
Only PolicyManager | Caller is not PolicyManagerV2 | Always go through PolicyManager |
Zero payout | Trigger tried to issue a $0 bond | Bug — check coverage rounding |
Not matured | redeemBond() called before maturity (730 days) | Wait until block.timestamp >= maturityTimestamp |
Insufficient bonds | Caller doesn’t hold enough ERC-1155 of that epoch | Check claimBond.balanceOf(caller, epochId) |
Price too low | LUMINA price < MIN_REDEEM_PRICE ($0.001) | Wait for price recovery — circuit breaker |
Insufficient reserve | Vault LUMINA balance < computed payout | Wait — should be transient |
When a redemption would exceed the 1.08% per-epoch throttle, the
bond is BURNED IMMEDIATELY and added to the FIFO queue for the next
epoch (no revert). The user gets a BondQueued event back; LUMINA
pays out when processQueue() is called against the target epoch.
See BondVault throttle for the full
queue lifecycle.
LuminaBondMarketplace
| Reason | Cause | Resolution |
|---|
InsufficientListingValue | Listing total below the anti-spam floor (audit fix M-3) | Increase totalPriceUsdc or amount |
NotMatured | Listing duration outside allowed range | Adjust the listing window |
NotBondOwner | Caller doesn’t own the bond being listed | Verify ownership via ClaimBond ERC-1155 |
ListingNotFound | Buying a listingId that’s already filled or cancelled | Refresh the listing book |
LuminaTokenV2
| Reason | Cause | Resolution |
|---|
ERC20InsufficientBalance | Buyer USDC balance < premium | Top up |
ERC20InsufficientAllowance | Approval to CoverRouter < premium | Approve at least the quoted premium |
Retry strategy
For transient errors (HTTP 5xx, ORACLE_STALE, SEQUENCER_DOWN,
chain_not_found):
- Initial retry after 1s
- Exponential backoff: 1s, 2s, 4s, 8s, 16s, 32s
- Max 6 retries (~63s total wall clock)
- Honour
Retry-After headers when present
For permanent errors (HTTP 4xx except 429, on-chain reverts other
than ORACLE_STALE / SEQUENCER_DOWN):
- Do not retry
- Surface the
code + message to your user with a clear next step
Webhook delivery
When the API can’t deliver a webhook (3xx / 4xx / 5xx / timeout):
- Retry schedule: 1m, 5m, 30m, 2h, 12h, 24h (6 attempts)
- After the 6 attempts, the webhook is marked
undeliverable
- Re-enable via
POST /api/v1/webhooks/{id}/retry
- Each delivery carries an HMAC signature in
X-Lumina-Signature —
reject any payload that fails verification