---
title: Pricing and credits
description: How credits are priced, quoted, held and settled; what you pay for and what you do not; where to read balances, the ledger and usage.
---

# Pricing and credits

Price a generation before submitting it, show the customer that price, and follow its hold through to a charge or refund. Published model prices, quotes and the itemized ledger all use credits.

![Credits reserved and settled as work completes](../assets/art/billing.jpg)

## Per-model pricing

Each model publishes its billing unit. Read the current catalog rather than keeping a second price list in your application.

| Modality | Billing unit | How the charge is calculated |
| --- | --- | --- |
| Image | `per_image` | The selected model and quality-tier price for each image. |
| Video | `per_clip` | The published price assumes `baseline_seconds`, normally 5 seconds. Charge `ceil(credits × duration_seconds / baseline_seconds)` for the requested duration. |
| Text to speech | `per_character` | The headline `credits` compares a 1,000-character script. Actual charge is `max(minimum_credits, ceil(characters / characters_per_credit))`; the minimum bills a short line as 200 characters. Count Unicode characters, not bytes. |
| 3D and other flat-rate generation | `per_generation` | One published rate per generation; inspect the selected model's `cost.unit`. |

<!-- gen:catalog-summary -->
| Image | Video | Audio | 3D |
| --- | --- | --- | --- |
| 48 | 74 | 17 | 2 |
<!-- /gen -->

See [Model APIs](./models.html) for the full per-model tables. A missing `cost` means the price is not published; display an unknown price, not zero. Nolgia does not expose a GPU-time billing fallback.

> [!WARNING]
> For speech, do not scale the 1,000-character headline price to price another length. Use `characters_per_credit` and `minimum_credits`, or request an exact quote.

## What you pay for

### Generations

Nolgia takes a credit hold when the request is accepted. Delivery consumes that hold; it does not create a second charge. A failed job normally releases the hold, subject to the policy-refusal exception below.

| Setting | Effect on credits |
| --- | --- |
| Image count | Each requested image contributes its model price. |
| Video duration | Scales the selected per-clip price by `duration_seconds / baseline_seconds`, rounded up. |
| Speech text | Uses the Unicode character count and the published minimum. |
| `quality` | Selects the complete per-tier price from `quality.options[].credits`; do not add that value to the base price. Video tiers use the same clip baseline. |
| `render_quality` | GPT Image 2.5's `xhigh` and `max` add the published credits **per image**, independently of the native/2k/4k quality tier. `auto`, `low`, `medium` and `high` do not add credits. |
| `generate_audio` | Where `audio_surcharge` is published, the default price includes it. `generate_audio=false` subtracts it before duration scaling. |

### Agent turns and Studio renders

| Work | Billing behavior |
| --- | --- |
| Agent turn | A separate `agent_turn` ledger row. A turn charges at least the published per-turn rate; a more expensive turn uses `max(flat_rate(model), ceil(provider_cost_usd / 0.01))`. Its row supplies `tokens` and `provider_cost_usd` when those explain a charge above the flat rate. |
| Generation started by an agent | Its own generation hold and ledger row, separate from the turn that requested it. |
| Studio render | The ledger reserves a `render` kind, but composition renders currently take no credit hold. Generating the source images, clips or narration is billed separately. |

![Credits move from available to held, then charged or refunded](../assets/diagrams/credits-hold-settle.svg)

## What you are not charged for

| Outcome | What happens to credits |
| --- | --- |
| Ordinary failed generation | The hold is released; check `failure.credits_refunded`. |
| `409` duplicate submission | No new hold and no second charge. Follow the existing `job_id`. |
| `408` request or wait timeout | The timeout response itself adds no charge. A wait timeout does not cancel or refund the accepted job; that job can still finish and settle its existing hold. |
| Quote with `POST /jobs/cost` | No job, reservation or charge. |
| Checking a job's status or waiting again | No additional generation charge. |

> [!WARNING]
> A failed status alone does not prove a refund. A content-policy refusal is refunded unless the provider billed the refused attempt; one the provider billed for consumes the hold. `failure.credits_refunded: true` means the job cost nothing; `false` means it was charged. An absent or null value means the ledger outcome is not available yet, or was never recorded. See [Model errors](./errors.html).

## Checking prices programmatically

### Published model prices

| Property | Value |
| --- | --- |
| Endpoint | `GET /pricing/models` |
| Authentication | Public; no token required |
| Response | `ModelPricingList`, with the same prices as `GET /models` |
| Caching | `Cache-Control: public, max-age=300` |

```bash title="Read published prices"
$ curl --fail-with-body -sS https://api.nolgia.ai/v1/pricing/models
```

This trimmed `flux-pro` entry preserves the real `cost` and `quality` values from the production model fixture. The public response wraps entries in `models`; other models and other pricing fields are omitted here.

```json title="Published price excerpt"
{
  "models": [
    {
      "id": "flux-pro",
      "modality": "image",
      "min_tier": "starter",
      "cost": { "credits": 4, "unit": "per_image" },
      "quality": {
        "default": "native",
        "options": [
          { "credits": 4, "id": "native", "premium": false },
          { "credits": 17, "id": "2k", "premium": true },
          { "credits": 54, "id": "4k", "premium": true }
        ]
      }
    }
  ]
}
```

<!-- gen:fields schema=ModelPricingList -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `models` | array of `ModelPricing` | Yes |  |
<!-- /gen -->

<!-- gen:fields schema=ModelPricing -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | The catalog id a generation request sends as `model`. |
| `display_name` | string | Yes | What to call this model on a customer-facing surface. Always present: no published model is nameless, because a raw id is sometimes a provider route path and must never be rendered. |
| `maker` | string | No | The company that made the model (`Google`, `Kuaishou`, `Black Forest Labs`).… |
| `summary` | string | No | One sentence about the model. Absent until written; copy arrives incrementally, and an absent summary renders as nothing. |
| `recommended` | boolean | No | Mirrors `Model.recommended` — the pick for its modality. |
| `video` | `VideoCapabilities` | No |  |
| `audio` | `AudioCapabilities` | No |  |
| `image` | `ImageCapabilities` | No |  |
| `references` | `ReferenceCapabilities` | No |  |
| `restore` | boolean | No | Mirrors `Model.restore`. |
| `remove_background` | boolean | No | Mirrors `Model.remove_background`. |
| `image_enhance` | boolean | No | Mirrors `Model.image_enhance`. |
| `image_expand` | boolean | No | Mirrors `Model.image_expand`. |
| `modality` | `Modality` | Yes |  |
| `min_tier` | `SubscriptionTier` | Yes | Minimum subscription plan required to run this model; `starter` means every plan can.… |
| `cost` | `ModelCost` | No |  |
| `quality` | `QualityCapabilities` | No |  |
| `three_d` | `ThreeDCapabilities` | No |  |
| `aspect_ratios` | array of string | Yes | The output aspect ratios this model renders (`video.aspect_ratios` for video models, `image.aspect_ratios` for image models), as plain strings so one field spans both enums.… |
| `render_quality` | `RenderQualityCapabilities` | No | Mirrors `image.render_quality` from `GET /models`.… |
| `inpaint_mask` | boolean | No | Mirrors `image.inpaint_mask` from `GET /models`: this model can edit only PART of an image, guided by a mask.… |
| `capabilities` | array of `VideoCapabilityTag` | No | Mirrors `Model.capabilities`: the video capability CLASSES this model exists to serve, as opposed to the individual inputs it accepts.… |
<!-- /gen -->

<!-- gen:fields schema=ModelCost -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `credits` | integer | Yes | Credits charged per unit at the model's DEFAULT audio state.… |
| `unit` | `ModelCostUnit` | Yes |  |
| `audio_surcharge` | integer, nullable | No | Extra credits (per the model's `unit`) that a soundtrack adds, when the provider charges more to render audio.… |
| `video_input_credits` | integer, nullable | No | `credits` for a request that carries a reference video, at the model's default tier, when the provider bills a video input at a different rate.… |
| `input_image_credits` | integer, nullable | No | Extra credits for each input image past `free_input_images`, on a model that charges for input images.… |
| `free_input_images` | integer, nullable | No | Present with `input_image_credits`: how many input images a request carries before the surcharge starts. |
| `baseline_seconds` | integer, nullable | No | Present only when unit is per_clip. The clip duration in seconds that `credits` assumes. The actual charge scales with the requested duration as ceil(credits * duration_seconds / baseline_seconds). |
| `characters_per_credit` | integer, nullable | No | Present only when unit is per_character.… |
| `minimum_credits` | integer, nullable | No | Present only when unit is per_character.… |
<!-- /gen -->

<!-- gen:fields schema=QualityOption -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | Tier identifier accepted by the `quality` request parameter. |
| `credits` | integer | Yes | Credits charged per the model's `cost.unit` when this tier is selected (for video, per `baseline_seconds` clip), at the model's DEFAULT audio state.… |
| `audio_surcharge` | integer, nullable | No | Extra credits this tier's soundtrack adds when the provider charges more for audio.… |
| `video_input_credits` | integer, nullable | No | Credits per `baseline_seconds` clip at this tier when the request carries a reference video (`video_asset_ids` / `video_urls`), for a model whose provider bills a video input at a different rate than a text or image render.… |
| `premium` | boolean | Yes | Marks a premium (highest-quality, higher credit cost) tier so clients can present it distinctly from standard options. |
| `aspect_ratios` | array of string | No | When present, the tier renders at these aspect ratios ONLY (a subset of the model's `video.aspect_ratios`); a request pairing the tier with any other `aspect_ratio` is refused with a 400 before any credits are held.… |
<!-- /gen -->

### Quote the exact request

| Property | Value |
| --- | --- |
| Endpoint | `POST /jobs/cost` |
| Input | `kind` and the matching generation body, such as `image` |
| Result | The exact credits the same settings will hold, plus a confirmation token you may return on submit |
| Re-quote when | The request changes or `expires_at` passes |

These examples use the generated clients. The CLI currently has no dedicated quote command; its tab uses curl with the same `NOLGIA_TOKEN`. The response below is a production capture, not a claim that each language example was run live.

```bash tab="curl" title="Quote an image example"
$ curl --fail-with-body -sS https://api.nolgia.ai/v1/jobs/cost \
  -H "Authorization: Bearer $NOLGIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"kind":"image","image":{"model":"flux-pro","prompt":"a paper-cut mountain range at dawn"}}'
```

```bash tab="CLI" title="Quote from the terminal example"
$ # No dedicated nolgia quote command; call the quote endpoint directly.
$ curl --fail-with-body -sS https://api.nolgia.ai/v1/jobs/cost \
  -H "Authorization: Bearer $NOLGIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"kind":"image","image":{"model":"flux-pro","prompt":"a paper-cut mountain range at dawn"}}'
```

```ts tab="TypeScript" title="Quote an image example"
import { createNolgiaClient } from "@nolgia/sdk";

const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!);
const { data: quote, error } = await nolgia.POST("/jobs/cost", {
  body: {
    kind: "image",
    image: { model: "flux-pro", prompt: "a paper-cut mountain range at dawn", num_images: 1 },
  },
});
if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`);
console.log(quote.credits, quote.expires_at);
```

```python tab="Python" title="Quote an image example"
import os
from nolgia import AuthenticatedClient
from nolgia.api.jobs import quote_job_cost
from nolgia.models import GenerateImageRequest, ImageModel, JobCostKind, JobCostQuote, JobCostRequest

client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"])
quote = quote_job_cost.sync(client=client, body=JobCostRequest(
    kind=JobCostKind("image"),
    image=GenerateImageRequest(model=ImageModel("flux-pro"), prompt="a paper-cut mountain range at dawn"),
))
if not isinstance(quote, JobCostQuote):
    raise SystemExit(f"refused: {quote}")
print(quote.credits_, quote.expires_at)
```

```rust tab="Rust" title="Quote an image example"
use nolgia_client::{types, ClientBuilder};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = ClientBuilder::new("https://api.nolgia.ai/v1")
        .bearer_token(std::env::var("NOLGIA_TOKEN")?)
        .build()?;
    let prompt: types::GenerateImageRequestPrompt = "a paper-cut mountain range at dawn".parse()?;
    let image: types::GenerateImageRequest = types::GenerateImageRequest::builder()
        .model("flux-pro").prompt(Some(prompt)).num_images(1u64).try_into()?;
    let quote = client.quote_job_cost()
        .body_map(|b| b.kind(types::JobCostKind::Image).image(Some(image)))
        .send().await?.into_inner();
    println!("{} {}", quote.credits, quote.expires_at);
    Ok(())
}
```

The real `cost-image.json` capture quotes one native `flux-pro` image. Its prompt differs from the reusable example above, and its opaque confirmation token is redacted.

```json title="200 quote response"
{
  "balance_credits": 2573,
  "basis": "one_generation",
  "confirmation_token": "…",
  "credits": 4,
  "expires_at": "2026-09-21T03:43:14.171728407Z",
  "kind": "image",
  "model": "flux-pro",
  "settings": [ { "label": "Model", "value": "flux-pro" } ],
  "sufficient_credits": true
}
```

<!-- gen:fields schema=JobCostRequest -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `kind` | `JobCostKind` | Yes |  |
| `image` | `GenerateImageRequest` | No |  |
| `video` | `GenerateVideoRequest` | No |  |
| `audio` | `GenerateAudioRequest` | No |  |
| `three_d` | `Generate3DRequest` | No |  |
| `set` | `GenerateSetRequest` | No |  |
<!-- /gen -->

<!-- gen:fields schema=JobCostQuote -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `kind` | `JobCostKind` | Yes |  |
| `model` | string | Yes | The model id the quote is for, after defaulting — not necessarily the one you sent. |
| `credits` | integer | Yes | Display credits. This is the number the submit will hold, computed by the same pricer the submit path uses, for exactly these settings. It is what to show the customer. |
| `basis` | one of `one_generation`, `set_run` | Yes | Whether `credits` covers one generation or a whole set run. |
| `members` | integer, nullable | No | Number of set members `credits` covers. Present only when `basis` is `set_run`. |
| `duration_seconds` | integer, nullable | No | The billed duration the price is for, on a video quote. |
| `quality` | string, nullable | No | The resolved quality tier the price is for, when the model has one. |
| `settings` | array of `JobCostSetting` | Yes | The settings that made this price, in the order a dialog should show them. Human-readable; do not parse it. |
| `balance_credits` | integer, nullable | No | The wallet balance this quote was compared against, when one could be read. |
| `sufficient_credits` | boolean | Yes | Whether the balance covers `credits` right now. Advisory — the submit re-checks. |
| `confirmation_token` | string | Yes | Short-lived, single-request proof that this price was quoted. Send it back as `confirmation_token` on the matching generate request. Opaque: do not parse or construct it. |
| `expires_at` | string | Yes | After this the token is refused and a submit carrying it fails with `confirmation_rejected`. Re-quote. |
<!-- /gen -->

![Confirmation gate: quote the request, show the price, and submit with its token](../assets/diagrams/confirmation-gate.svg)

Return `confirmation_token` on the unchanged generation body if you want submission bound to the quoted request and price. The token is optional; when supplied, a stale, malformed, foreign or mismatched token fails with `422 confirmation_rejected`. Quote again. `sufficient_credits` is advisory because submission checks the balance again.

## Balances and the ledger

### Spendable balances

Personal Access Tokens you create spend `available_for_api`. The app, the Nolgia Agent, organization API keys and connected assistants (ChatGPT, Claude or any MCP client connected through OAuth) spend `available_for_app`. `available_for_credential` is the figure for the credential that made the request, and `credential_channel` names its channel. Monthly `app_subscription` credits expire at the subscription cycle end; `shared_topup` credits do not expire and are spendable by either surface.

```bash title="Read your balance"
$ curl --fail-with-body -sS https://api.nolgia.ai/v1/billing/credits \
  -H "Authorization: Bearer $NOLGIA_TOKEN"
```

This schema-built example shows a personal account with 100 subscription credits and 200 top-up credits.

```json title="200 balance response example"
{
  "user_id": "00000000-0000-4000-8000-000000000001",
  "scope": "personal",
  "app_subscription": 100,
  "shared_topup": 200,
  "total": 300,
  "available_for_app": 300,
  "available_for_api": 200,
  "buckets": [
    { "wallet_id": "00000000-0000-4000-8000-000000000002", "type": "app_subscription", "balance": 100, "expires_at": "2026-10-01T00:00:00Z" },
    { "wallet_id": "00000000-0000-4000-8000-000000000003", "type": "shared_topup", "balance": 200, "expires_at": null }
  ]
}
```

<!-- gen:fields schema=CreditBalance -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `user_id` | string | Yes |  |
| `app_subscription` | integer | Yes | Monthly app-only credits that expire at the subscription cycle end. |
| `shared_topup` | integer | Yes | Shared app/API prepaid credits that do not expire. |
| `total` | integer | Yes |  |
| `available_for_app` | integer | Yes | App-visible credits; subscription bucket plus shared top-up overflow. |
| `available_for_api` | integer | Yes | API prepaid credits; shared top-up only at launch. |
| `credential_channel` | `CreditChannel` | No |  |
| `available_for_credential` | integer | No | What the credential that made this request can spend: `available_for_app` on the app channel, `available_for_api` on the API channel (see `credential_channel`).… |
| `buckets` | array of `CreditBalanceBucket` | Yes |  |
| `scope` | `BillingScope` | No |  |
| `organization_id` | string | No | Set when `scope` is `organization`: the balances above are the ORGANIZATION's shared pool (the caller's personal wallets are not consulted inside an organization) and `user_id` is the caller. |
| `seats` | integer | No | Seats on the organization's subscription; organization scope only. |
| `seat_limit` | integer, nullable | No | The organization's seat cap (`null` = unlimited); organization scope only. |
| `member_budget` | integer, nullable | No | The caller's monthly credit budget in the organization (`null` = unlimited); organization scope only. |
| `member_spent_this_month` | integer | No | What the caller has spent from the organization's pool this UTC calendar month (held plus consumed reservations); organization scope only. |
<!-- /gen -->

<!-- gen:fields schema=CreditBalanceBucket -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `wallet_id` | string | Yes |  |
| `type` | `CreditWalletType` | Yes |  |
| `balance` | integer | Yes |  |
| `expires_at` | string, nullable | No |  |
<!-- /gen -->

### Itemized transactions

`GET /billing/transactions` returns newest-first rows. The debit when a hold is taken **is** the charge; completion writes no second debit. Releasing it writes a `refund` for the same amount. `balance_after` belongs to the particular `wallet_id`, not to the combined account balance.

```bash title="Read the ledger"
$ curl --fail-with-body -sS 'https://api.nolgia.ai/v1/billing/transactions?limit=20' \
  -H "Authorization: Bearer $NOLGIA_TOKEN"
```

This schema-built example uses the documented Quick Start amounts: a 12-credit `veo-3.1-lite` four-second charge and its 12-credit refund. The row ids, timestamps and wallet balances are illustrative; this is not a captured ledger response.

```json title="200 ledger response example"
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000005",
      "occurred_at": "2026-09-21T03:36:00Z",
      "kind": "refund",
      "wallet": "topup",
      "wallet_id": "00000000-0000-4000-8000-000000000003",
      "credits": 12,
      "balance_after": 200,
      "description": "Refund · Video generation · veo-3.1-lite · 4 s",
      "model": "veo-3.1-lite",
      "job_id": "00000000-0000-4000-8000-000000000006"
    },
    {
      "id": "00000000-0000-4000-8000-000000000004",
      "occurred_at": "2026-09-21T03:35:00Z",
      "kind": "generation",
      "wallet": "topup",
      "wallet_id": "00000000-0000-4000-8000-000000000003",
      "credits": -12,
      "balance_after": 188,
      "description": "Video generation · veo-3.1-lite · 4 s",
      "model": "veo-3.1-lite",
      "job_id": "00000000-0000-4000-8000-000000000006"
    }
  ],
  "next_cursor": null,
  "scope": "personal"
}
```

<!-- gen:params op=listCreditTransactions -->
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `cursor` | query | No | Opaque pagination cursor returned by a prior response. |
| `limit` | query | No | Rows per page. |
| `from` | query | No | Inclusive window start (RFC 3339). Omit for no lower bound. |
| `to` | query | No | Exclusive window end (RFC 3339). Omit for no upper bound. |
| `kind` | query | No | Only rows of this kind. |
| `wallet` | query | No | Only rows on this wallet type (default `all`). |
| `member_id` | query | No | Organization context only: rows caused by this member. Owner, admin and billing roles may name any member; other roles only themselves. `400` in the personal space. |
<!-- /gen -->

<!-- gen:fields schema=CreditTransactionPage -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `items` | array of `CreditTransaction` | Yes | Rows newest first. |
| `next_cursor` | string, nullable | Yes | Pass as `cursor` for the next page; `null` on the last page. |
| `scope` | `BillingScope` | Yes |  |
| `organization_id` | string | No | Set when `scope` is `organization`. |
<!-- /gen -->

<!-- gen:fields schema=CreditTransaction -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes |  |
| `occurred_at` | string | Yes |  |
| `kind` | `CreditTransactionKind` | Yes |  |
| `wallet` | `CreditTransactionWallet` | Yes |  |
| `wallet_id` | string | Yes |  |
| `wallet_expires_at` | string, nullable | No | When the wallet's credits expire (subscription wallets); `null` for the top-up wallet. |
| `credits` | integer | Yes | Signed movement. Negative for charges and debits, positive for grants, top-ups, redemptions and refunds. |
| `balance_after` | integer | Yes | The wallet's balance after this row (running sum of the wallet identified by `wallet_id`). |
| `description` | string | Yes | Plain-language label, for example `Video generation · seedance-2.5 · 5 s` or `Refund · Agent turn`. Never contains an em dash. |
| `model` | string | No | Catalog model id when the row charges or refunds a generation, and the agent brain (for example `claude-opus-5-5`) when it charges or refunds an agent turn. Label it client-side. |
| `preset` | string | No | Preset slug when the generation was launched from a preset. |
| `job_id` | string | No | The generation job behind a `generation` charge or its refund. |
| `session_id` | string | No | The agent chat session behind an `agent_turn` charge or its refund. |
| `tokens` | integer | No | Tokens the agent turn behind this row reported, across every iteration of its reasoning loop.… |
| `provider_cost_usd` | number | No | Measured provider cost in US dollars of the agent turn behind this row.… |
| `render_id` | string | No | The render behind a `render` charge or its refund. |
| `member` | `CreditTransactionMember` | No |  |
| `meter` | `CreditTransactionMeter` | No |  |
<!-- /gen -->

| Kind | Meaning |
| --- | --- |
| `grant` | Monthly plan credits or credits granted by Nolgia. |
| `topup` | A credit purchase. |
| `redeem` | A credit-code redemption. |
| `generation` | A generation charge. |
| `agent_turn` | A chat turn charge. |
| `render` | Reserved for composition-render charges; renders currently take no hold. |
| `refund` | Credits returned for a prior charge. |
| `adjustment` | A plan correction or other manual movement. |

### Usage totals

`GET /billing/transactions/summary` rolls up the same rows and scope. It defaults to the current UTC month; `credits_used` and `credits_refunded` are positive totals, while `by_kind[].credits` is signed.

```bash title="Read credit usage"
$ curl --fail-with-body -sS https://api.nolgia.ai/v1/billing/transactions/summary \
  -H "Authorization: Bearer $NOLGIA_TOKEN"
```

This schema-built response illustrates the charge and refund above, with no other movements in the month.

```json title="200 usage summary example"
{
  "from": "2026-09-01T00:00:00Z",
  "to": "2026-10-01T00:00:00Z",
  "scope": "personal",
  "credits_used": 12,
  "credits_refunded": 12,
  "credits_added": 0,
  "by_day": [ { "date": "2026-09-21", "credits_used": 12, "credits_refunded": 12 } ],
  "by_kind": [
    { "kind": "generation", "credits": -12, "count": 1 },
    { "kind": "refund", "credits": 12, "count": 1 }
  ]
}
```

<!-- gen:fields schema=CreditUsageSummary -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `from` | string | Yes |  |
| `to` | string | Yes |  |
| `scope` | `BillingScope` | Yes |  |
| `organization_id` | string | No | Set when `scope` is `organization`. |
| `member_id` | string | No | Set when the summary is narrowed to one member's rows. |
| `credits_used` | integer | Yes |  |
| `credits_refunded` | integer | Yes |  |
| `credits_added` | integer | Yes | Grants, top-ups and redemptions in the window. |
| `by_day` | array of `CreditUsageDay` | Yes |  |
| `by_kind` | array of `CreditUsageKindTotal` | Yes |  |
<!-- /gen -->

## Plans, top-ups and codes

See [nolgia.ai/pricing](https://nolgia.ai/pricing) for current plans and credit purchases. Subscriptions supply monthly app credits; top-ups supply shared prepaid credits. A model's `min_tier` and its credit price are separate: adding credits does not unlock an out-of-plan model.

| Operation | Use it for |
| --- | --- |
| `GET /billing/subscription` | Read the plan, status and current period end. |
| `POST /billing/portal-link` | Open the returned expiring Stripe Customer Portal URL to manage billing. |
| `POST /credits/redeem` | Redeem an issued code into shared top-up credits, once per account. |
| `GET /billing/auto-refresh` | Read automatic top-up settings. |
| `PUT /billing/auto-refresh` | Update whether auto-refresh is enabled, its threshold and purchase amount. |

<!-- gen:endpoints tag=Billing -->
| Method | Path | What it does |
| --- | --- | --- |
| [GET](../api/#tag/billing/get/billing/subscription) | `/billing/subscription` | Get the current user's subscription state. |
| [GET](../api/#tag/billing/get/billing/trial) | `/billing/trial` | The account's free trial state and whether it can start one. |
| [POST](../api/#tag/billing/post/billing/trial) | `/billing/trial` | Start the account's seven-day free trial. |
| [POST](../api/#tag/billing/post/billing/trial/confirm) | `/billing/trial/confirm` | Start the free trial from an emailed confirmation link. |
| [POST](../api/#tag/billing/post/billing/portal-link) | `/billing/portal-link` | Create a Stripe Customer Portal session and return its URL. |
| [GET](../api/#tag/billing/get/billing/credits) | `/billing/credits` | Get the current user's credit wallet balances. |
| [GET](../api/#tag/billing/get/billing/transactions) | `/billing/transactions` | List the itemized credit ledger, newest first. |
| [GET](../api/#tag/billing/get/billing/transactions/summary) | `/billing/transactions/summary` | Credits used per day and totals per kind over a window. |
| [GET](../api/#tag/billing/get/billing/auto-refresh) | `/billing/auto-refresh` | Get the current user's auto-refresh settings. |
| [PUT](../api/#tag/billing/put/billing/auto-refresh) | `/billing/auto-refresh` | Update the current user's auto-refresh settings. |
| [POST](../api/#tag/billing/post/credits/redeem) | `/credits/redeem` | Redeem a credit code for shared top-up credits. |
<!-- /gen -->

A code refusal returns `404` for an unknown code, `410` for a deactivated, expired or exhausted code, `409` if this account already redeemed it, and `429` for rate-limited attempts. A repeated redemption can never grant credits twice.

## Organizations

In an organization, spending uses the organization's wallets, not your personal balance. `GET /organizations/{id}/credits` reports the shared pool, seats, and monthly member budgets. An unlimited budget is `null`; the member's month-to-date spend includes both held and consumed reservations.

| Role | Credit and usage visibility |
| --- | --- |
| Owner, admin, billing | The organization pool and every member's rows. |
| Member, viewer | The shared pool and their own member row or spend. |
| Non-member | `404`; the organization is not exposed. |

`GET /organizations/{id}/usage` groups consumed credits by member, model or UTC day; it defaults to the current UTC calendar month and excludes held and released reservations. [Usage and activity](./usage.html#organization-usage) explains why these totals differ from gross ledger charges. For organization contracts, see the Enterprise option on [Pricing](https://nolgia.ai/pricing).

:::cards
- [Usage and activity](./usage.html): Find spend totals, recent work and the audit trail.
- [Concurrency limits](./concurrency-limits.html): Read the limits on simultaneous generation.
- [Model APIs](./models.html): Compare the catalog's models and published prices.
:::
