Guides
Sell credits
Prepaid credits for an API or an AI service: buyers top up themselves, your server spends one call at a time. 0% on every top-up.
Credits suit anything sold by use: tokens, images, minutes, searches. The buyer pays for a pack in crypto; you spend from it on every call; when it runs low they top up on their own page. Nothing is ever charged automatically — the buyer always pays themselves — and Perly takes nothing from a top-up.
Set it up
- 1
Make a product and its packs
Dashboard → Products → New, then New price → Credit pack: e.g. 1,000 credits for $10, 5,000 for $40. Or
pay.prices.create({ product, model: "credit_pack", unit_amount: 1000, credits: 1000 }). - 2
Choose when buyers hear they are low
On the product's page, under Credits: the low-balance alert, the buyer's letter, and Any amount — N of one pack, so a buyer can top up $25 instead of a fixed pack.
- 3
Give each buyer a customer
Make one per user with your own id:
pay.customers.create({ email, external_id: user.id }). From then on the SDK takes that id.
Spend, check, top up
- The customer is a
cus_…id, an email, or your own id for them (external_id). Name aproductwhen you sell credits for more than one. - A spend is atomic and never goes below zero: two at once never both pass on the same credits. Short is
insufficient_credits(409), and nothing is spent. - Send your request's id as the idempotency key. A retry — after a timeout, a crash, even a day later — returns the first spend instead of spending again.
actionis your label (chat,image.hd,api/search). The product's page sums spends by action and by customer.
The top-up page
top_up_url is the buyer's own page, the same every time: it shows what is left and your packs, in your checkout's look, and opens a normal invoice when they choose one. When it is paid, the credits land on their balance once. Put the link in your app, in an "out of credits" error, or send it — the buyer portal has a Top up button too.
When credits run low
| Event | When |
|---|---|
credits.low | The balance fell to the product's alert. Once per crossing: it comes again only after a top-up lifts it above. |
credits.depleted | The balance reached zero. Once per crossing. |
Both carry the customer as data.object, with data.product and data.balance. With the alert set and the letter on, the buyer also gets one email with their top-up link — at most one a day. In test mode letters go to your team only.