<!-- Source: https://docs.geopera.com/api-reference/guides/billing · Markdown for LLMs -->

# Billing & credits

Geopera bills in **credits**: a prepaid balance you spend on orders, processing, and
other `external_spend` operations. Credits are pegged at **100 credits = A$1**. You can
see prices in your local currency, but the wallet and settlement are in AUD.

This guide covers the customer money path: check your balance, top up (including the
3-D Secure case), and automate top-ups. All payment operations require the
`billing:write` scope; reads require `billing:read`.

| Operation | Side-effect | Scope | Use |
|---|---|---|---|
| `billing.credits.balance` | read | `billing:read` | Current credit balance |
| `billing.credits.transactions` | read | `billing:read` | Ledger history |
| `payment_methods.create_setup_intent` | compute | `billing:write` | Start attaching a card |
| `payment_methods.attach` | compute | `billing:write` | Persist a confirmed card |
| `payment_methods.set_default` | compute | `billing:write` | Choose the off-session card |
| `billing.topup` | spend | `billing:write` | Charge the card → grant credits |
| `billing.set_auto_topup` | compute | `billing:write` | Auto top-up below a threshold |

## Check the balance

```bash
curl -s https://api.geopera.com/v1/op/billing.credits.balance \
  -H "Authorization: Bearer $TOKEN"
```

Reads have idiomatic `GET` routes, so this is a plain GET. The ledger behind it is
itemised — every reservation, charge, refund, and grant — and reachable via
`billing.credits.transactions`.

## Attach a card

Top-ups charge a saved card off-session, so attach one first. The flow uses Stripe's
SetupIntent so card details never touch Geopera:

1. `payment_methods.create_setup_intent` → returns a `client_secret`.
2. Confirm the SetupIntent client-side with Stripe.js (the card data goes straight to
   Stripe).
3. `payment_methods.attach` with the confirmed PaymentMethod id to persist it.
4. `payment_methods.set_default` to make it the card used for off-session charges.

```bash
curl -s -X POST https://api.geopera.com/v1/op/payment_methods.create_setup_intent \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{}'
```

```json
{ "client_secret": "seti_..._secret_...", "setup_intent_id": "seti_..." }
```

## Top up credits

`billing.topup` charges the org's default card and grants credits. Specify the amount
one of two ways:

- **`aud_cents`** — the dollar amount to charge (minimum `10000` = A$100), or
- **`credits`** — the credit count to grant (the charge is derived from it).

A **+5% volume bonus** applies above A$20,000. Send an `Idempotency-Key` (threaded to
Stripe) so a retry charges and grants at most once.

```bash
curl -s -X POST https://api.geopera.com/v1/op/billing.topup \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: top_8a1f...-unique" \
  -d '{ "aud_cents": 50000 }'
```

```json
{
  "topup_attempt_id": "ta_5c2b...",
  "payment_intent_id": "pi_3Q...",
  "status": "succeeded",
  "credits_granted": 50000,
  "new_balance": 73500,
  "bonus_credits": 0
}
```

### The 3-D Secure retry

If the bank requires a Strong Customer Authentication (3-D Secure) challenge, the top-up
can't complete off-session. You get a `402 Payment Required` whose body carries a
`client_secret` at the top level:

```json
{
  "title": "Authentication Required",
  "status": 402,
  "detail": "Card requires authentication (3DS) — customer must complete on-session",
  "client_secret": "pi_3Q..._secret_...",
  "requires_action": true
}
```

Handle it like this:

1. Take `client_secret` and complete the challenge client-side with Stripe.js
   (`stripe.confirmCardPayment` / `handleCardAction`).
2. Once the bank confirms, **retry `billing.topup` with the same `Idempotency-Key`** —
   the now-authenticated PaymentIntent settles and credits are granted exactly once.

Other `402` cases: **no default card** (`Payment Method Required` — attach one) and
**card declined** (`Card Declined` — try another card). A Stripe outage surfaces as
`502`, and an unconfigured billing backend as `503`.

## Automate top-ups

Keep the balance above a floor without manual intervention:

```bash
curl -s -X POST https://api.geopera.com/v1/op/billing.set_auto_topup \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "threshold_credits": 5000,
    "topup_amount_credits": 50000,
    "daily_charge_cap_aud": 500
  }'
```

When the balance drops below `threshold_credits`, the platform charges the default card
to grant `topup_amount_credits`, never exceeding `daily_charge_cap_aud` in a day.
Enabling auto top-up requires a default card.

## How spend operations charge

Every `external_spend` operation (placing an order, accepting a tasking quote, creating
a processing job) **reserves** credits up front, then settles on the outcome:

- **Reserve** at placement — your available balance drops immediately.
- **Capture** on success — the reservation becomes a charge.
- **Refund / void** on cancel or failure — the reservation is returned.

This reserve-then-settle model is why an order or job never silently over- or
under-charges, and why a cancelled order returns your credits. If you have no credits,
free organizations can instead authorize a **card hold** at checkout (captured on
delivery, voided on failure) — see billing modes in
[Ordering archive imagery](/api-reference/guides/ordering#billing-modes).

## Enterprise invoicing

Enterprise organizations don't prepay a wallet — their spend **accrues to a monthly
invoice** instead of reserving credits. Spend operations behave identically from the
caller's perspective (`billingMode: "enterprise"` in the response); the difference is in
settlement. Approval gates for large purchases (`billing.approvals.*`) are available for
organizations that opt into them.
