# Billing Top Up

- **Method:** `POST`
- **Path:** `/api/v1/billing/top-ups`
- **Operation ID:** `billing_top_up`
- **Tags:** usage

Buy prepaid credit: a one-off invoice for `amount_usd` x $1.00, answered with the client secret the
Payment Element pays it with (section 3.3).

A `TopUpWorkflow` makes and finalizes the invoice, and credits the balance once when Stripe says it is
paid - on the webhook, or on its own read ten minutes later if the webhook never comes. The browser's
word is never enough. An invoice left unpaid for a day is voided. Refused with a 409 when it would take
this month's charges past the monthly budget. Usually answers `open` with `client_secret`; if the invoice
is slow to make it answers `pending`, and `GET /billing/top-ups/{id}` carries the secret once it exists.

## Effective servers

- `https://api.saram.sh`

  Saram

## Authentication

- **HTTPBearer**: HTTP bearer

  a Saram API key (`sk_saram_<env>_...`) or a person's AuthKit access token, as `Authorization: Bearer <credential>`. Both are told apart by shape and produce the same principal.

## Request body

**Required:** `true`

**Content type:** `application/json`

schema: `TopUpWrite`, no additional properties

`POST /api/v1/billing/top-ups`: how much credit to buy, in whole dollars.

- **`amount_usd` (required)**: `integer`, minimum: `10`, maximum: `500`

  Whole dollars of prepaid credit: the invoice reads 'Saram usage credit, N x $1.00', with tax on top where it applies. Counts against the monthly budget, manual and automatic top-ups together.

**Example:**

```json
{
  "amount_usd": 10
}
```

## Responses

### 201 Successful Response

**Content type:** `application/json`

schema: `TopUpRecord`, no additional properties

One top-up: where it stands, and while it is open, the secret the Payment Element pays it with.

`pending` (the invoice is being made), `open` (awaiting payment), then `paid` (credited to the balance),
`failed` (declined, or no invoice could be made), `requires_action` (an off-session charge the bank wants the
person to authenticate) or `void` (unpaid after a day). Credit is added once, when Stripe says the invoice is
paid - never on the browser's word. A top-up that ended unpaid says why in `failure_code` and
`failure_message`.

- **`amount_usd` (required)**: `number`
- **`created_at` (required)**: `string`, format: `date-time`
- **`id` (required)**: `string`
- **`origin` (required)**: `string`, possible values: `"manual", "auto"`
- **`status` (required)**: `string`, possible values: `"pending", "open", "paid", "failed", "requires_action", "void"`
- **`client_secret`**: `string | null`

  What the Payment Element confirms in the browser (stripe.confirmPayment / confirmSetup), where SCA happens. Present only while there is something to confirm. Saram never sees the card.
- **`failure_code`**: `string | null`

  Why a `failed`, `requires_action` or `void` top-up did not go through, for a screen to branch on. When the card was refused it is the bank's reason as Stripe names it - the `decline_code` (`generic_decline`, `insufficient_funds`, ...) or, when there is none, Stripe's error `code` (`card_declined`, `expired_card`, ...): another card may go through. Otherwise it is one of Saram's own: `requires_action` (the bank wants the person to confirm an automatic refill, which only a top-up by hand can do), `payment_failed` (an automatic refill's payment did not complete), `expired` (unpaid after a day), `voided` (the invoice was cancelled before it was paid) or `not_started` (the payment could not be started; try again later). Nothing is charged for any of them. Absent while in flight and once paid.
- **`failure_message`**: `string | null`

  The same, in a sentence written to be shown to the person: the bank's own words as Stripe gives them for the cardholder ('Your card was declined.'), or Saram's for its own codes.
- **`finished_at`**: `string | null`, format: `date-time`
- **`invoice_id`**: `string | null`

**Example:**

```json
{
  "id": "",
  "amount_usd": 1,
  "origin": "manual",
  "status": "pending",
  "invoice_id": "",
  "client_secret": "",
  "failure_code": "",
  "failure_message": "",
  "created_at": "",
  "finished_at": ""
}
```

### 403 this credential may not manage billing: an API key without the keys:admin scope, or a person whose role in this tenant is not owner or admin

### 409 the tenant is already on Saram Pro or on a subscription an operator moves; or the top-up would pass the tenant's own monthly budget

### 422 an amount out of bounds, or auto-refill turned on without a threshold, amount or card

### 502 Stripe refused the request or could not be reached; the detail is Stripe's own message

### 503 this deployment has no Stripe key configured, or its Stripe account is not set up for what was asked, so nothing is started and nothing is charged

## Schemas

- `TopUpRecord` — shown above.
- `TopUpWrite` — shown above.
