Skip to main content
See /concepts/lifecycle for the end-to-end flow (policy → trigger → bond → wait/sell decision).
The agent’s job is one HTTP call. Everything else — gas, on-chain encoding, event scraping for the policyId — happens server-side.

Sequence

Full TypeScript example

import { LuminaClient } from '@lumina-org/sdk'   // ^0.6.0

// 1. Construct the client with your minted API key.
const lumina = new LuminaClient({ apiKey: process.env.LUMINA_API_KEY! })

// 2. Purchase a policy. The SDK auto-resolves productId hash + asset='BTC'
//    from the canonical product name, and the relayer pays the gas.
const policy = await lumina.policies.purchase({
  productName:    'FLASHBTC24-001',
  buyer:          '0xYourWalletAddress',
  coverageAmount: '1000000000',                // $1,000 in 6-dec USDC
  idempotencyKey: crypto.randomUUID(),         // strongly recommended
})

console.log('policyId:', policy.policyId)
console.log('txHash:  ', policy.txHash)
console.log('premium: ', policy.premiumPaid)

// 3. Verify the policy was recorded.
const onChain = await lumina.policies.get(policy.productId, policy.policyId)
if (onChain.status !== 'active') throw new Error('policy not active')

// 4. Monitor for triggers. Two patterns — webhook (preferred) or polling.

//    A) Webhook — push-based, near-real-time.
await lumina.webhooks.create({
  url:    'https://my-bot.example.com/webhooks/lumina',
  events: ['policy_triggered', 'bond_minted'],
})
//    See /agents/webhooks for the verification snippet.

//    B) Polling — pull-based, simple for batch jobs.
const poll = setInterval(async () => {
  const p = await lumina.policies.get(policy.productId, policy.policyId)
  if (p.status === 'triggered') {
    console.log('triggered — bond minted at', p.triggeredAt)
    clearInterval(poll)
  }
}, 30_000)   // every 30s is plenty; triggers settle within a block

curl equivalent

curl -X POST https://lumina-api-production-ac85.up.railway.app/api/v1/policies \
  -H "x-api-key: $LUMINA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "productName":    "FLASHBTC24-001",
    "coverageAmount": "1000000000",
    "buyer":          "0xYourWalletAddress"
  }'

Field deep-dive

  • productName — canonical name (FLASHBTC1H-001, FLASHETH24-001, …). Preferred input — the API resolves both the productId hash AND the per-shield asset literal from it. See Products and assets for the registered set.
  • productId — bytes32 hash. Optional alternative to productName. Compute as keccak256(toUtf8Bytes('FLASHBTC24-001')). Pre-computed values are listed in Shields.
  • coverageAmount — string of USDC base units. **Minimum: "100000000" (= 100,enforcedonchain).100, enforced on-chain).** 100 = "100000000", $1,000 = "1000000000". Always pass as a string to preserve precision.
  • asset — bytes32 of the asset symbol. Optional — the API auto-resolves the per-shield literal when omitted. This must equal the product’s coveredAsset (exposed on GET /products) — not the USDC payment token. Each shield expects a specific value (BTC for FlashBTC, ETH for FlashETH); sending the wrong one reverts with InvalidAsset(bytes32) (selector 0x8196d462). See Covered asset vs payment asset for the distinction.
  • buyer — the wallet that pays the USDC premium (NOT the relayer). Must hold ≥ premium and have approved the relayer-side spender.

Idempotency

Pass Idempotency-Key: <uuidv4> on every retryable purchase. Replays return the original response without double-spending. The key is scoped per agent; it’s safe (and good practice) to derive a fresh UUID per logical attempt.

Errors

HTTPCodeRetry?
400validation_errorNo
401invalid_api_keyNo
422shield_pausedMaybe later
422exceeds_capacityMaybe later
429rate_limitYes (backoff)
5xxserver_errorYes (≤3 attempts)