Payment Orchestrator
Subscription
Usage & Entitlements

Usage & Entitlements

Two endpoints connect your product to the subscription engine at runtime: usage/record reports consumption of a metered feature (billing), and usage/check-entitlement asks whether a customer may use a feature right now (enforcement). Together with the feature types you get metered billing, hard quotas and boolean gates from the same model.

Both endpoints use your secret key as a Bearer token under https://api-gateway.lunixi.com/api/v1/subscriptions.

QUOTA vs METERED_USAGE

QUOTAMETERED_USAGE
Question it answers"May this customer still do X?""How much X did they do?"
Runtime callusage/check-entitlement before the actionusage/record after the action
When it stops the customerWhen the allowance is used up (check fails)Never — it meters, billing reflects it
ResetsEvery resetPeriodDays on the featureCounter window resets per resetPeriodDays
BOOLEAN for comparisoncheck-entitlement returns allowed/denied purely by plan—

For a capped-but-billable feature, combine them: check the entitlement first, and record the usage once the action succeeds.

Record usage (metered billing)

POST /subscriptions/usage/record — report consumption for a METERED_USAGE (or windowed QUOTA) feature. idempotencyKey deduplicates: retrying the same request never double-counts.

curl -X POST https://api-gateway.lunixi.com/api/v1/subscriptions/usage/record \
  -H "Authorization: Bearer at_live_5f3e2b1a..." \
  -H "Content-Type: application/json" \
  -d '{
    "subscriptionId": "sub_01JX5B8R3M",
    "featureCode": "api-calls",
    "quantity": 25,
    "idempotencyKey": "req-7f3a91c2"
  }'

Each accepted record emits a usage.recorded webhook. Recorded quantities are rated onto the subscription's period invoice.

⚠️

Derive idempotencyKey from the business event you are metering (your request id, job id, message id) — not from a random value per attempt. That is what makes retries safe: same event, same key, counted once.

Check an entitlement (quota enforcement)

POST /subscriptions/usage/check-entitlement — ask, by customer, whether a feature may be used right now. Works for BOOLEAN (has it / doesn't) and QUOTA (allowance left / used up) features.

curl -X POST https://api-gateway.lunixi.com/api/v1/subscriptions/usage/check-entitlement \
  -H "Authorization: Bearer at_live_5f3e2b1a..." \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "cust_01JX2F9K4T",
    "featureCode": "reports-export"
  }'
{
  "allowed": true,
  "featureCode": "reports-export"
}

Gate the action on allowed. A denial means the customer's plan doesn't include the feature, or a QUOTA allowance is exhausted for the current window.

Integration pattern: gate every call, record after success

The standard shape for a capped, metered API feature:

Check before the action

Call check-entitlement with the customer and feature. If allowed is false, reject with your own upgrade prompt (HTTP 402/403 in your API).

Do the work

Run the action only after the entitlement passed.

Record after success

Call usage/record with an idempotency key derived from the request — never record usage for work that failed.

// Express-style middleware + handler
async function requireFeature(req, res, next) {
  const r = await fetch(
    'https://api-gateway.lunixi.com/api/v1/subscriptions/usage/check-entitlement',
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.LUNIXI_SECRET_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        customerId: req.customer.lunixiId,
        featureCode: 'api-calls',
      }),
    },
  );
  const { allowed } = await r.json();
  if (!allowed) return res.status(402).json({ error: 'quota_exceeded' });
  next();
}
 
app.post('/v1/reports', requireFeature, async (req, res) => {
  const report = await generateReport(req.body);   // the actual work
 
  await fetch('https://api-gateway.lunixi.com/api/v1/subscriptions/usage/record', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.LUNIXI_SECRET_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      subscriptionId: req.customer.subscriptionId,
      featureCode: 'api-calls',
      quantity: 1,
      idempotencyKey: `report-${report.id}`,       // business-derived key
    }),
  });
 
  res.json(report);
});

For high-throughput paths, batch on your side: aggregate counts per subscription over a short window and send one usage/record with the summed quantity and a window-derived idempotencyKey (e.g. api-calls-sub_x-2026-07-06T14:05). Fewer calls, same billing outcome.

Errors

StatusMeaningWhat to do
400Validation failed (missing idempotencyKey, non-positive quantity)Fix the body
401Missing/invalid Bearer keyCheck the Authorization header
404Unknown subscriptionId, customerId or featureCodeVerify ids; feature codes must match the feature definition
409Idempotency conflict — same key, different payloadReuse a key only for the exact same event
422Feature type doesn't support the operation (e.g. recording usage against a BOOLEAN feature)Check the feature's type

Webhooks

EventWhen
usage.recordedA usage record was accepted
invoice.createdThe period invoice carrying rated usage was generated

See Webhooks for delivery and signature verification.

Next steps

  • Features & Add-ons — define the features you meter and gate
  • Plans — attach features and allowances to plans
  • Invoices — where metered usage is billed