Lucius API

Usage events

How metrics work, how events are rated, and how to send volume plus a pre-calculated fee.

Usage events are how you report metered activity. Lucius rates each event and updates the matching draft invoice immediately.

POST /v1/billing/usage  →  rate  →  draft invoice

Metrics are per contract

There is no global Lucius metric catalog. Each contract’s usage slugs come from that contract’s pricing program. One client may use usd_transaction_volume_processed and another trading_volume — send the exact string on that contract.

Discover the exact strings with:

GET /v1/billing/contracts

To attach a test program to a customer you already created in the dashboard (customer ≠ contract):

curl -X POST https://api-dev.lucius.finance/v1/billing/contracts \
  -H "Authorization: Bearer luc_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"customer_id":"YOUR_CUSTOMER_UUID"}'

Lucius generates {slug}-test as the contract id, a sample CSA, and usage metrics domestic_wire / international_wire. Use that id as external_contract_id.

There is no usage screen in the dashboard. List accepted events with GET /v1/billing/usage (optional ?contract=). They also land as line items on the period invoice (GET /v1/billing/invoices, or Receivables → customer → invoice).

Use the contract id as external_contract_id and a value from metrics[] as metric. A mismatch returns Metric '…' not in pricing program.

metrics[] is empty when the program has only implementation, subscription, or minimum fees — no usage rule. You cannot submit events until you add one.

Event fields

POST /v1/billing/usage accepts up to 100 events.

FieldRequiredWhat it is
external_contract_idYesContract id from GET /v1/billing/contracts
event_idYesYour unique idempotency key
metricYesExact usage slug from that contract’s metrics[]
quantityYesVolume / notional the fee is based on (usually USD). ≥ 0
timestampYesWhen the usage occurred (ISO 8601)
fee_amountNoFinal fee in the contract currency. Authoritative — this is what lands on the invoice
applied_rateNoDecimal rate (0.0001 = 1 bps). Charge = quantity × applied_rate
counterpartyNoOther party on the trade. Matches counterparty-scoped rules
sideNomaker or taker
pairNoInstrument (e.g. BRL/USDC-DT). Matches pair-scoped usage rules
window_start / window_endNoService period dates. Defaults to the timestamp’s date

fee_amount is not a metric. Do not create a calculated_fee slug. Send the fee on the event.

How the charge is calculated

Priority, first match wins:

  1. fee_amount — used as-is (USD). Ignores the contract rate and applied_rate.
  2. applied_rate — quantity × applied_rate. applied_rate is a decimal, not integer bps (0.25 is 25%, not 0.25 bps).
  3. Contract usage rule — percentage (quantity × rate), flat (quantity × unit_price), tiered, or threshold.

If you already compute maker/taker exceptions upstream, send quantity (volume) and fee_amount (your fee). Lucius will not re-apply the contract bps.

{
  "events": [{
    "external_contract_id": "acme-corp-2026",
    "event_id": "trade-abc123:taker",
    "metric": "usd_transaction_volume_processed",
    "quantity": 250000,
    "fee_amount": 25.00,
    "applied_rate": 0.0001,
    "counterparty": "acme-markets",
    "side": "taker",
    "timestamp": "2026-03-25T12:00:00Z"
  }]
}

applied_rate here is optional documentation of the rate you used. fee_amount is what gets billed.

Contract has no metrics

Add one usage rule, then submit against that slug.

Recommended default for USD volume:

curl -X POST https://api.lucius.finance/v1/billing/contracts/acme-corp-2026/pricing-rules \
  -H "Authorization: Bearer luc_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "USD transaction volume",
    "type": "usage",
    "model": "percentage",
    "metric": "usd_transaction_volume_processed",
    "rate": 0,
    "currency": "USD"
  }'

rate: 0 is correct when every event will carry fee_amount. Reuse usd_transaction_volume_processed — do not invent a new slug per client or exception.

Then:

{
  "events": [{
    "external_contract_id": "acme-corp-2026",
    "event_id": "trade-abc:maker",
    "metric": "usd_transaction_volume_processed",
    "quantity": 1000000,
    "fee_amount": 25.00,
    "timestamp": "2026-09-01T12:00:00Z"
  }]
}

Counterparty-scoped rules (optional)

If you want Lucius to pick the rate (instead of sending fee_amount), add usage rules with counterparty and counterparty_side. A matching maker/taker rule beats the generic rule for that metric.

For pair-level exceptions you already compute yourself, prefer fee_amount over modeling every pair in Lucius.

What happens after accept

  1. Event is stored (event_id is the idempotency key).
  2. It is rated using the priority above.
  3. The draft invoice whose billing period covers the timestamp (or window_*) is updated.

Same event_id again → accepted, no second charge. See Idempotency.

On this page