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

# Buy your first policy

> POST /api/v1/policies, end-to-end, with the relayer paying gas.

<Note>
  See [/concepts/lifecycle](/concepts/lifecycle) for the end-to-end flow (policy → trigger → bond → wait/sell decision).
</Note>

The agent's job is one HTTP call. Everything else — gas, on-chain encoding,
event scraping for the `policyId` — happens server-side.

## Sequence

```mermaid theme={null}
sequenceDiagram
  participant Agent
  participant API
  participant Relayer
  participant CoverRouter
  participant USDC

  Agent->>API: POST /api/v1/policies (x-api-key, Idempotency-Key)
  API->>API: validate productId / cover / asset / buyer
  API->>USDC: read buyer.allowance(relayer)  (pre-flight)
  API->>Relayer: sign purchasePolicyFor(productId, cover, asset, buyer)
  Relayer->>CoverRouter: purchasePolicyFor(...)
  CoverRouter->>USDC: transferFrom(buyer, vault, premium)
  CoverRouter->>API: PolicyCreated event
  API-->>Agent: 201 { policyId, txHash, premiumPaid, ... }
```

## Full TypeScript example

```ts theme={null}
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

```bash theme={null}
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](/agents/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](/concepts/shields).
* **`coverageAmount`** — string of USDC base units. \*\*Minimum: `"100000000"` (= $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](/concepts/assets) 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

| HTTP | Code               | Retry?            |
| ---- | ------------------ | ----------------- |
| 400  | `validation_error` | No                |
| 401  | `invalid_api_key`  | No                |
| 422  | `shield_paused`    | Maybe later       |
| 422  | `exceeds_capacity` | Maybe later       |
| 429  | `rate_limit`       | Yes (backoff)     |
| 5xx  | `server_error`     | Yes (≤3 attempts) |
