Skip to content

Record usage events

The usage_events.create operation: POST /api/v1/usage_events in the REST API and the usage_events_create tool in the MCP server.

POST/api/v1/usage_events

Records a batch of 1 to 1,000 usage events for metered prices, billed in arrears on the customer's next invoice. Each event names the customerId, the meter (a meter of one of the brand's metered prices), the quantity (an integer string; negative usage, such as refunded volume, reduces the period), when it happened (occurredAt), optional attributes, and its own idempotencyKey, unique in the brand and mode.

Every event gets a result, in request order (results[i]), and the batch is recorded even when some events fail:

  • created: recorded now;
  • duplicate: an event with this key and the same content was recorded earlier (or earlier in the batch); it is counted once, so resending a batch after a timeout is safe;
  • failed: not recorded, with an error (usage_idempotency_key_reused, resource_not_found or customer_deleted for the customer, usage_meter_unknown, usage_occurred_at_out_of_range, resource_not_found or subscription_item_meter_mismatch for a subscriptionItemId).

A malformed request (a missing field, a quantity that isn't an integer, more than 1,000 events) is refused as a whole with validation_failed, and nothing is recorded.

Usage is aggregated per customer, meter, day (in the entity's time zone) and attribute set. Usage that happened in a period that was already invoiced is recorded as late and billed on the next invoice as a separate late usage line. Each request counts once against the API key's rate limit, whatever its size.

At a glance

  • REST: POST /api/v1/usage_events
  • MCP tool: usage_events_create
  • Operation ID: usage_events.create
  • Scope: Brand
  • Permission: usage:write
  • Risk tier: write
  • Dry run: Supported (send Ownrecord-Dry-Run: true)
  • Idempotency: Idempotency-Key required
  • Modes: Test and live
  • Plan feature: usage_billing.enabled
  • Authentication: API key with the usage:write scope
  • Since: M5

Request

Body

A JSON object (Content-Type: application/json):

FieldTypeRequiredDescription
eventsarray of objectYes1 to 1000 usage events.
events[].idempotencyKeystringYesUnique per event in the brand and mode, e.g. your own event ID. Resending an event with its key is safe: it is counted once.
events[].customerIdcus_… IDYesThe customer whose usage this is (cus_…).
events[].meterstringYesThe meter usage events name, e.g. api.calls or payments.volume.
events[].quantitystring or integerYesHow much was used, as an integer string ("3", "-1500000"); negative usage reduces the period (§16.7). For a percent-of-amount meter it is an amount in micros of the price's currency.
events[].occurredAtdate-timeYesWhen the usage happened (ISO 8601): at most 5 minutes ahead and three years back.
events[].attributesobjectNoOptional dimensions of the event, e.g. { "model": "large" }; usage is aggregated per day and attribute set.
events[].subscriptionItemIdsi_… IDNoThe subscription item to bill (si_…); not available yet: usage goes to the customer's meter.

Response

200 OK with a JSON object:

FieldTypeDescription
object"usage_event_batch"
createdinteger
duplicatesinteger
failedinteger
resultsarray of objectOne result per event, in request order.
results[].indexintegerThe event's position in the request (0-based).
results[].idempotencyKeystring
results[].statusone of created, duplicate, failedcreated: recorded now; duplicate: recorded earlier with this key, counted once; failed: not recorded.
results[].iduev_… ID or nullThe recorded event (also for duplicates); null when it failed.
results[].lateboolean or nullWhether the event is late usage; null when it failed.
results[].errorobject or nullWhy the event wasn't recorded (codes in the error catalogue).

Examples

Two events, one sent again

The second event was already recorded by an earlier request: it is reported, not counted again.

HTTP
POST /api/v1/usage_events
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "events": [
    {
      "idempotencyKey": "req_7f3a9c",
      "customerId": "cus_01m4ty3c2b5qx7mhd9k8w6s0a1",
      "meter": "api.calls",
      "quantity": "120",
      "occurredAt": "2026-10-13T09:15:00Z",
      "attributes": {
        "model": "large"
      }
    },
    {
      "idempotencyKey": "req_7f3a9b",
      "customerId": "cus_01m4ty3c2b5qx7mhd9k8w6s0a1",
      "meter": "api.calls",
      "quantity": "80",
      "occurredAt": "2026-10-13T09:14:00Z"
    }
  ]
}

Response (200):

JSON
{
  "object": "usage_event_batch",
  "created": 1,
  "duplicates": 1,
  "failed": 0,
  "results": [
    {
      "index": 0,
      "idempotencyKey": "req_7f3a9c",
      "status": "created",
      "id": "uev_01m4v0d9k2j7r5t3w8x6y4z2a0",
      "late": false,
      "error": null
    },
    {
      "index": 1,
      "idempotencyKey": "req_7f3a9b",
      "status": "duplicate",
      "id": "uev_01m4v0d9k2j7r5t3w8x6y4z2a1",
      "late": false,
      "error": null
    }
  ]
}

Refunded volume, and a misspelled meter

HTTP
POST /api/v1/usage_events
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "events": [
    {
      "idempotencyKey": "refund_re_8812",
      "customerId": "cus_01m4ty3c2b5qx7mhd9k8w6s0a1",
      "meter": "payments.volume",
      "quantity": "-25000000",
      "occurredAt": "2026-10-13T10:00:00Z"
    },
    {
      "idempotencyKey": "call_99102",
      "customerId": "cus_01m4ty3c2b5qx7mhd9k8w6s0a1",
      "meter": "api.cals",
      "quantity": 1,
      "occurredAt": "2026-10-13T10:00:05Z"
    }
  ]
}

Response (200):

JSON
{
  "object": "usage_event_batch",
  "created": 1,
  "duplicates": 0,
  "failed": 1,
  "results": [
    {
      "index": 0,
      "idempotencyKey": "refund_re_8812",
      "status": "created",
      "id": "uev_01m4v0d9k2j7r5t3w8x6y4z2a0",
      "late": false,
      "error": null
    },
    {
      "index": 1,
      "idempotencyKey": "call_99102",
      "status": "failed",
      "id": null,
      "late": null,
      "error": {
        "code": "usage_meter_unknown",
        "message": "No metered price of this brand uses this meter.",
        "param": "events.1.meter",
        "details": {
          "meter": "api.cals"
        }
      }
    }
  ]
}

Errors

CodeHTTP statusWhenWhat to do
entitlement_required403The organization's plan doesn't include usage billing (usage_billing.enabled).Upgrade the organization's plan, or remove something to get back under the limit.
validation_failed400The request is malformed: no events or more than 1,000, a missing field, a bad quantity or key (details.issues). Nothing is recorded.Fix the fields listed in details.issues and send the request again.
usage_idempotency_key_reused409Per event: the key was already used for an event with other content.Use a new idempotency key for a new event. Resending the original event unchanged is safe: it is reported as a duplicate and counted once.
resource_not_found404Per event: no customer with this ID in the brand and mode.Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.
customer_deleted400Per event: the customer was deleted.Create a new customer for this buyer. Deleted customers stay readable for the records they belong to, but can't be changed.
usage_meter_unknown400Per event: no metered price of the brand uses the meter.Check the meter key for typos, or create a metered price with this meter first (archived prices count too).
usage_occurred_at_out_of_range400Per event: occurredAt is more than 5 minutes ahead or more than three years back.Send when the usage happened: at most 5 minutes ahead of now (clock skew) and at most three years back.
subscription_item_meter_mismatch400Per event: the subscriptionItemId isn't the customer's live metered item for this meter.Name the customer's live metered item for the event's meter, or leave out subscriptionItemId: usage is billed to the customer's metered item for the meter.
permission_denied403The credentials can't record this brand's usage (usage:write).Ask an organization owner or admin for a role or scope that allows this action.

It can also return these common errors: api_version_unsupported, invalid_json, unsupported_media_type, authentication_required, api_key_invalid, scope_required, idempotency_key_required, idempotency_key_reused, idempotency_in_progress, approval_required, approval_rejected, approval_expired, stale_approval, approval_policy_denied, rate_limited, internal_error.