> ## 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`         | Unless `externalSubject` is set | The customer or entity the event relates to — typically a `customerId`. Prefer this when you know the Paygentic customer ID.                                                                                                                                                                                         |
| `externalSubject` | Unless `subject` is set         | Your own customer identifier, matched against the customer's `externalId`. Fallback for when you don't know the Paygentic customer ID — see [Reporting with your own customer ID](#reporting-with-your-own-customer-id-externalsubject). Alphanumeric characters, hyphens, and underscores only; max 255 characters. |
| `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"
}
```

## Reporting with your own customer ID (externalSubject)

If you don't know — or don't yet have — a Paygentic customer ID, report events with `externalSubject` set to your own customer identifier instead of `subject`:

```json theme={null}
{
  "type": "ai.inference",
  "source": "https://api.myapp.com",
  "externalSubject": "acct-4521",
  "data": { "tokens": 1500 }
}
```

Paygentic resolves `externalSubject` against the `externalId` field on your customers (scoped to your organization):

* **Customer already exists** with `externalId: "acct-4521"` — the event is linked to that customer at ingestion, exactly as if you had sent its `subject`.
* **Customer doesn't exist yet** — the event is accepted and stored unlinked. When you later create a customer with `externalId: "acct-4521"` (or set that `externalId` on an existing customer), the stored events are linked to it retroactively. Linked events are invoiced normally as long as their timestamps fall within a billing period of the customer's subscription.

This means you can start metering before customer records exist: send events with `externalSubject`, then create the customer and subscription — usage reported before the customer existed is picked up at invoicing.

Notes and caveats:

* `subject` is preferred when you know the Paygentic customer ID; `externalSubject` is a fallback.
* Resolution by `externalSubject` is eventually consistent. After a customer's `externalId` is removed or reassigned, events may still resolve to the previous customer for up to one hour. Use `subject` when immediate, unambiguous attribution is required.
* Once an event is linked to a subject, it is never re-linked. Changing a customer's `externalId` only affects unlinked and future events, subject to the one-hour grace period above.
* You can send both `subject` and `externalSubject`; `subject` wins and **they must refer to the same customer**. Sending a `subject` of one customer with another customer's `externalSubject` will misattribute usage.
* Retroactive linking completes within seconds of the customer being created/updated. In rare cases an event that was in flight at exactly that moment can remain unlinked; re-saving the customer's `externalId` re-triggers linking.

## 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, `externalId`/`externalSubject` with disallowed characters, or neither `subject` nor `externalSubject` provided) |
| `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
