> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paygentic.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Meter Events

> Ingest usage data for metering, entitlements, and analytics without automatically triggering billing

Meter events let you record what your customers consume — tokens, API calls, storage, compute — without triggering a payment or requiring a billing plan. Once ingested, this data powers metered entitlements, usage analytics, and internal rate limiting.

Unlike [usage events](/platform/billing/usage-events), meter events are fire-and-forget: the API always responds `202 Accepted` and never rejects a well-formed event. There are no billing side-effects, no subscription requirements, and no timing constraints.

## How it works

When you send an event to `POST /v0/events`, Paygentic:

1. Validates that the `type` field matches a billable metric configured in your organization
2. Returns `202 Accepted` immediately
3. Paygentic deduplicates and stores the event

The stored events are then available for aggregation — either to enforce metered entitlement quotas or for your own analytics.

## Before you begin

* At least one [billable metric](/platform/pricing/billable-metrics) with an `eventType` configured
* An API key with `events:create` permission

## Setting up a billable metric for metering

A billable metric becomes a meter definition by configuring three fields:

| Field           | Required | Description                                                                                                                                                  |
| --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `eventType`     | Yes      | The `type` value your events will carry. Events with a non-matching type are rejected with `422`.                                                            |
| `valueProperty` | No       | JSONPath expression into the event `data` payload. Extracts the numeric value to aggregate (e.g., `$.tokens`). Required for aggregations other than `count`. |
| `groupBy`       | No       | A map of dimension names to JSONPath expressions. Allows slicing usage by properties like model or region.                                                   |
| `aggregation`   | Yes      | How to combine values: `sum`, `count`, `avg`, `min`, `max`, `unique_count`, or `latest`.                                                                     |

<Note>
  One billable metric = one measurement. To track multiple values from the same event (e.g., input tokens and output tokens separately), create multiple billable metrics pointing to the same `eventType` with different `valueProperty` paths.
</Note>

**Example: billable metric for AI inference tokens**

```json theme={null}
{
  "name": "AI Tokens",
  "unit": "tokens",
  "eventType": "ai.inference",
  "valueProperty": "$.tokens",
  "aggregation": "SUM",
  "groupBy": {
    "model": "$.model"
  }
}
```

## Sending events

Send a `POST` request to `/v0/events` for each unit of consumption:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.paygentic.io/v0/events" \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "type": "ai.inference",
      "source": "https://api.myapp.com",
      "subject": "cus_abc123",
      "data": {
        "tokens": 1500,
        "model": "gpt-4o",
        "region": "us-east-1"
      },
      "idempotencyKey": "req_xyz789",
      "externalId": "txn_456"
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.paygentic.io/v0/events', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sk_live_YOUR_API_KEY',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      type: 'ai.inference',
      source: 'https://api.myapp.com',
      subject: 'cus_abc123',
      data: {
        tokens: 1500,
        model: 'gpt-4o',
        region: 'us-east-1',
      },
      idempotencyKey: 'req_xyz789',
      externalId: 'txn_456',
    }),
  });
  // Always 202 for a valid, well-formed event
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.paygentic.io/v0/events",
      headers={"Authorization": "Bearer sk_live_YOUR_API_KEY"},
      json={
          "type": "ai.inference",
          "source": "https://api.myapp.com",
          "subject": "cus_abc123",
          "data": {
              "tokens": 1500,
              "model": "gpt-4o",
              "region": "us-east-1",
          },
          "idempotencyKey": "req_xyz789",
          "externalId": "txn_456",
      },
  )
  # response.status_code == 202
  ```
</CodeGroup>

### Event fields

| Field            | Required | Description                                                                                                                                                                            |
| ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`           | Yes      | Must match an `eventType` on one of your billable metrics.                                                                                                                             |
| `source`         | Yes      | URI identifying the system sending the event (e.g., `https://api.myapp.com`).                                                                                                          |
| `subject`        | Yes      | The customer or entity the event relates to — typically a `customerId`.                                                                                                                |
| `data`           | Yes      | Arbitrary JSON object containing the metering payload.                                                                                                                                 |
| `idempotencyKey` | No       | Your own deduplication key. If omitted, a unique key is generated.                                                                                                                     |
| `externalId`     | No       | Your own identifier for cross-referencing the event with external systems. Alphanumeric characters, hyphens, and underscores only; max 255 characters. Searchable when listing events. |
| `namespace`      | No       | Organization ID. Defaults to your authenticated organization.                                                                                                                          |
| `timestamp`      | No       | ISO 8601 event timestamp. Defaults to server receipt time.                                                                                                                             |

### Response

```json theme={null}
{
  "id": "evt_abc123",
  "object": "event",
  "type": "ai.inference",
  "source": "https://api.myapp.com",
  "subject": "cus_abc123",
  "namespace": "org_xyz",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "idempotencyKey": "req_xyz789",
  "externalId": "txn_456"
}
```

## Multi-dimensional metering

The `data` payload can carry as many fields as you need. Use `groupBy` on your billable metric to slice usage by dimension.

**Example: track tokens per model**

Billable metric configuration:

```json theme={null}
{
  "eventType": "ai.inference",
  "valueProperty": "$.tokens",
  "aggregation": "sum",
  "groupBy": {
    "model": "$.model",
    "region": "$.region"
  }
}
```

Every event with `type: "ai.inference"` contributes to the aggregate, and usage can be broken down by `model` or `region` when checking entitlement balances or querying analytics.

<Note>
  The `groupBy` paths use JSONPath notation. `$.model` extracts the top-level `model` field from the event `data` object. Nested fields use dot notation: `$.request.metadata.tier`.
</Note>

## Multiple metrics from one event stream

Multiple billable metrics can read from the same `eventType`. This lets you track different aspects of the same operation without sending separate events.

**Example: track input and output tokens separately from one AI inference event**

Event payload:

```json theme={null}
{
  "type": "ai.inference",
  "subject": "cus_abc123",
  "source": "https://api.myapp.com",
  "data": {
    "inputTokens": 800,
    "outputTokens": 700,
    "model": "gpt-4o"
  }
}
```

Billable metric 1 — Input Tokens:

```json theme={null}
{
  "name": "Input Tokens",
  "eventType": "ai.inference",
  "valueProperty": "$.inputTokens",
  "aggregation": "sum"
}
```

Billable metric 2 — Output Tokens:

```json theme={null}
{
  "name": "Output Tokens",
  "eventType": "ai.inference",
  "valueProperty": "$.outputTokens",
  "aggregation": "sum"
}
```

One event ingested, two metrics updated. Both can have independent pricing, quotas, and entitlement limits.

## Standalone metering (no plan required)

Meter events do not require a billing plan, a subscription, or even a customer account. The only requirement is a billable metric with a matching `eventType`.

This makes meter events suitable for:

* **Internal analytics** — Track usage across your infrastructure before you've set up billing
* **Rate limiting** — Enforce per-customer limits without any billing integration
* **Capacity planning** — Instrument your system to understand consumption patterns
* **Usage dashboards** — Give customers visibility into their own usage

You can add billing later by creating a plan with a price linked to your billable metric — the same events you're already ingesting will feed into billing automatically.

## Using meters with metered entitlements

Meter events are the data source for [metered entitlements](/platform/pricing/features#metered-features). When a customer has a metered entitlement, the grant engine reads their aggregated meter data to determine how much quota they have remaining.

To check whether a customer has remaining quota, use `GET /v1/entitlements/{entitlementId}`. For metered entitlements, this endpoint returns balance, usage, and period data inline. The `entitlementId` comes from the [list entitlements](/platform/pricing/features#checking-entitlements-via-api) response.

See [Features and Entitlements — Metered Features](/platform/pricing/features#metered-features) for code examples, the full balance response schema, and hard vs soft limit configuration.

## Idempotency

Use `idempotencyKey` to prevent duplicate events when retrying failed requests:

* If you send two events with the same `idempotencyKey`, only the first is recorded
* The deduplication window is 24 hours
* If you omit `idempotencyKey`, the API generates a unique key for you (retries will create duplicate events)

**Best practice:** Generate idempotency keys deterministically from your internal identifiers:

```javascript theme={null}
const idempotencyKey = `${requestId}-${customerId}-${eventType}`;
```

## Error handling

| Status                     | Condition                                                                                       |
| -------------------------- | ----------------------------------------------------------------------------------------------- |
| `202 Accepted`             | Event accepted and queued for processing                                                        |
| `400 Bad Request`          | Malformed request (e.g., invalid `timestamp` format or `externalId` with disallowed characters) |
| `422 Unprocessable Entity` | The `type` field does not match any billable metric in your organization                        |
| `401 Unauthorized`         | Missing or invalid API key                                                                      |
| `403 Forbidden`            | API key lacks `events:create` permission for the target namespace                               |

<Note>
  A `422` response means you need to create a billable metric with the matching `eventType` before sending events of that type. Events with an unrecognized type are not queued.
</Note>

## Next steps

* [Billable Metrics](/platform/pricing/billable-metrics) — Configure `eventType`, `valueProperty`, `groupBy`, and aggregation
* [Features and Entitlements](/platform/pricing/features) — Enforce usage quotas with metered entitlements
* [Usage Events](/platform/billing/usage-events) — Billing-first event model that triggers immediate payment
