Ingest Usage Event
Submits a raw usage event to a published extractor rule set. The ingest API accepts the event, writes it to the usage queue, and returns a raw event ID. Normalization, charge item expansion, rating, wallet debit, and analytics projection run asynchronously after the response.
POST /v1/projects/:project_id/extractor-rule-sets/:id/events/ingestAuthentication
Section titled “Authentication”Authorization: Bearer <api-key>Content-Type: application/jsonRequires usage ingest access. Use this endpoint only from a trusted backend, gateway, or server-side SDK.
Path Parameters
Section titled “Path Parameters”| Field | Required | Description |
|---|---|---|
project_id |
Yes | Project that owns the ingest stream and usage analytics boundary. |
id |
Yes | Published extractor rule set ID used to normalize this event. |
Request Body
Section titled “Request Body”{ "source": "openai-gateway", "external_event_id": "chatcmpl_123", "idempotency_key": "openai-gateway:chatcmpl_123", "subject_type": "organization", "subject_id": "org_001", "occurred_at": 1781222400, "raw_json": { "type": "response", "provider": "openai", "usage": { "model": "gpt-4.1-mini", "prompt_tokens": 1200, "output_tokens": 300 }, "meta": { "org_id": "org_001", "user_id": "user_123", "api_key_id": "key_001" } }, "x-billing": { "items": [ { "metric": "tool_call.web_search", "quantity": 1, "unit": "call" } ], "pricing_hints": { "tool_call.web_search": { "unit_price": 0.1, "pricing_unit": 1, "currency": "USD" } } }, "x-usage-control": { "budget_lease_id": "ble_..." }, "metadata": { "gateway_region": "us-east-1" }}| Field | Required | Description |
|---|---|---|
source |
Yes | Stable event source name, for example openai-gateway, agent-runtime, or workflow-engine. |
external_event_id |
No | Upstream event or request ID. Useful for support and reconciliation. |
idempotency_key |
No | Stable retry key. Reusing the same key with the same tenant_id, project_id, subject_type, subject_id, and occurred_at produces the same raw_event_id and prevents duplicate raw event facts during processing. |
subject_type |
Conditionally | Explicit subject type for this event. Must match [a-z][a-z0-9_]{0,63} and be provided together with subject_id. |
subject_id |
Conditionally | Explicit subject ID for this event. Must be provided together with subject_type. |
occurred_at |
Yes | Event occurrence time as Unix seconds. Required for partition routing and idempotency. |
raw_json |
Yes | Original usage JSON. The extractor rule set decides which fields become subject, dimensions, and charge items. |
x-billing |
No | Dynamic billing extension at the same request level as metadata. Use it for sparse request-level charge items and pricing or discount hints. |
x-usage-control |
No | Usage-control extension. Set budget_lease_id for a one-time agent-run budget. Billing snapshots it for audit and advances that Lease only after rating. |
metadata |
No | Ingest-side metadata for audit or operations. Keep this small and non-sensitive. |
The quota admission is performed for every ingest attempt. If the HTTP response
is lost after the event was accepted and you retry with the same
idempotency_key, the raw event is deduplicated downstream, but the retry still
counts as a new ingest attempt for the realtime quota. Do not use retries to
probe quota availability.
Example
Section titled “Example”curl 'http://127.0.0.1:8080/v1/projects/proj_gateway/extractor-rule-sets/ers_openai/events/ingest' \ -X POST \ -H 'Authorization: Bearer <api-key>' \ -H 'Content-Type: application/json' \ -d '{ "source": "openai-gateway", "external_event_id": "chatcmpl_123", "idempotency_key": "openai-gateway:chatcmpl_123", "subject_type": "organization", "subject_id": "org_001", "occurred_at": 1781222400, "raw_json": { "type": "response", "provider": "openai", "usage": { "model": "gpt-4.1-mini", "prompt_tokens": 1200, "output_tokens": 300 }, "meta": { "org_id": "org_001", "user_id": "user_123" } } }'Response
Section titled “Response”The API returns HTTP 202 Accepted when the event is accepted for asynchronous processing.
{ "raw_event_id": "raw_01hxy7f9q8m2", "status": "received"}Dynamic Billing
Section titled “Dynamic Billing”Use top-level x-billing.items for sparse, request-level charge items that are only known at runtime, such as optional tool calls or add-ons. pricing_hints and discount_hints are keyed by metric.
When the same metric appears in both extractor-generated charge items and x-billing.items, the reported x-billing item is treated as the source of truth for that metric.
Subject Resolution
Section titled “Subject Resolution”When both top-level subject_type and subject_id are present, Meterry uses that pair as the event subject and does not execute the extractor’s subject paths. Otherwise, it resolves each subject field from type_path or id_path, then falls back to default_type or default_id. Supplying only one top-level subject field is invalid.
Before enqueueing, omitted subjects are resolved using the published extractor. A complete, valid subject_type and subject_id pair is copied into the queued request. If extraction cannot resolve the pair, the fields remain empty and enqueueing continues normally. The queue partition key combines tenant, project, subject type, and subject ID; unresolved subjects use the empty subject key within that tenant/project.
- The extractor rule set must belong to the same tenant and project, and it must be published.
- Use a stable
idempotency_keyfor retryable gateway requests. - Use List Usage Event Details with
raw_event_idto inspect the processed usage event and charge items.
Entry Errors
Section titled “Entry Errors”Malformed requests return 400, invalid credentials return 401, insufficient access returns 403, and unavailable authentication dependencies return 503. See HTTP entry errors for the common response format.
Request rate limiting returns 429:
{"error":"rate_limit_exceeded","scope":"project","operation":"ingest_request"}error is a string; scope is tenant or project; operation identifies this endpoint’s rate-limit operation.
Oversized bodies return 413. A rejection based on the known Content-Length also includes max_body_bytes (an integer in bytes). An overflow detected while reading a streamed body returns only error. The size below is an example:
{"error":"request_body_too_large","max_body_bytes":1048576}