How metering works
CLIMeter records usage events as your tools execute and transforms them into billing data. Here is the full lifecycle of a metering event, from function call to invoice line item.
Event lifecycle
1. Tool function called
│
▼
2. @meter.track() intercepts the call
│
├── Extract: tool_id, timestamp, caller API key
├── Validate: API key (cached, no round-trip)
│
▼
3. Tool function executes (your code)
│
▼
4. Function returns (success) or raises (error)
│
▼
5. SDK creates Event object
│
├── Fields: tool_id, price_usd, duration_ms, success, metadata
├── Tags: env, SDK version, caller_id
│
▼
6. Event added to in-memory buffer
│
▼
7. Background flush thread sends batch to CLIMeter API
│
├── POST /v1/events
├── Authorization: Bearer <token>
├── Retry on 5xx (3 attempts, exponential backoff)
│
▼
8. CLIMeter stores event, increments usage counter
│
▼
9. CLIMeter invoices consumer, pays builder via Stripe ConnectBuffering
Events are never sent synchronously during your function call. Instead, the SDK buffers events in memory and flushes them in a background thread. This means:
- Zero network overhead during your function execution
- Events are batched for efficiency (up to 50 per request by default)
- Temporary network issues do not affect your tool performance
| Setting | Default | Description |
|---|---|---|
| batch_size | 50 | Flush when buffer reaches this many events |
| flush_interval | 10s | Flush regardless of buffer size after this interval |
| max_buffer | 1000 | Maximum events in memory; oldest dropped if exceeded |
| retry_count | 3 | Retry failed flushes this many times |
| retry_backoff | 1s, 2s, 4s | Exponential backoff between retries |
Fire-and-forget model
By default, CLIMeter uses a fire-and-forget model for the SDK. When your function returns, the event is queued — not sent. This means your function latency is never affected by network conditions between your tool and the CLIMeter API.
CLI tools
For short-lived CLI processes, call
meter.flush() or use atexit.register(meter.flush) to ensure all events are sent before the process exits.Deduplication
CLIMeter deduplicates events using an idempotency key. If the same event is submitted twice (e.g., due to a retry), only one is recorded. This prevents double-billing.
Python
import uuid
# Provide your own idempotency key
event_id = meter.record(
"my-tool",
duration_ms=142,
idempotency_key=f"call_{request_id}", # stable, unique per invocation
)
# The SDK auto-generates idempotency keys for @meter.track() decorators
# based on: tool_id + timestamp + process_id + call_countDelivery guarantees
| Scenario | Behavior |
|---|---|
| Network blip | Retried 3x automatically; events queued in buffer |
| CLIMeter API down | Events buffered in memory; flushed when API recovers |
| Process crash | In-memory buffer is lost; use flush() before exit |
| Duplicate submission | Deduplicated by idempotency key; only one charged |
| Clock skew | Events accepted up to 24h in the past; future timestamps normalized |