Endpoint
Authentication
Send your organization API key as a bearer token. Quivly derives your organization from the key.401.
Request body
Send a batch of events. 1–1000 events per request.Event fields
| Field | Type | Required | Description |
|---|---|---|---|
external_customer_id | string | Yes | An ID Quivly already recognizes for the customer (e.g. a CRM ID). Max 255 chars. Unrecognized IDs are dropped and listed in errors. |
metric_key | string | Yes | The metric name, e.g. api_calls. Max 128 chars. New metric keys are accepted automatically. |
value | number | Yes | The measured quantity. Must be a finite number. |
timestamp | string | No | ISO 8601 timestamp of the usage (e.g. 2026-06-11T00:00:00Z). Defaults to the time of ingestion when omitted. |
granularity | string | No | One of hourly, daily, weekly, monthly. Defaults to daily. |
properties | object | No | Free-form metadata stored with the event. Must be ≤ 4 KB. |
Example request
Idempotency
Sending theIdempotency-Key header makes retries safe. If a request with the same key has already been processed, Quivly replays the stored response instead of processing again, and adds the header Idempotency-Replayed: true.
Rate limits
Up to 1,000 requests per minute per organization. Exceeding the limit returns429 with a Retry-After header (seconds to wait).
Verify mode and dry runs
Before you publish the configuration, every call is verify-only — events are resolved against your customers and reported, but nothing is stored. After publishing, you can still force a verify-only call by adding?dry_run=true:
Response
A successful call returns200 with a summary of what happened.
?dry_run=true), the same call returns mode: "test", stored: false, inserted: 0, and each result’s written is false — confirming the events resolved but were not saved.
Response fields
| Field | Description |
|---|---|
mode | live when events were stored, test for a verify-only call. |
stored | Whether events were written to product usage. |
inserted | Number of events written. |
summary.total | Events received. |
summary.matched | Events whose external_customer_id matched a customer. |
summary.unmatched | Events dropped because the customer ID wasn’t recognized. |
results | Per-event detail (first 100). |
errors | Events that were dropped, with the reason (first 100). |
errors like this:
Errors
| Status | When | Body |
|---|---|---|
401 | Missing or invalid API key | { "success": false, "error": "Invalid or missing API key", "code": "UNAUTHORIZED" } |
422 | The payload failed validation | See Validation errors below |
422 | A live call where every event was dropped (none matched a customer) | error: "No events could be ingested…", with the full summary under details |
429 | Rate limit exceeded | { "success": false, "error": "Too many requests", "code": "RATE_LIMITED" } + Retry-After header |
Validation errors
When the payload is malformed, Quivly returns422 and points to the exact field paths that failed:

