Sell prepaid credits
Say customers buy credits, and every member of a customer’s team spends from the same balance. That’s a balance limit on the team, with credits added when they buy.
1. Create the balance
Section titled “1. Create the balance”The window total never resets. The amount is what every team starts
with, here 100 free credits:
import { createClient } from "@limitry/sdk";
const limitry = createClient();
await limitry.limits.create({ name: "Credits", subject: { kind: "team" }, action: "*", amount: 100, unit: "credits", window: "total",});from limitry import Limitry
limitry = Limitry()
limitry.limits.create( name="Credits", subject={"kind": "team"}, action="*", amount=100, unit="credits", window="total",)limitry limits create --name Credits --subject '{"kind":"team"}' --action "*" \ --amount 100 --unit credits --window totalcurl -X POST https://api.limitry.com/v1/limits \ -H "Authorization: Bearer $LIMITRY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Credits","subject":{"kind":"team"},"action":"*", "amount":100,"unit":"credits","window":"total"}'2. Add credits when a customer pays
Section titled “2. Add credits when a customer pays”In your payment webhook, add the credits they bought. Use the payment’s id as the idempotency key, so a redelivered webhook can’t add credits twice:
import { createClient } from "@limitry/sdk";
const limitry = createClient();
await limitry.limits.credits.create( "lmt_3f0c…", { subject: { kind: "team", id: "team_42" }, amount: 5000, note: "5,000 credits", }, { idempotencyKey: "payment_8231" },);from limitry import Limitry
limitry = Limitry()
limitry.limits.credits.create( "lmt_3f0c…", subject={"kind": "team", "id": "team_42"}, amount=5000, note="5,000 credits", request_options={"idempotency_key": "payment_8231"},)limitry limits credits create lmt_3f0c… --subject '{"kind":"team","id":"team_42"}' \ --amount 5000 --note "5,000 credits"curl -X POST https://api.limitry.com/v1/limits/$LIMIT_ID/credits \ -H "Authorization: Bearer $LIMITRY_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: payment_8231" \ -d '{ "subject": { "kind": "team", "id": "team_42" }, "amount": 5000, "note": "5,000 credits" }'Credits go to the balance in the key’s environment. Your payment
handler in production needs a live key (lim_live_…), with
$LIMIT_ID the live limit’s id. A test key adds test credits, which
suits your payment provider’s test mode. See
Test and live.
3. Spend from the team’s balance
Section titled “3. Spend from the team’s balance”Check as the user, and name the team as the shared subject. Any limits you set on the user still apply as well:
const report = await limitry.guard( { subject: { kind: "user", id: user.id }, shared: [{ kind: "team", id: user.teamId }], action: "report", cost: 25, }, () => buildReport(params),);with limitry.guard( subject={"kind": "user", "id": user.id}, shared=[{"kind": "team", "id": user.team_id}], action="report", cost=25,): report = build_report(params)However many teammates spend at the same moment, a check never takes the balance below zero: Limitry refuses a cost that doesn’t fit. Two things can still take it below zero: committing a reservation at more than it reserved, which Limitry charges in full (see Reservations), and taking back credits the team already spent (below).
4. When the balance runs out
Section titled “4. When the balance runs out”The check is refused, with retryAfterSeconds: null, because waiting
won’t help. Use that to offer more credits rather than “try again
later”.
To show the balance in your product, read limits[].remaining from any check’s
answer, or make a preview check, which counts nothing.
5. When you refund a payment
Section titled “5. When you refund a payment”In your refund webhook, take the credits back with a negative amount, a note, and the refund’s id as the idempotency key:
import { createClient } from "@limitry/sdk";
const limitry = createClient();
await limitry.limits.credits.create( "lmt_3f0c…", { subject: { kind: "team", id: "team_42" }, amount: -5000, note: "Refund of payment_8231", }, { idempotencyKey: "refund_311" },);from limitry import Limitry
limitry = Limitry()
limitry.limits.credits.create( "lmt_3f0c…", subject={"kind": "team", "id": "team_42"}, amount=-5000, note="Refund of payment_8231", request_options={"idempotency_key": "refund_311"},)limitry limits credits create lmt_3f0c… --subject '{"kind":"team","id":"team_42"}' \ --amount -5000 --note "Refund of payment_8231"curl -X POST https://api.limitry.com/v1/limits/$LIMIT_ID/credits \ -H "Authorization: Bearer $LIMITRY_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: refund_311" \ -d '{ "subject": { "kind": "team", "id": "team_42" }, "amount": -5000, "note": "Refund of payment_8231" }'If the team already spent the credits, the balance goes below zero and their checks are refused until they buy more. A chargeback works the same way.
Related: Pools, balances and budgets, Idempotency.