Skip to content
EntitlerDocs

Usage and meters

Report usage of metered features, and read the meter that counts it against the customer's allowance.

A metered feature has an allowance, such as 100 ai_credits a month, and a meter that counts the customer's usage against it. You report usage; Entitler counts it, refuses what doesn't fit when you ask it to, and resets the meter on the feature's schedule.

Reporting usage

curl -X POST "$ENTITLER_API/v1/customers/ada/usage" \
  -H "Authorization: Bearer $ENTITLER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"featureKey": "ai_credits", "amount": 3, "idempotencyKey": "run-42"}'
FieldMeaning
featureKeyA metered feature. Usage of any other kind of feature is refused with USAGE_FEATURE_NOT_METERED.
amountA whole number, 1 when you leave it out.
idempotencyKeyRequired. Your own ID for this use, such as a job or message ID.
modeUSAGE_MODE_GATE, the default, or USAGE_MODE_OBSERVE.
occurredAtMsWhen the use happened, in Unix milliseconds, when it isn't now.
registertrue registers the customer first when they don't exist yet.

Gate and observe

  • Gate, the default, checks and records in one step. When the customer isn't entitled to the feature, or the amount doesn't fit what remains, nothing is recorded and the outcome is USAGE_OUTCOME_REFUSED, with refusal set to USAGE_REFUSAL_NOT_ENTITLED or USAGE_REFUSAL_OVER_ALLOWANCE. Use it before you do work you only want to do within the allowance.
  • Observe always records the use, and says by how much the meter is past the allowance in overBy. Use it for work that has already happened, such as usage you bill for after the fact.

A refused report is a normal answer, 200, not an error: read outcome to decide what to do.

Idempotency

Every report carries an idempotency key, scoped to the customer. Sending the same report with the same key again records nothing and answers USAGE_OUTCOME_DUPLICATE with the first report's result, so retrying after a timeout is always safe. Using the key for a different report, another feature or amount, is refused with USAGE_KEY_MISMATCH. A key that was refused can be used again, since a refusal stores nothing.

The answer

Every usage answer has the same shape: tracked.usage says what happened, and tracked.entitlement is the feature's entitlement after it, with its meter.

{
  "tracked": {
    "usage": {
      "outcome": "USAGE_OUTCOME_RECORDED",
      "mode": "USAGE_MODE_GATE",
      "amount": 3,
      "meterChange": 3,
      "overBy": 0,
      "late": false,
      "replayed": false,
      "usageId": "…"
    },
    "entitlement": {
      "featureKey": "ai_credits",
      "entitled": true,
      "meter": { "used": 33, "held": 0, "remaining": { "count": 67 }, "resetsAt": 1793577600, "banked": 0 }
    }
  }
}

Meters and resets

A meter resets with the customer's billing period, or on the feature's own schedule, such as every day or every week, as you set it on the feature. resetsAt is the next reset. A feature can also bank: the allowance a customer didn't use carries into the next period, and shows as banked.

A report dated in an earlier period than now is recorded as late: it stays in the customer's usage history, and it never changes the current meter.

Many reports at once

From a server, POST /v1/usage/events takes up to 500 events for any customers in one request, in observe mode, and answers each in order. It suits usage you collect and send in batches.

curl -X POST "$ENTITLER_API/v1/usage/events" \
  -H "Authorization: Bearer $ENTITLER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"events": [{"externalId": "ada", "featureKey": "ai_credits", "amount": 2, "idempotencyKey": "batch-7-1"}]}'

Reading usage

GET /v1/customers/{external_id}/usage lists the customer's recent reports, newest first, with each metered feature's meter. Your key needs usage:read.

To correct a report, cancel it with POST /v1/customers/{external_id}/usage/{usage_id}:cancel, which takes it off the meter, or set the meter with POST /v1/customers/{external_id}/meters/{feature_key}:adjust. Both need customers:write.

Edit this page on GitHub