# Billing

GPU Forge is **prepaid**. You add credit to your balance by card, and usage is deducted from it as it happens. There is no subscription, no minimum term and no setup fee, and credit does not expire.

| Endpoint | Purpose |
|---|---|
| `GET /v1/billing` | Balance, status, cost right now, spend, machines, card, top-up settings |
| `GET /v1/billing/usage` | What each machine and job cost over the last N hours |
| `GET /v1/billing/capabilities` | What each program cost today and over the last N hours, with its daily budget |
| `GET /v1/billing/busy` | Per program: how busy your machines were, how long they sat parked, and what you paid |
| `GET /v1/billing/top-ups` | Money in and out, newest first |
| `POST /v1/billing/top-up` | Charge the card saved on the console and add the amount to your balance |
| `GET /v1/notifications` | Messages about your account (low balance, machines stopped, top-ups) |
| `POST /v1/notifications/read` | Mark notifications read |

## What you pay

| What | How it is charged |
|---|---|
| A running machine | Every minute, at its hourly price |
| A parked machine | Every minute, at its storage price (shown by `GET /v1/fleet/{pod_id}/stop-choices`) |
| A job | Once, when it finishes: render seconds × the program's hourly price ÷ 3600, at least $0.002. Failed jobs are free |

### How machine prices are set

GPU Forge samples the providers' listings regularly and keeps the **market price** of each GPU model. The hourly price you pay for a machine is based on that market price plus GPU Forge's service fee. Storage on a parked machine is charged on the same basis.

- You see the price before renting: `price.customer_usd_hr` on each search result (when null, `price.total_usd_hr`), and `price_usd_hr` in the deploy answer.
- The price is **locked when the machine is rented**. Later market changes never change a running machine's bill.
- Each ready program's usual hourly price is `avg_price_per_hour` in `GET /v1/catalog`; jobs are priced from it.

For typical figures (cost per image, per minute of video, per GPU hour) see the public pricing page at [gpuforge.io/pricing](https://gpuforge.io/pricing).

## Your balance

```bash
curl -s "https://api.gpuforge.io/v1/billing" -H "Authorization: Bearer $GF_KEY"
```

```json
{
  "user_id": "usr_...",
  "billing": "prepaid",
  "status": "ok",
  "balance_usd": 42.17,
  "cost_now_usd_hr": 1.12,
  "hours_left": 37.65,
  "stops_machines_at": null,
  "spend_usd": {"last_24h": 3.41, "last_7d": 18.9, "last_30d": 57.2},
  "machines": [
    {"pod_id": "hello-gpu-3f9a1c", "name": "hello-gpu", "capability": "hello-gpu",
     "gpu": "RTX 4090", "gpu_count": 1, "state": "active", "cost_usd_hr": 0.53}
  ],
  "card": {"brand": "visa", "last4": "4242", "exp_month": 12, "exp_year": 2028},
  "auto_topup": {"enabled": true, "below_usd": 10.0, "add_usd": 25.0},
  "auto_topup_fires_below_usd": 10.0,
  "daily_cap_usd": 100.0,
  "topped_up_last_24h_usd": 0.0
}
```

| Field | Description |
|---|---|
| `status` | `ok`, `low` (less than about an hour left at the current spend) or `empty` ($0 or less) |
| `balance_usd` | Your balance |
| `cost_now_usd_hr` | What everything on your account costs per hour right now (running machines at their price, parked ones at their storage price) |
| `hours_left` | How long the balance lasts at that rate (null when nothing is running) |
| `stops_machines_at` | When your machines will be ended if the balance stays empty |
| `spend_usd` | Spend over the last 24 hours, 7 days and 30 days, jobs included |
| `machines` | Each machine on your account and what it costs you per hour now |
| `card` | The saved card (brand, last four digits, expiry), or null |
| `auto_topup` | Your automatic top-up rule, or null |
| `auto_topup_fires_below_usd` | The balance below which automatic top-up fires right now (see below) |
| `daily_cap_usd` | The most agent and automatic top-ups may add in any 24 hours |
| `topped_up_last_24h_usd` | How much of the cap was used in the last 24 hours |

## Adding credit

**On the console** (Billing): pay by card through a Stripe page. You can save the card for later top-ups at the same time. A top-up is $5–$5,000. Your own top-ups on the console are not limited by the daily cap.

**From the API or CLI**, using the card you saved on the console:

```bash
curl -s "https://api.gpuforge.io/v1/billing/top-up" \
  -H "Authorization: Bearer $GF_KEY" -H "Content-Type: application/json" \
  -d '{"amount_usd": 20}'
```

```json
{
  "top_up": {"topup_id": "top_1a2b3c4d", "amount_usd": 20.0, "trigger": "agent", "status": "succeeded",
             "requested_at": "...", "finished_at": "...", "detail": null, "receipt_url": "https://..."},
  "balance_usd": 62.17,
  "note": null
}
```

- Charges **only** the card saved on the console; an API key cannot add or change a card.
- Counts toward your **daily cap** (default $100 per 24 hours), together with automatic top-ups.
- `402`: the card was declined, or the bank wants you to confirm the payment yourself; top up on the console.
- `409`: no card saved, another top-up is still going through, or the daily cap would be passed. The message says which.
- If Stripe has not confirmed yet, `status` is `pending` and `note` says so; `GET /v1/billing/top-ups` shows the outcome.

### Automatic top-up

On the console you can set a rule: *when the balance is below X, add Y* from your saved card (Y between $5 and $5,000).

- It also fires early when the balance covers less than one hour of your current spend, so a fast-spending account does not run dry between checks. `auto_topup_fires_below_usd` shows the line in effect now.
- It does not fire within 10 minutes of another card payment, and after a declined charge it waits an hour before trying again.
- When nothing is running, it fires only if the balance is empty.
- It counts toward the daily cap. If the cap stops it, you get a notification.
- Removing the saved card stops automatic top-up.

### Daily cap

The daily cap ($100 by default) bounds how much **agent** top-ups (`POST /v1/billing/top-up` with an API key) and **automatic** top-ups may add in any rolling 24 hours. You change it on the console; API keys cannot.

## Spending limits on programs

| Setting (on a capability) | Effect |
|---|---|
| `max_price_per_hour` | No machine above this price is rented |
| `surge_price_per_hour` | A higher limit used only when nothing fits under the normal one |
| `daily_budget_usd` | Once today's (UTC) spend for this program reaches it, its machines are parked or ended and nothing new starts until 00:00 UTC. A start refused for this reason answers `402` with `daily_budget_reached: true` |
| `idle_ttl_seconds` | Idle machines park or end after this long |

See [Capabilities](capabilities.md).

## When the balance runs out

- **Low** (less than about an hour of your current spend left): a notification, and automatic top-up if it is on.
- **Not enough for a new machine:** starting or waking a machine needs the balance to cover about one hour of everything running plus the new machine; otherwise it is refused with `402`.
- **$0:** no new machines, wakes or jobs (`402`). You are told when your machines will stop.
- **$0 for about 10 minutes:** every machine on your account is ended and its disk deleted. You are notified.


## Usage and history

### Per machine and job

```
GET /v1/billing/usage?hours=24
```

`hours` is 1–2160 (90 days). Returns `total_usd` and `machines`, most expensive first:

| Field | Description |
|---|---|
| `pod_id` | The machine, or `job:<job_id>` for a job |
| `name`, `capability`, `gpu`, `gpu_count` | What it was |
| `cost_usd` | What it cost in the window |
| `hours_billed` | Billed time in hours |
| `created_at`, `destroyed_at` | The machine's lifetime |

### Per program

```
GET /v1/billing/capabilities?hours=168
```

For each of your capabilities: `today_usd` (since 00:00 UTC), `window_usd` (over `hours`), `daily_budget_usd` and `budget_reached`. Machine spend only; jobs are listed in `/v1/billing/usage`.

```
GET /v1/billing/busy?hours=168
```

Per program, over the last `hours`: how busy your running machines were, how long they sat parked, and what you paid.

### Top-ups

```
GET /v1/billing/top-ups?limit=50
```

Newest first, up to 200. Each entry: `topup_id`, `amount_usd`, `trigger`, `status` (`pending`, `succeeded` or `failed`), `requested_at`, `finished_at`, `detail` and `receipt_url`.

| `trigger` | Meaning |
|---|---|
| `checkout`, `website` | You paid on the console |
| `agent` | An API key or the CLI topped up from the saved card |
| `auto` | Automatic top-up |
| `refund` | Money refunded to your card (a negative amount) |
| `dispute` | A disputed card payment taken off the balance (negative), or given back if the dispute is resolved in your favour |
| `admin` | Credit added by GPU Forge |

## Notifications

```
GET /v1/notifications?unread=true&limit=50
```

Messages GPU Forge sent you: balance low or empty, machines stopped, top-ups, automatic top-up paused by the cap, a parked machine about to be released. Returns `notifications` (newest first, up to 200) and `unread_count`.

```bash
curl -s "https://api.gpuforge.io/v1/notifications/read" \
  -H "Authorization: Bearer $GF_KEY" -H "Content-Type: application/json" \
  -d '{"all": true}'
```

Send `{"ids": ["..."]}` to mark some, or `{"all": true}` for all. Agents should pass these messages on to their person.

## Refunds

Unused prepaid credit is refundable. Write to [support@gpuforge.io](mailto:support@gpuforge.io) from your account's email address and we refund the unused balance to the original card, usually within 10 business days. Compute already used (machine time and completed jobs) is not refundable. If something on our side failed and charged you, tell us and we will put it right.

If a card payment is disputed, the same amount is deducted from your balance and the account is put on hold until it is settled.

## Payments and card data

Card payments are processed by **Stripe**. GPU Forge never sees or stores your full card number; it keeps only the card brand, the last four digits and the expiry date, to show you which card is saved. Saving or removing a card is done only on the console, by you.
