Ingest usage events
Changed onSend up to 100 usage events in one request.
Returns 202 Accepted with the number of events accepted and the event_id of each, in submission order — your own value where you supplied one, a generated value where you did not.
Ingestion is idempotent on (store, event_id): re-sending an event_id this store has already accepted records nothing and is not an error, so retrying a whole batch after a timeout or a partial failure is always safe. Duplicates are deduplicated silently rather than reported — a replayed batch returns the same 202 and the same event_ids as the original. Supply your own event_id to get that guarantee; when you omit it we generate one, which makes the event unique and a retry a second event.
Your event_id is stored trimmed, and the event_ids we return are the stored values — so " abc " is recorded and echoed as "abc", which is what reference= on GET /v1/events matches. Two entries in one batch whose ids differ only by surrounding whitespace are therefore the same event: the second is deduplicated against the first, both are reported accepted, and both carry the same id. An event_id that is entirely whitespace is rejected like an empty one (422) rather than being replaced with a generated id — an unusable key is worth telling you about.
The whole batch is validated before anything is accepted: if any event is invalid the request is rejected with 422 and param pointing at the offending event (e.g. events[3].customer_id), and no event in the batch is ingested.
Billing attribution uses the timestamp you supply (defaulting to the time of ingestion), bounded by the late-event window: once a billing period has been closed for longer than that window, an event arriving for it is still stored durably but is no longer folded into that period's totals.
202, not 200, is deliberate. It means the batch has been accepted for processing, not that every downstream effect has completed: aggregation into meter totals is already asynchronous today, and the transport behind this endpoint may become queue-backed. A queue producer cannot know how many rows a consumer will ultimately insert, so this response reports acceptance rather than an insert/duplicate split — the contract you code against stays identical when that lands.
Request
- Base URLhttps://api.creem.io
- AuthAPI key in header x-api-key
- TypeScriptA function with typed inputs. Its response is checked against the schema.
Request body
Example request
{
"events": [
{
"name": "image.generated",
"customer_id": "cust_abc123",
"external_customer_id": "user-42",
"event_id": "order-1234",
"timestamp": "2026-08-15T10:30:00.000Z",
"properties": {
"tokens": 1200,
"tier": "pro"
}
}
]
}Response
Batch accepted for processing
Example response
{
"accepted": 100,
"event_ids": [
"order-1",
"order-2"
],
"warnings": [
{
"index": 3,
"code": "no_matching_meter"
}
]
}Changes
- info
endpoint added
- info