> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gavai.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Billing

> Read and manage the billing state for a workspace — plan, usage, invoices, and payment method.

export const ErrorTable = ({errors}) => <div className="gv-error-table" role="table" aria-label="Errors and fixes">
    <div className="gv-error-table__head" role="row">
      <span className="gv-error-table__col-head" role="columnheader">Error</span>
      <span className="gv-error-table__col-head" role="columnheader">Cause</span>
      <span className="gv-error-table__col-head" role="columnheader">Fix</span>
    </div>
    {errors.map((err, i) => <div className="gv-error-table__row" role="row" key={i}>
        <code className="gv-error-table__code" role="cell">{err.code}</code>
        <div className="gv-error-table__cause" role="cell">{err.cause}</div>
        <div className="gv-error-table__fix" role="cell">{err.fix}</div>
      </div>)}
  </div>;

export const ExpectedOutput = ({caption = "Expected output", children}) => <div className="gv-output">
    <div className="gv-output__label">
      <svg className="gv-output__icon" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
        <polyline points="15 10 20 15 15 20" />
        <path d="M4 4v7a4 4 0 0 0 4 4h12" />
      </svg>
      <span>{caption}</span>
    </div>
    <div className="gv-output__body">{children}</div>
  </div>;

export const APIBadge = ({method = "GET", path, scope}) => {
  const m = method.toUpperCase();
  return <span className="gv-api-badge">
      <span className={`gv-api-badge__method gv-api-badge__method--${m.toLowerCase()}`}>
        {m}
      </span>
      <code className="gv-api-badge__path">{path}</code>
      {scope && <span className="gv-api-badge__scope">
          <span className="gv-api-badge__scope-label">scope</span>
          <code>{scope}</code>
        </span>}
    </span>;
};

export const Eyebrow = ({label, tone = "default"}) => <div className={`gv-eyebrow gv-eyebrow--${tone}`}>
    <span className="gv-eyebrow__bar" aria-hidden="true" />
    <span className="gv-eyebrow__label">{label}</span>
  </div>;

<Eyebrow label="RESOURCE" />

The Billing resource represents what gavAI charges you, the workspace owner, for using the platform. Use it to inspect your plan and usage, retrieve invoices, swap plans, and update your payment method on file. By the end of this page you will know which endpoints exist and the rules around plan changes and payment-method updates.

<Note>
  This resource covers **gavAI billing the workspace owner**. To charge your own end users through Stripe Connect, see [Monetization](/api-reference/resources/monetization).
</Note>

## A billing state in shape

<ExpectedOutput>
  ```json theme={null}
  {
    "plan":                 { "id": "plan_pro", "name": "Pro" },
    "status":               "active",
    "current_period_start": "2026-05-01T00:00:00Z",
    "current_period_end":   "2026-06-01T00:00:00Z",
    "payment_method":       { "type": "card", "last4": "4242", "brand": "visa" }
  }
  ```
</ExpectedOutput>

`payment_method` is `null` if no method is on file. Subscription `status` values include `active`, `past_due`, and `canceled`.

A workspace that hits `past_due` enters a grace window before it moves to `canceled`. During the grace window the workspace stays serviceable but writes flagged as billable trigger an in-band reminder banner; once the grace window elapses without a successful retry, the workspace transitions to `canceled` and runtime traffic is paused. The transition is one-way through the API surface — re-enabling a `canceled` workspace requires resolving the unpaid invoice from the console.

## Endpoints

<APIBadge method="GET" path="/v1/workspaces/{slug}/billing" scope="billing:read" />

Current billing state for the workspace.

<APIBadge method="POST" path="/v1/workspaces/{slug}/billing/plan" scope="billing:write" />

Change the workspace plan.

<APIBadge method="GET" path="/v1/workspaces/{slug}/billing/usage" scope="billing:read" />

Usage metrics for the current or a past billing period.

<APIBadge method="GET" path="/v1/workspaces/{slug}/billing/invoices" scope="billing:read" />

Paginated list of invoices.

<APIBadge method="POST" path="/v1/workspaces/{slug}/billing/payment-method" scope="billing:write" />

Update the payment method on file.

## Plan changes

`POST /billing/plan` takes `{ "plan_id": "..." }`. Changes take effect at the next billing period boundary unless the platform upgrades immediately (behaviour depends on plan configuration).

<ErrorTable
  errors={[
{ code: "400 invalid_plan", cause: "`plan_id` does not match a known plan.", fix: "Verify the plan id against the published plan catalogue." },
{ code: "422 plan_change_not_allowed", cause: "Plan change blocked — active trial, pending cancellation, etc.", fix: "Resolve the blocking state first, then retry the change." },
]}
/>

<Warning>
  Downgrading a plan may disable capabilities or lower usage limits. Verify the impact before calling this endpoint in production.
</Warning>

## Usage and invoices

`GET /billing/usage` returns a `line_items` array of `{ metric, quantity, unit }` records — `requests`, `storage`, `runtime_time`, and similar. Omit query parameters to read the current billing period; pass `period_start` and `period_end` to read a historical window.

Within an open period the numbers are *running totals*, refreshed in near-real-time from the runtime as events arrive. At the period boundary they freeze: the platform runs a close job that reconciles the running totals against the authoritative usage events, writes the final values, and then opens the next period. A request made during the brief close window may see slightly older numbers until the new period publishes — the response is still consistent, just not yet bumped to the new period.

Plan-level usage caps (when configured) are tracked per metric. The platform reconciles each cap against actual usage on the period boundary; an overage line item appears on the invoice for the closed period if usage exceeded the cap.

`GET /billing/invoices` is cursor-paginated. Each invoice carries `id`, `period_start`, `period_end`, `amount`, `currency` (ISO 4217), `status` (one of `draft`, `open`, `paid`, `void`, `uncollectible`), and `pdf_url`.

## Payment method

`POST /billing/payment-method` accepts `{ "token": "tok_..." }`. The typical flow is to tokenise card details client-side with Stripe.js and POST the resulting token here so raw card numbers never touch your server.

<ErrorTable
  errors={[
{ code: "400 invalid_token", cause: "The Stripe token was rejected or expired.", fix: "Re-tokenise card details with Stripe.js and resend." },
{ code: "422 payment_method_declined", cause: "Stripe declined the card during setup.", fix: "Have the workspace owner try a different card or contact their issuer." },
]}
/>

## Common errors

<ErrorTable
  errors={[
{ code: "403 insufficient_scope", cause: "Token lacks `billing:read` or `billing:write`.", fix: "Mint a token with the scope you need." },
{ code: "404 workspace_not_found", cause: "No workspace with that slug.", fix: "Verify the slug and that the token can see the workspace." },
]}
/>

## Related

<Card title="Monetization" icon="credit-card" href="/api-reference/resources/monetization">
  Charge your own end users via Stripe Connect — powers the `collectPayment` and `listPlans` capabilities.
</Card>
