# Рецепт: API-кредити

> Продавайте передплачені кредити на API чи AI-функцію й списуйте один за запит. Коли балансу бракує — відповідь 402 з посиланням на поповнення.

Покупці наперед оплачують пакет кредитів; кожен запит витрачає кілька. Облік веде Perly Pay: витрата — один атомарний виклик, що не пускає в мінус, а кожен покупець має власну сторінку поповнення. Нічого не списується саме — коли кредити скінчилися, покупець поповнює. Усе про кредити — у [Sell credits](https://pay.perly.io/docs/credits.md); тут рецепт навколо них.

> **Примітка:** З асистентом: цей рецепт робить у вашому проєкті скіл `sell-credits` плагіна Perly Pay для Claude Code — плагін і його приклад `examples/api-credits` (MIT) уже на шляху. Доти будь-який асистент прочитає той самий скіл з `/.well-known/skills/perly-pay-sell-credits/SKILL.md`.

## Як це працює

1. **Пакет кредитів** — Продукт і ціна з `model: "credit_pack"`, `unit_amount` у центах і `credits`. Один раз на режим.
2. **Ваші користувачі — клієнти** — Створюються раз з `external_id` (ваш ідентифікатор користувача); далі `credits.*` приймають цей ідентифікатор.
3. **Поповнення** — `credits.topUpUrl(userId)` — власна сторінка покупця: він обирає пакет і платить. Кожен сплачений пакет додає кредити.
4. **Витрата за запит** — Спершу `credits.consume(userId, cost, { idempotencyKey })`, потім робота. Повтор із тим самим ключем повертає перше списання.
5. **Бракує?** — `insufficient_credits` (409) нічого не списує: відповідайте `402` зі сторінкою поповнення, а не 500.

**Spend one request's credits**

```js
try {
  await pay.credits.consume(userId, cost, { product: PRODUCT, action: "api", idempotencyKey: `req-${userId}-${requestId}` });
} catch (e) {
  if (e.code !== "insufficient_credits") throw e;
  return reply(402, { top_up: await pay.credits.topUpUrl(userId, { product: PRODUCT }) });
}
// now do the work
```

## Запустіть приклад

1. **Встановіть** — `npm install` у `examples/api-credits` (Node 20.12+).
2. **Ключ** — Скопіюйте `.env.example` у `.env` і впишіть тестовий ключ (`sk_test_…`) у `PERLY_PAY_SECRET_KEY`.
3. **Підготуйте** — `npm run setup` створює продукт і ціну вашим ключем та друкує їхні ідентифікатори для `.env`.
4. **Запустіть** — `npm start` (або `npm run dev`), як каже його README.
5. **Сплатіть без гаманця** — Викличте `POST /v1/shout` (402), відкрийте сторінку, яку дає `POST /top-up`, оберіть пакет, натисніть **Simulate payment** на рахунку і викличте знову: 200 і `X-Credits-Remaining: 499`.

## Пастки

- Витрата без ключа ідемпотентності: повторений запит списує двічі.
- Перевіряти баланс, а потім списувати двома кроками. `consume` сам не пускає в мінус; баланс — для показу.
- Спершу робота, потім витрата. Навпаки: спершу витрата.
- Тестові ідентифікатори з живим ключем. Продукти, ціни й вебхуки належать одному режиму: перед запуском повторіть налаштування з `sk_live_…`.
