Idempotency
How the API handles duplicate requests and ensures safe retries.
Usage event submissions are idempotent. You can safely retry failed requests without creating duplicate events or charges.
How it works
Each usage event requires a unique event_id field. This serves as the idempotency key:
{
"events": [{
"external_contract_id": "acme-corp-2026",
"event_id": "inv-12345-2026-03",
"metric": "revenue_amount_invoiced",
"quantity": 15000,
"timestamp": "2026-03-15T00:00:00.000Z"
}]
}If you submit the same event_id twice:
- The first request creates the event and rates it
- The second request returns
status: "accepted"without creating a duplicate - The invoice is not double-charged
Choosing event IDs
Use a deterministic ID that uniquely identifies the business event:
| Pattern | Example | When to use |
|---|---|---|
| Invoice reference | inv-12345-2026-03 | Billing on invoice amounts |
| Transaction ID | txn-abc123 | Billing on payment volume |
| Date-based | daily-revenue-2026-03-15 | Daily aggregated metrics |
| UUID | 550e8400-e29b-41d4-a716-446655440000 | When no natural key exists |
Avoid random IDs if you need to retry — you won't be able to reconstruct the same ID.
Retry strategy
For transient failures (HTTP 500, timeouts, network errors):
- Retry with the same
event_id - Use exponential backoff (1s, 2s, 4s, 8s)
- Cap at 3–5 retries
- The API will deduplicate automatically
async function submitWithRetry(event: UsageEvent, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const res = await fetch("https://api.lucius.finance/v1/billing/usage", {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ events: [event] }),
});
if (res.ok) return await res.json();
if (res.status < 500) throw new Error(`Client error: ${res.status}`);
} catch (e) {
if (attempt === maxRetries) throw e;
await new Promise((r) => setTimeout(r, 1000 * 2 ** attempt));
}
}
}Scope
Idempotency keys are scoped to your company. Different companies can use the same event_id without conflict.
The key is permanent — once an event_id is used, it cannot be reused for a different event, even after the original event is rated.