# Billing API

Source: https://docs.oiy.ai/docs/api/billing

Read complete billing history and understand hosted Checkout authentication and reconciliation.



Billing history reads use normal account authentication. Hosted Checkout requires a verified interactive sign-in and enabled payments. Reading history does not initiate a payment or change a resource rate.

## History endpoints [#history-endpoints]

The current preview source provides these account-owned reads:

| Method | Path                   | Result                                                  |
| ------ | ---------------------- | ------------------------------------------------------- |
| GET    | `/api/billing/summary` | Aggregate every matching posting in the selected window |
| GET    | `/api/billing/ledger`  | One ledger page and a continuation cursor               |

Both endpoints require inclusive `from` and `to` UTC dates in `YYYY-MM-DD` format. A window covers 1–90 days and must end no later than today. Query earlier windows for older history. They do **not** require Stripe Checkout to be enabled.

```sh
curl --get "$OIY_API_URL/api/billing/summary" \
  -H "Authorization: Bearer $OIY_API_KEY" \
  --data-urlencode "from=2026-09-01" \
  --data-urlencode "to=2026-09-29"
```

Optional filters are `kind`, `serviceId`, and `volumeId`. Resource filters are mutually exclusive; a service selects compute and a volume selects storage. An explicitly supplied kind must agree with that resource type.

## Summary response [#summary-response]

The response contains `window`, `generatedAt` (Unix milliseconds), `currency: "USD"`, `totals`, `days`, `resources`, and `resourcesTruncated`.

Totals cover **every matching entry**, not just one page or the account snapshot's latest 100 entries. Daily buckets cover the entire selected date window. The ranking contains at most 20 resource groups; the truncation flag applies to ranking detail, not totals.

All monetary totals are integer micro-USD. `credits` includes positive adjustments, while `adjustments` is a signed adjustment subtotal. They overlap; use the provided signed `net` instead of adding them together. See [report interpretation](/docs/billing-history).

## Ledger pagination [#ledger-pagination]

```sh
curl --get "$OIY_API_URL/api/billing/ledger" \
  -H "Authorization: Bearer $OIY_API_KEY" \
  --data-urlencode "from=2026-09-01" \
  --data-urlencode "to=2026-09-29" \
  --data-urlencode "kind=storage" \
  --data-urlencode "limit=30"
```

The HTTP page size is 1–100, default 50. The response contains `window`, `entries`, `nextCursor`, `generatedAt`, and `amountsMayUpdate: true`. Each entry extends the ordinary ledger fields with a nullable `resource` object containing type, ID, and a nullable name.

For the next page, repeat the original dates and filters and pass the returned `nextCursor` as `cursor`. A cursor is opaque and at most 2,048 characters. Stop when it is `null`. Refresh without a cursor to include new insertions.

Open hourly rows can update even during pagination. Amounts and `balanceMicros` are not an immutable financial snapshot. Dates describe **postings**, not exact workload execution intervals. Existing meters accrue through the time of the read, as they do for ordinary account snapshots.

## Source-distributed clients [#source-distributed-clients]

```python
from oiy_ai import Client

client = Client()
page = client.billing(
    view="ledger",
    from_date="2026-09-01",
    to_date="2026-09-29",
    kind="storage",
    limit=30,
)
```

For continuation, supply both original dates and all original filters, with `cursor=page["nextCursor"]`. Do not start another request when that value is `None`. The CLI and Python client default to a 30-day summary when dates are omitted; this client convenience is not an HTTP API default.

## Checkout availability [#checkout-availability]

`GET /api/catalog` reports `payment.provider`, `payment.status`, and the deployment's payment mode. A disabled payment capability cannot create a Checkout session. Catalog visibility does not grant permission to charge a card.

## Create a Checkout session [#create-a-checkout-session]

`POST /api/billing/checkout` requires:

* An enabled payment configuration.
* A verified Firebase identity obtained through interactive sign-in.
* An `Idempotency-Key` for this specific top-up intent.
* JSON with the integer field `amountCents`.

**A personal Oiy API key cannot initiate a top-up.** Use the console's Billing flow for ordinary payments. An API-key-authenticated integration receives `INTERACTIVE_SIGN_IN_REQUIRED` if it attempts this operation while payments are otherwise enabled.

The current input range is 500–1,000,000 cents (USD 5–10,000). This is an API validation range, not a recommendation or automatic spending authorization.

```json
{ "amountCents": 2000 }
```

The response has this shape:

```json
{
  "id": "11111111-1111-4111-8111-111111111111",
  "url": "https://checkout.stripe.com/EXAMPLE_ONLY"
}
```

The UUID and URL above are illustrative. Use the actual returned URL for the hosted payment flow. Creating a session does not credit the account and does not, by itself, complete a payment.

## Read a top-up [#read-a-top-up]

`GET /api/billing/topups/{id}` uses normal account authentication and returns:

| Field         | Meaning                                                                                    |
| ------------- | ------------------------------------------------------------------------------------------ |
| `id`          | Top-up UUID                                                                                |
| `amountCents` | Integer USD amount in cents                                                                |
| `status`      | `pending` or `paid`                                                                        |
| `expiresAt`   | Checkout expiry as a Unix timestamp in **seconds**, or `null` before a session is attached |

The account ledger uses micro-USD, while this endpoint uses cents. Most other account timestamps use milliseconds. Keep these units distinct.

`pending` does not prove payment failed. Returning from Checkout to the console does not prove credit was applied. Signed payment confirmation records the credit; inspect both order state and account balance before creating another order.

## Retries and errors [#retries-and-errors]

| Code                           | Action                                                                                                       |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| `PAYMENTS_NOT_ENABLED`         | Payment configuration is not available. Use the console for current availability.                            |
| `INTERACTIVE_SIGN_IN_REQUIRED` | Sign in interactively with a verified account.                                                               |
| `IDEMPOTENCY_CONFLICT`         | The key was reused for a different amount or payment mode. Reconcile the existing intent.                    |
| `CHECKOUT_EXPIRED`             | The session or uncertain order is too old to reuse. Inspect the previous order before starting a new top-up. |
| `TOPUP_PAID`                   | This order has already been credited. Read the account state.                                                |
| `CHECKOUT_UNAVAILABLE`         | A usable Checkout URL was not returned. Retry the same top-up intent rather than inventing a new one.        |
| `PAYMENT_CONFIGURATION`        | The deployment's payment configuration is inconsistent. Repeated client requests will not fix it.            |

Payment-provider webhooks are an internal integration surface. Clients should never call them to grant themselves credit.
