All endpoints require `Authorization: Bearer <jwt>` from the [session](/authentication/session-jwt), except where marked public.

## Auth

| Method | Path | Description |
| --- | --- | --- |
| POST | `/auth/register` | Register (Turnstile, then email verification). |
| POST | `/auth/login` | Login (Turnstile). |
| GET | `/auth/me` | The current user. |
| GET | `/auth/verify-email` | Verify an email (redirects to the dashboard). The one-time signup bonus is granted here. |
| POST | `/auth/resend-verification` | Resend the verification email (once per minute). |
| POST | `/auth/forgot-password` | Send a password reset email. |
| POST | `/auth/reset-password` | Set a new password with the reset token. |
| GET | `/auth/config` | Public feature config. |

## User

| Method | Path | Description |
| --- | --- | --- |
| GET | `/user/keys` | List API keys. |
| POST | `/user/keys` | Create a key (verified email required). |
| DELETE | `/user/keys/:id` | Revoke a key. |
| GET | `/user/usage` | Aggregated monthly usage. |

## Billing

1ToolRing is pay as you go: prepaid credits, 1 credit = Rp1, no plans or subscriptions. Payments are processed by [Polar](https://polar.sh), the merchant of record. Current prices are on the [pricing page](https://1toolring.com/pricing/).

| Method | Path | Description |
| --- | --- | --- |
| GET | `/billing/plans` | Public. The top-up packages in `payg_packages`. |
| POST | `/billing/topup` | Start a top-up. Body `{"package_id": "starter"}` (`starter` Rp29.000, `standard` Rp59.000, `growth` Rp109.000, `scale` Rp249.000). Creates a Polar checkout and returns its `payment_url` and an `invoice_id`. Nothing is credited yet. |
| GET | `/billing/topup/status/{invoice_id}` | The invoice as the ledger sees it: `pending`, `paid`, `failed` or `review`. Reading it never grants credits. |
| GET | `/billing/credits` | Credit balance, per-product prices and the ledger. |
| POST | `/billing/polar/webhook` | Called by Polar, not by you. Standard Webhooks signature, verified. |

### How a top-up is credited

1. `POST /billing/topup` records a pending invoice and creates a Polar checkout charged in IDR at exactly the package price. Discount codes are disabled at checkout.
2. You pay on Polar's hosted checkout (card, Apple Pay, Google Pay). Polar adds sales tax where it applies; you are credited the package amount, never the tax.
3. Polar calls `POST /billing/polar/webhook`. Only a signature-verified `order.paid` event grants credits, once per invoice. The order must match the invoice (same checkout, currency IDR, no discount, same amount); a mismatch is held for manual review instead of granting.
4. Poll `GET /billing/topup/status/{invoice_id}` or refresh the dashboard until the invoice is `paid`.

These four packages are the only way to buy credits. Every product, email verification included, spends the same balance.

A refund is handled by a person, not reversed automatically. Vouchers are redeemed in the dashboard and add API credits to the same balance; API credit is the only kind of voucher. New codes look like `TR-XXXX-XXXX-XXXX`; codes issued before the rename stay redeemable.

## Example

```bash
curl -X POST https://api.1toolring.com/billing/topup \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -d '{"package_id":"starter"}'
```

```json
{
  "data": {
    "invoice_id": "inv_...",
    "package_id": "starter",
    "credits": 29000,
    "amount": 29000,
    "currency": "IDR",
    "payment_provider": "polar",
    "payment_url": "https://polar.sh/checkout/...",
    "payment_id": "...",
    "expires_at": "2026-11-02T10:00:00Z"
  }
}
```

## Errors

- `EMAIL_NOT_VERIFIED` (403): key creation before verification.
- `INVALID_PACKAGE` (400): unknown `package_id`.
- `PAYMENT_NOT_CONFIGURED` (503): the payment provider is not configured.
- `PAYMENT_PROVIDER_ERROR` (502): the checkout could not be created; nothing was charged.
- `NOT_FOUND` (404): unknown invoice, or an invoice that belongs to another account.