# Billing Auto Refill

- **Method:** `POST`
- **Path:** `/api/v1/billing/auto-refill`
- **Operation ID:** `billing_auto_refill`
- **Tags:** usage

Set auto-refill and the monthly budget. Turning auto-refill on wants a threshold, an amount and a card
on file (`POST /billing/setup`); if the balance is already under the threshold, the first refill starts
now rather than at the next run.

## 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: `AutoRefill`, no additional properties

The tenant's auto-refill setting and monthly budget (`POST /api/v1/billing/auto-refill`, and on
`GET /api/v1/billing`).

With `auto_refill` on, whenever the balance less what running runs hold falls below `refill_below_usd`, Saram
charges the card on file `refill_amount_usd` - never past `monthly_budget_usd` in a calendar month, manual
top-ups included. A declined charge, or one the bank wants authenticated, turns auto-refill off.

- **`auto_refill` (required)**: `boolean`
- **`monthly_budget_usd`**: `number | null`, minimum: `0`

  The most Saram may charge the card in a calendar month; null is no budget.
- **`refill_amount_usd`**: `integer | null`, minimum: `10`, maximum: `500`
- **`refill_below_usd`**: `number | null`, minimum: `0`

**Example:**

```json
{
  "auto_refill": true,
  "refill_below_usd": 0,
  "refill_amount_usd": 10,
  "monthly_budget_usd": 0
}
```

## Responses

### 200 Successful Response

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

schema: `BillingOverview`, no additional properties

`GET /api/v1/billing`: the tenant's plan, prepaid balance, card on file, auto-refill and recent top-ups.

The balance is Saram's ledger, not Stripe's: `included` is the plan's monthly credit and expires at month end,
`purchased` is top-ups and does not; `held` is what runs in flight have reserved, and `available` is what a
new run can hold against.

- **`auto_refill` (required)**: `object`, schema: `AutoRefillState`, no additional properties

  The auto-refill setting as `GET /api/v1/billing` reads it: the setting, and the refill it last did not
  charge. Only the setting is written back (`POST /api/v1/billing/auto-refill` takes `AutoRefill`).
  - **`auto_refill` (required)**: `boolean`
  - **`last_skipped`**: `object | null`, schema: `RefillSkip`, no additional properties

    The latest refill that was due and not charged, so a screen can say 'Auto-refill is on, but this month's budget is spent'. Null once a refill has been started since, once the setting has been changed (by POST /billing/auto-refill, or by auto-refill turning itself off after a declined card), and for a monthly\_budget skip once its calendar month is over, because the new month's budget has room again.
    - **`at` (required)**: `string`, format: `date-time`

      When the latest skipped refill was decided. Each skip moves it.
    - **`detail` (required)**: `string`

      The same in a sentence: for the budget, what was already charged and what would still fit.
    - **`reason` (required)**: `string`, possible values: `"monthly_budget", "no_card"`

      `monthly_budget`: the refill would take this month's top-ups, manual and automatic, past monthly\_budget\_usd, so it waits for a higher budget or the next calendar month. `no_card`: there is no card on file to charge, so it waits for one to be saved through POST /billing/setup.
  - **`monthly_budget_usd`**: `number | null`, minimum: `0`

    The most Saram may charge the card in a calendar month; null is no budget.
  - **`refill_amount_usd`**: `integer | null`, minimum: `10`, maximum: `500`
  - **`refill_below_usd`**: `number | null`, minimum: `0`
- **`available_usd` (required)**: `number`
- **`balance_usd` (required)**: `number`
- **`billing_status` (required)**: `string`
- **`budget_used_usd` (required)**: `number`

  This month's top-ups, paid or still in flight.
- **`card_on_file` (required)**: `boolean`
- **`held_usd` (required)**: `number`
- **`included_usd` (required)**: `number`
- **`plan` (required)**: `string`
- **`purchased_usd` (required)**: `number`
- **`top_ups` (required)**: `array`

  The newest twenty, newest first. No client secrets.

  **Items:**

  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`
- **`included_credit_usd`**: `number`, default: `0`

  The credit your plan includes each month, which expires at month end: $1 on Free, $10 on Pro. What this month has left of it is included\_usd.
- **`limits`**: `object`, schema: `PlanLimits`, no additional properties

  What your plan allows, from the plan's row in force now.
  - **`automation`**: `boolean`, default: `true`

    Whether your plan includes Saram's built-in automation: filing a run's issues on GitHub and commenting on the pull request (connectors.github), the run-finished webhook (connectors.webhook), and the CI action that posts a run spec with them. False on Free and true on Pro. On a plan without it, a run spec naming either connector is refused when it is validated or started, never run without it; the API and MCP are open on every plan.
  - **`evidence_retention_days`**: `integer | null`

    How many days your plan keeps a run's evidence - its videos, screenshots and step traces - counted from the run's creation: 14 on Free, 90 on Pro. A run keeps the longer of this and what your plan kept when the run was created, and says the date it applies as evidence\_expires\_at; the run's findings are kept after it. Null when your plan sets no number, and then this deployment's own number applies.
  - **`max_concurrent_sessions`**: `integer | null`

    How many sessions of one run your plan runs at the same time: 2 on Free, 8 on Pro. A run whose budget.max\_concurrent\_sessions asks for more runs this many at once, and a run that names none runs as many as the browser pool has seats, up to this. Null when your plan sets no limit.
  - **`max_members`**: `integer | null`

    How many people your workspace holds, every role counted: 2 on Free, 10 on Pro. An invitation accepted once members\_used has reached it does not become a member, and the person is told why when they sign in; nobody already in is removed. Null when your plan sets no limit.
- **`members_used`**: `integer`, default: `0`

  How many people are in your workspace now, every role counted: see limits.max\_members.
- **`monthly_fee_usd`**: `number`, default: `0`

  Your plan's monthly fee, before tax: $0 on Free, $20 on Pro. From the plan's row now.
- **`subscription`**: `object | null`, schema: `SubscriptionState`, no additional properties

  Your Saram Pro subscription, when there is one: whether it renews or ends, and when. Absent on a tenant that never started a Pro checkout.
  - **`cancel_at_period_end` (required)**: `boolean`

    True after Pro was cancelled (DELETE /billing/subscription): it stays Pro until current\_period\_end, then the tenant moves to Free. POST /billing/subscription takes the cancellation back.
  - **`status` (required)**: `string`

    Stripe's: `incomplete` until the first invoice is paid, then `active`; `past_due` when a renewal failed; `canceled` once it has ended, `incomplete_expired` for a checkout nobody paid.
  - **`current_period_end`**: `string | null`, format: `date-time`

    When the month already paid for ends: the renewal date, or the date Pro ends when cancel\_at\_period\_end is true. Absent until Stripe has said.
- **`usage_price_usd`**: `number | null`

  USD per Usage on your plan now: available\_usd divided by this is 'about N Usage'. Absent on a plan billed in steps, which has no Usage price.

**Example:**

```json
{
  "plan": "",
  "limits": {
    "max_concurrent_sessions": 1,
    "evidence_retention_days": 1,
    "max_members": 1,
    "automation": true
  },
  "monthly_fee_usd": 0,
  "included_credit_usd": 0,
  "usage_price_usd": 1,
  "subscription": {
    "status": "",
    "cancel_at_period_end": true,
    "current_period_end": ""
  },
  "members_used": 0,
  "billing_status": "",
  "included_usd": 1,
  "purchased_usd": 1,
  "balance_usd": 1,
  "held_usd": 1,
  "available_usd": 1,
  "card_on_file": true,
  "auto_refill": {
    "auto_refill": true,
    "refill_below_usd": 0,
    "refill_amount_usd": 10,
    "monthly_budget_usd": 0,
    "last_skipped": {
      "at": "",
      "reason": "monthly_budget",
      "detail": ""
    }
  },
  "budget_used_usd": 1,
  "top_ups": [
    {
      "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

### 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

- `AutoRefill` — shown above.
- `AutoRefillState` — shown above.
- `BillingOverview` — shown above.
- `PlanLimits` — shown above.

  What the tenant's plan allows, as the plan stands now.

  Each limit is `null` when the plan sets none, and then the platform's own bound applies instead.
- `RefillSkip` — shown above.
- `SubscriptionState` — shown above.
- `TopUpRecord` — shown above.
