Revvly
Docs
Guide

Report metered usage with REST.

Use direct usage mutations when your worker owns a current count, a delta, or a commission sale — with idempotency on every write.

Give this prompt to an integration agent

You are implementing Revvly REST usage reporting from a trusted backend that owns deltas, absolute counts, or commission sales.

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/guides/usage-reporting

Goal
Report metered usage into Revvly so variable components can bill against the correct subscription and meter.

Base URL and auth
- Host: https://api.revvly.ai
- Authorization: Bearer <API_KEY> on every request
- Every mutating usage call MUST send an Idempotency-Key header
- Usage endpoints do NOT use the HMAC signing header required by POST https://api.revvly.ai/api/v1/events
- Never expose the API key in browser code

Choose the right endpoint
- POST https://api.revvly.ai/api/v1/usage — body `delta`: a positive or negative integer applied to the current count
- POST https://api.revvly.ai/api/v1/usage/set — body `quantity`: replaces the current quantity with an absolute count
- POST https://api.revvly.ai/api/v1/usage/commission — body `saleAmount` + `transactionId` for commission billing
- POST https://api.revvly.ai/api/v1/usage/batch — body `items[]`, up to 100 independent deltas; response is HTTP 207 with per-item results
- All four also accept optional `reportedAt` (ISO 8601) and `notes`; each returns the resulting currentCount, peakCount, cumulativeCount and peakReachedAt.
- Prefer POST https://api.revvly.ai/api/v1/events instead when the source emits a stream of individually-identified events you want deduplicated on an event id. Never send the same consumption through both surfaces.

Identity rules (every item)
- Exactly one subscription reference: subscriptionId OR externalSubscriptionId
- Exactly one meter reference: variableId OR variableSlug
- Prefer externalSubscriptionId + variableSlug when the operator already has those external references
- externalSubscriptionId resolves against the SUBSCRIPTION's externalId — not the client's externalId, and not any CRM externalRef. Confirm the value is set on the subscription before building on it.
- Supplying both or neither of a pair is 400; a reference that does not resolve is 404; a reference that resolves outside the key's scope is 403.

Discover references
- GET https://api.revvly.ai/api/v1/variables — meters with their id and slug (add ?includeInactive=true to see archived ones)
- GET https://api.revvly.ai/api/v1/subscriptions — keyset-paginated summaries; filter with ?organizationId= / ?externalId= / ?status= and follow nextCursor
- GET https://api.revvly.ai/api/v1/subscriptions/by-external/:externalId — resolve one subscription by your own id
- These reads are Bearer-only and filtered to the key's scope.

Read back what you reported
- GET https://api.revvly.ai/api/v1/usage/:subscriptionId/:variableId — current state (currentCount, peakCount, cumulativeCount, peakReachedAt)
- GET https://api.revvly.ai/api/v1/usage/:subscriptionId/:variableId/history — the audit log, with ?since= / ?until= / ?limit= (default 100, max 1000) and a hasMore flag; every entry carries the idempotencyKey that produced it
- Use these to reconcile after a failed run instead of re-posting and hoping.

Idempotency
- Reuse the same Idempotency-Key only when retrying the same logical usage event
- For /api/v1/usage/batch, also give every item a unique idempotencyKeySuffix (the effective key is `<header>::<suffix>`)
- Inspect each results[].status independently on HTTP 207 — do not treat 207 as blanket success. Per-item statuses are numeric HTTP-style codes (200, 400, 403, 404, 422).
- A replay of an applied key returns the original outcome and applies nothing.

Example delta
POST https://api.revvly.ai/api/v1/usage
Authorization: Bearer <API_KEY>
Idempotency-Key: usage-event-evt_01J8K3
Content-Type: application/json

{
  "externalSubscriptionId": "sub-acme-growth-2026",
  "variableSlug": "api-calls",
  "delta": 1500,
  "reportedAt": "2026-08-02T14:00:00.000Z",
  "notes": "Hourly usage rollup"
}

Example batch
POST https://api.revvly.ai/api/v1/usage/batch
Authorization: Bearer <API_KEY>
Idempotency-Key: hourly-rollup-2026-08-02T14

{
  "items": [
    {
      "externalSubscriptionId": "sub-acme-growth-2026",
      "variableSlug": "api-calls",
      "delta": 1500,
      "idempotencyKeySuffix": "sub-acme-growth-2026"
    },
    {
      "externalSubscriptionId": "sub-northwind-scale-2026",
      "variableSlug": "api-calls",
      "delta": 900,
      "idempotencyKeySuffix": "sub-northwind-scale-2026"
    }
  ]
}

Implementation checklist
1. Confirm which mutation fits the source system (delta vs set vs commission vs batch).
2. Confirm the external subscription id and meter slug/id mapping, and that the subscription actually carries that externalId.
3. Confirm idempotency key strategy (event id, period key, etc.).
4. Store/log per-item batch failures for retry without double-counting successful items.
5. Do not use https://api.revvly.ai/api/v1/events HMAC signing for these usage routes.

Example 1: send a usage delta

Send positive deltas to add usage and negative deltas to remove it. Reuse the same idempotency key only when retrying the same logical usage event.

const response = await fetch('https://api.revvly.ai/api/v1/usage', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.REVVLY_TOKEN}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': usageEvent.id,
  },
  body: JSON.stringify({
    externalSubscriptionId: usageEvent.accountId,
    variableSlug: 'api-calls',
    delta: usageEvent.count,
    reportedAt: usageEvent.occurredAt,
    notes: 'Hourly usage rollup',
  }),
});

if (!response.ok) throw new Error(await response.text());
const state = await response.json();
// { currentCount, peakCount, cumulativeCount, ... }

Example 2: send an hourly batch

Batch up to 100 deltas. The request can succeed while one item fails, so store and inspect each result rather than treating the 207 response as blanket success.

POST https://api.revvly.ai/api/v1/usage/batch
Authorization: Bearer <API_KEY>
Idempotency-Key: hourly-rollup-2026-08-02T14

{
  "items": [
    {
      "externalSubscriptionId": "acct_123",
      "variableSlug": "api-calls",
      "delta": 1500,
      "idempotencyKeySuffix": "acct_123"
    },
    {
      "externalSubscriptionId": "acct_456",
      "variableSlug": "api-calls",
      "delta": 900,
      "idempotencyKeySuffix": "acct_456"
    }
  ]
}

// HTTP 207: inspect results[].status independently.

Choose the right mutation

POSThttps://api.revvly.ai/api/v1/usage

Apply a positive or negative delta.

POSThttps://api.revvly.ai/api/v1/usage/set

Replace the current quantity with an absolute count.

POSThttps://api.revvly.ai/api/v1/usage/commission

Record a sale amount and your transaction id for commission billing.

POSThttps://api.revvly.ai/api/v1/usage/batch

Apply up to 100 independent deltas; receive per-item results.

Read usage back

Reconcile against Revvly instead of re-posting after a failed run. Both reads are Bearer-only — no signature, no idempotency key — and are filtered to the key’s scope.

GEThttps://api.revvly.ai/api/v1/usage/:subscriptionId/:variableId

Current state: currentCount, peakCount, cumulativeCount, peakReachedAt. These are accrued unit counts, not billed amounts. currentCount is a point-in-time gauge, while cumulativeCount is the total for the current period on components that accumulate per period — so it resets at renewal and will not match a lifetime total.

GEThttps://api.revvly.ai/api/v1/usage/:subscriptionId/:variableId/history

The audit log, filtered with since / until / limit (default 100, max 1000) and a hasMore flag. Each entry carries the idempotency key that produced it, so a half-finished run can be resumed exactly.

Related