Source: https://www.revvly.ai/docs/api/events

API Reference

# Report usage *events* to Revvly.

Stream metered usage — API calls, token consumption, seat changes, commissions — directly from your application. Events are validated, deduplicated, and applied to your subscriptions in real time.

## Authentication

Every request requires two credentials issued together when you create or rotate an API key. Both are shown once and never again — capture them before closing the dialog.

- **Bearer token**: Sent in the Authorization header. Format: rvly_sk_live_<22 chars>. Identifies your tenant and verifies the caller.

- **Signing secret**: Used to compute the X-Signature-SHA256 HMAC over the raw request body. Prevents event injection even if the Bearer token is compromised.

i

Issue API keys from **Settings → API Keys** in the Revvly dashboard. When you scope a key to specific subscriptions and variables, events outside that scope are rejected with status REJECTED.

Request headers

```
Authorization:      Bearer rvly_sk_live_<your-token>
Content-Type:       application/json
X-Signature-SHA256: sha256=<hmac-hex>
```

## Give this prompt to an integration agent

Copy this into an agent that is building a metering or commission-event integration.

```
You are implementing signed Revvly event ingestion from a trusted backend that streams individually-identified metered usage or commission events.

Working agreement before you implement:
- Do not write production code until you have enough concrete context from the operator.
- Ask clarifying questions first — prefer one short round of questions over guessing.
- Confirm at least: source system / trigger, field mapping, where the API key and any signing secret will be stored, the target environment (local, staging, or production), and the Revvly tenantId when it is not already provided below.
- Once those answers are in place, implement the full integration against the contract below end-to-end.
- Open or fetch the Full guide URL if you need examples or edge-case detail beyond this prompt.
- Never invent API keys, signing secrets, region ids, subscription ids, or other tenant data.
- Never put secrets in browser code, logs, client bundles, or source control.

Tenant context
- The operator has not supplied a tenantId in this prompt. Ask for the Revvly tenant id before making tenant-scoped assumptions.

Platform rules that apply to every Revvly API call:
- Auth is a Bearer API key issued in the dashboard under Settings → API Keys. Tokens are prefixed `rvly_sk_live_`.
- Rate limits are per API key and default to 10 requests/second and 100 requests/minute. Overage is HTTP 429 with a `Retry-After` header in seconds (public GraphQL reports `errors[].extensions.code = RATE_LIMITED`). Honour Retry-After and back off — never retry a 429 in a tight loop.
- A key can be scoped to specific subscriptions and meters. List reads are filtered to that scope; reading or writing a single out-of-scope resource is 403 (`FORBIDDEN`), not an empty result.
- Every call is tenant-scoped by the key itself. There is no tenant parameter to switch — a different tenant means a different key.
- The token and the signing secret are server-side credentials: no browser code, no client bundles, no logs, no source control.

Full guide: https://www.revvly.ai/docs/api/events

Goal
Stream immutable, deduplicated usage events into Revvly so meters aggregate them without your side tracking running totals.

Endpoint and auth
- POST https://api.revvly.ai/api/v1/events — Bearer token AND an HMAC signature. Both are required.
- Authorization: Bearer <API_KEY>
- X-Signature-SHA256: sha256=<hex HMAC-SHA256 of the exact raw request body under the key's signing secret>. Bare hex without the prefix is also accepted.
- Content-Type: application/json
- Serialize the JSON once, HMAC those exact bytes, send those same bytes. Re-serializing after signing (or letting an HTTP client re-encode the body) invalidates the signature.
- The signing secret is a second credential, shown once alongside the token at issue and rotation. A key issued before signing secrets existed has none and this endpoint answers 401 "API key has no signing secret" until the key is rotated.
- There is no Idempotency-Key header here — the per-event eventId is the idempotency key.

Batch shape
{ "events": [ … ] } with 1 to 1000 events. An empty or non-array `events` is 400; more than 1000 is 400.

Per-event fields
- eventId — required. Stable and unique per tenant; ULID recommended. This is the dedup key: a replay returns DUPLICATE and applies nothing.
- Exactly one subscription reference: subscriptionId OR externalSubscriptionId (the SUBSCRIPTION's externalId, not the client's)
- Exactly one meter reference: variableId OR variableSlug
- type — USAGE_REPORTED (the default when omitted), USAGE_CORRECTED, or COMMISSION_EVENT
- quantity — required for USAGE_REPORTED / USAGE_CORRECTED
- saleAmount + transactionId — required for COMMISSION_EVENT
- reportedAt — required ISO 8601. No more than 15 minutes in the future and no more than 7 days in the past, or the event is REJECTED for clock skew. Backfills older than 7 days need an operator, not a retry.
- context — optional free-form object carried into the audit trail

Reading the response
HTTP 202 means the batch was accepted for processing — it is NOT a per-event success. Inspect every results[] entry; they come back in the order you sent them.
- ACCEPTED — validated and queued; this is the normal success status
- PROCESSED — applied synchronously (returned when the queue is bypassed)
- DUPLICATE — this eventId was already seen; the original outcome stands. Safe, and not an error.
- REJECTED — rejectionReason carries the cause (unresolved reference, missing field, clock skew, scope violation). Surface it; replaying the same payload will not fix it.
One bad event never sinks the batch — the rest still apply.

Discover references
- GET https://api.revvly.ai/api/v1/variables — meter ids and slugs
- GET https://api.revvly.ai/api/v1/subscriptions and /subscriptions/by-external/:externalId — subscription references
- Discovery reads are Bearer-only; no HMAC on reads.

Choosing this endpoint over /api/v1/usage
- /api/v1/events when the source emits discrete events with their own ids, and you want dedup on eventId plus an append-only trail.
- /api/v1/usage* when you hold the delta or the absolute count and want a synchronous answer with the resulting totals.
- Never send the same consumption through both.

Example
POST https://api.revvly.ai/api/v1/events
Authorization: Bearer <API_KEY>
X-Signature-SHA256: sha256=<hmac-hex>
Content-Type: application/json

{
  "events": [
    {
      "eventId": "01HYZ7K2N8V3WXPQ4JCBM5FGTE",
      "externalSubscriptionId": "sub-acme-growth-2026",
      "variableSlug": "api-calls",
      "type": "USAGE_REPORTED",
      "quantity": 1500,
      "reportedAt": "2026-08-02T14:00:00.000Z"
    }
  ]
}

Implementation checklist
1. Confirm where eventId comes from (a provider event id, or a ULID you mint and persist before sending).
2. Confirm where the signing secret lives and that it rotates together with the Bearer token.
3. Sign raw bytes — assert in a test that the buffer you signed is the buffer you wrote to the socket.
4. Persist per-event outcomes so REJECTED rows can be repaired without replaying ACCEPTED ones.
5. Add tests for happy path, duplicate eventId, a clock-skew rejection, and one bad reference inside an otherwise-valid batch.
```

## [#](#post-events)Endpoint

POST

https://api.revvly.ai/api/v1/events

202 Accepted

Submit a batch of up to 1 000 events per request. Each event is processed independently — one rejected event does not block the others. The response always returns one result object per submitted event.

## Request body

Shape

```
{
  "events": [Event, ...]   // 1 – 1 000 items
}
```

### Event fields

- **eventIdrequired**: Client-generated unique identifier. Revvly uses this for deduplication — a second event with the same eventId returns DUPLICATE without re-applying the delta. ULIDs are recommended for sortability.

- **subscriptionIdone of**: The internal Revvly subscription id this usage belongs to. Supply **either** this **or** externalSubscriptionId — exactly one of the pair. Find it in the dashboard, via the GraphQL subscriptions query, or via GET https://api.revvly.ai/api/v1/subscriptions.

- **externalSubscriptionIdone of**: Your own reference for the subscription — the externalId you set when the subscription was created (unique per tenant). Use this **instead of** subscriptionId so you never have to store a Revvly id. A value that doesn’t resolve produces a per-event REJECTED result.

- **variableIdone of**: The internal id of the meter (seat, call, token, funnel) to report against. Supply **either** this **or** variableSlug — exactly one of the pair. The meter must be attached to the subscription through a Variable Component — either inherited from the pinned module/bundle or attached directly. (Field name kept as variableId for backward compatibility with the pre-rename API.)

- **variableSlugone of**: The tenant-unique slug of the meter (e.g. api-calls). Use this **instead of** variableId. Discover available slugs via GET https://api.revvly.ai/api/v1/variables (endpoint path unchanged by the rename). A slug that doesn’t resolve produces a per-event REJECTED result.

- **typeoptional**: Defaults to USAGE_REPORTED when omitted. Any other value is rejected per-event. USAGE_REPORTED — add or remove usage (sign of quantity controls direction). USAGE_CORRECTED — the same application path, tagged as a correction in the audit trail. Also needs quantity. COMMISSION_EVENT — record a sale for revenue-share billing.

- **reportedAtrequired**: ISO 8601 timestamp for when the usage occurred. Must be within the last 7 days and no more than 15 minutes in the future. Events older than 24 h are accepted but flagged as late arrivals.

- **quantityUSAGE_REPORTED**: Integer or decimal delta. Positive adds usage, negative removes it. Required when type is USAGE_REPORTED.

- **saleAmountCOMMISSION_EVENT**: Sale value in the subscription’s billing currency. Negative for refunds. Required when type is COMMISSION_EVENT.

- **transactionIdCOMMISSION_EVENT**: Your internal transaction reference. Required for COMMISSION_EVENT; used for reconciliation.

- **contextoptional**: Arbitrary key/value metadata. Stored on the event record for debugging and dispute investigation. Not used in billing calculations.

i

**Identify the target with exactly one of each pair.** Each event must carry either subscriptionId or externalSubscriptionId (not both, not neither), and either variableId or variableSlug. The same one-of rule applies to the usage endpoints (https://api.revvly.ai/api/v1/usage, https://api.revvly.ai/api/v1/usage/set, https://api.revvly.ai/api/v1/usage/commission, https://api.revvly.ai/api/v1/usage/batch), where an unresolved reference returns a 404 / 400 instead.

### Where do these IDs come from?

You don’t have to store Revvly’s internal Mongo ids at all. The recommended pattern is to set externalId on the subscription at creation — your own account or subscription id — and then always report usage with externalSubscriptionId. Discover a meter’s variableSlug (or its internal variableId) once via GET https://api.revvly.ai/api/v1/variables. If you’d rather sync and store Revvly ids, list subscriptions with GET https://api.revvly.ai/api/v1/subscriptions. Both the pattern and the discovery endpoints are covered in full on the [External references & ID discovery](https://www.revvly.ai/docs/api/external-references) page.

## Signing the request

Compute HMAC-SHA256 over the exact bytes you send as the request body, using the signing secret as the key. Pass the result as a hex string in the X-Signature-SHA256 header.

i

Sign the **raw body bytes**, not a re-serialized copy. Any whitespace or key-order difference will invalidate the signature. Construct your JSON string once, sign it, then send that same string.

HMAC signature — Node.js

```
const crypto = require('crypto');

function signRequest(body, signingSecret) {
  return crypto
    .createHmac('sha256', signingSecret)
    .update(body) // raw bytes — do not re-serialize
    .digest('hex');
}
```

- **Algorithm**: HMAC-SHA256

- **Key**: Your signing secret (plaintext, not base64-encoded)

- **Data**: Raw request body bytes

- **Header format**: X-Signature-SHA256: sha256=<hex>

## Full examples

A complete request — authentication, signature, and a single USAGE_REPORTED event.

```
const crypto = require('crypto');
const https  = require('https');

const API_KEY     = 'rvly_sk_live_<your-token>';
const SIGN_SECRET = '<your-signing-secret>';
const HOST        = 'api.revvly.ai';

const payload = JSON.stringify({
  events: [
    {
      eventId:        '01HYZ7K2N8V3WXPQ4JCBM5FGTE', // ULID recommended
      subscriptionId: '663b1f2e4a3c2d1e0f8b7a9c',
      variableId:     '663b1f2e4a3c2d1e0f8b7b01',
      type:           'USAGE_REPORTED',
      quantity:       1,
      reportedAt:     new Date().toISOString(),
    },
  ],
});

const sig = crypto
  .createHmac('sha256', SIGN_SECRET)
  .update(payload)
  .digest('hex');

const req = https.request({
  host:   HOST,
  path:   '/api/v1/events',
  method: 'POST',
  headers: {
    'Content-Type':       'application/json',
    'Content-Length':     Buffer.byteLength(payload),
    'Authorization':      `Bearer ${API_KEY}`,
    'X-Signature-SHA256': `sha256=${sig}`,
  },
}, (res) => {
  let data = '';
  res.on('data', (chunk) => { data += chunk; });
  res.on('end', () => console.log(JSON.parse(data)));
});

req.write(payload);
req.end();
```

## Response

Revvly returns one result object per submitted event, in submission order. A 202 Accepted status is returned even when individual events are rejected — inspect each result’s status field.

```
HTTP/1.1 202 Accepted

{
  "results": [
    {
      "eventId": "01HYZ7K2N8V3WXPQ4JCBM5FGTE",
      "status": "PROCESSED",
      "currentCount": 42
    },
    {
      "eventId": "01HYZ7K2N8V3WXPQ4JCBM5FGTF",
      "status": "DUPLICATE"
    },
    {
      "eventId": "01HYZ7K2N8V3WXPQ4JCBM5FGTG",
      "status": "REJECTED",
      "rejectionReason": "reportedAt is more than 7 days in the past"
    }
  ]
}
```

What happens to a batch

1. Signed requestBearer key plus HMAC of the raw body.
2. Batch acceptedUp to 1,000 events. More returns 400.
3. PROCESSEDEvent applied to the subscription.orDUPLICATESame eventId already received. No delta.orREJECTEDFailed validation or scope. A reason is given.
4. 202 AcceptedOne result per event, in submission order.

Each event gets its own outcome, so one rejection never blocks the rest of the batch.

PROCESSED

Event applied successfully. currentCount reflects the subscription’s usage total after this delta.

DUPLICATE

An event with this eventId was already received. The original result stands; no delta is re-applied.

REJECTED

Event failed validation. rejectionReason explains why. Other events in the same batch are unaffected.

## Validation rules

- **Batch size**: Maximum 1 000 events per request. Exceeding this returns 400 Bad Request before any events are processed.

- **Clock skew**: reportedAt must not be more than 15 minutes in the future. Events further ahead are REJECTED.

- **Stale events**: reportedAt must not be more than 7 days in the past. Older events are REJECTED.

- **Late arrivals**: Events with reportedAt older than 24 h are accepted and applied, but flagged internally as late arrivals for operator visibility.

- **Scope check**: If the API key is scoped to specific subscriptions or meters, events targeting anything outside that scope are REJECTED. Unscoped keys accept any subscription and meter belonging to your tenant.

- **Idempotency**: Safe to retry with the same eventId. Revvly deduplicates globally — re-sending an event returns DUPLICATE without double-counting.

## Key rotation

Rotate an API key from the dashboard or via the rotateApiKey GraphQL mutation. After rotation, both the old and new key remain valid for 30 days, giving you time to deploy the replacement signing secret without downtime.

⚠

The new plaintext token and signing secret are shown once at rotation. Store them in your secret manager before dismissing the dialog — they cannot be retrieved after the rotation completes.

