# Usage events tools

> MCP tools for Usage events: usage_events_create, usage_events_list.

- `usage_events_create`: Record usage events
- `usage_events_list`: List usage events

## `usage_events_create`

**Record usage events.** Operation [`usage_events.create`](https://ownrecord.io/docs/reference/usage-events-create.md) · Brand level · permission `usage:write` · risk tier `write` · since M5

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.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument          | Type            | Required | Description                                                                                                                                                                                              |
| ----------------- | --------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `events`          | array of object | yes      | 1 to 1000 usage events. (1–1000 items)                                                                                                                                                                   |
| `entity_id`       | ID (`ent_…`)    | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                                                                                 |
| `brand_id`        | ID (`brd_…`)    | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand.                                                                 |
| `idempotency_key` | string          | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean         | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.
- **Plan feature:** needs `usage_billing.enabled`.

### Errors

- [`entitlement_required`](https://ownrecord.io/docs/reference/errors.md#entitlement_required): The organization's plan doesn't include usage billing (`usage_billing.enabled`).
- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): The request is malformed: no events or more than 1,000, a missing field, a bad quantity or key (`details.issues`). Nothing is recorded.
- [`usage_idempotency_key_reused`](https://ownrecord.io/docs/reference/errors.md#usage_idempotency_key_reused): Per event: the key was already used for an event with other content.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): Per event: no customer with this ID in the brand and mode.
- [`customer_deleted`](https://ownrecord.io/docs/reference/errors.md#customer_deleted): Per event: the customer was deleted.
- [`usage_meter_unknown`](https://ownrecord.io/docs/reference/errors.md#usage_meter_unknown): Per event: no metered price of the brand uses the meter.
- [`usage_occurred_at_out_of_range`](https://ownrecord.io/docs/reference/errors.md#usage_occurred_at_out_of_range): Per event: `occurredAt` is more than 5 minutes ahead or more than three years back.
- [`subscription_item_meter_mismatch`](https://ownrecord.io/docs/reference/errors.md#subscription_item_meter_mismatch): Per event: the `subscriptionItemId` isn't the customer's live metered item for this meter.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): The credentials can't record this brand's usage (`usage:write`).

### Examples

**Two events, one sent again**

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

Arguments:

```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"
    }
  ],
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```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**

Arguments:

```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"
    }
  ],
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```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"
        }
      }
    }
  ]
}
```

## `usage_events_list`

**List usage events.** Operation [`usage_events.list`](https://ownrecord.io/docs/reference/usage-events-list.md) · Brand level · permission `usage:read` · risk tier `read` · since M5

Lists the brand's usage events in the mode of the request, newest recorded first.

- `customerId`, `meter`: one customer's or one meter's events;
- `occurredFrom` (inclusive) and `occurredTo` (exclusive): when the usage happened (ISO 8601 instants);
- `late=true`: only late usage (it happened in a period that was already invoiced), `late=false`: the others.

Paginate with `limit` (1–100, default 10) and `starting_after` (the `next_cursor` of the previous page).

Annotations: `readOnlyHint: true` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument         | Type                 | Required | Description                                                                                                                              |
| ---------------- | -------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `limit`          | integer              | no       | (1–100)                                                                                                                                  |
| `starting_after` | ID (`uev_…`)         | no       | –                                                                                                                                        |
| `customerId`     | ID (`cus_…`)         | no       | Only this customer's events.                                                                                                             |
| `meter`          | string               | no       | Only this meter's events. (at most 100 characters)                                                                                       |
| `occurredFrom`   | date-time (ISO 8601) | no       | Events that happened at or after this instant.                                                                                           |
| `occurredTo`     | date-time (ISO 8601) | no       | Events that happened before this instant.                                                                                                |
| `late`           | string               | no       | `true`: only late events (they happened in a period that was already invoiced); `false`: only the others.                                |
| `entity_id`      | ID (`ent_…`)         | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                 |
| `brand_id`       | ID (`brd_…`)         | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Read-only:** changes nothing; `idempotency_key` is ignored.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): A filter or the page size is malformed.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't read this brand's usage (`usage:read`).

### Example

**A customer's API calls on one day**

Arguments:

```json
{
  "customerId": "cus_01m4ty3c2b5qx7mhd9k8w6s0a1",
  "meter": "api.calls",
  "occurredFrom": "2026-10-13T00:00:00+02:00",
  "occurredTo": "2026-10-14T00:00:00+02:00",
  "limit": 2,
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
```

Result `output`:

```json
{
  "object": "list",
  "data": [
    {
      "id": "uev_01m4v0d9k2j7r5t3w8x6y4z2a0",
      "object": "usage_event",
      "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
      "livemode": false,
      "customerId": "cus_01m4ty3c2b5qx7mhd9k8w6s0a1",
      "subscriptionItemId": null,
      "meter": "api.calls",
      "quantity": "120",
      "occurredAt": "2026-10-13T09:15:00.000Z",
      "attributes": {
        "model": "large"
      },
      "idempotencyKey": "req_7f3a9c",
      "late": false,
      "walletId": null,
      "reservationId": null,
      "amount": null,
      "createdAt": "2026-10-13T09:15:02.000Z"
    },
    {
      "id": "uev_01m4v0d9k2j7r5t3w8x6y4z2a1",
      "object": "usage_event",
      "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
      "livemode": false,
      "customerId": "cus_01m4ty3c2b5qx7mhd9k8w6s0a1",
      "subscriptionItemId": null,
      "meter": "api.calls",
      "quantity": "80",
      "occurredAt": "2026-10-13T09:14:00.000Z",
      "attributes": {},
      "idempotencyKey": "req_7f3a9b",
      "late": false,
      "walletId": null,
      "reservationId": null,
      "amount": null,
      "createdAt": "2026-10-13T09:14:01.000Z"
    }
  ],
  "has_more": true,
  "next_cursor": "uev_01m4v0d9k2j7r5t3w8x6y4z2a1"
}
```
