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

# Billing

> View your plan and credit usage, and manage your subscription through Stripe.

Anona holds the Stripe relationship, so you never touch Stripe keys directly. Paid-plan
changes go through Stripe Checkout or the Customer Portal; free-plan changes happen in
place.

| Method and path             | What it does                                     | Role required  |
| --------------------------- | ------------------------------------------------ | -------------- |
| `GET /v1/billing`           | Current plan, credit usage, and available plans. | Any member     |
| `POST /v1/billing/upgrade`  | Moves to a free plan, in place.                  | Owner or admin |
| `POST /v1/billing/checkout` | Starts Stripe Checkout for a paid plan.          | Owner or admin |
| `POST /v1/billing/portal`   | Opens the Stripe Customer Portal.                | Owner or admin |
| `POST /v1/billing/cancel`   | Cancels at the end of the billing period.        | Owner          |

## View billing status

`GET /v1/billing` returns the current plan, credit usage, and every plan you could switch
to.

`credits_reset_at` is when the credit allowance refreshes: on a paid plan, the end of the
Stripe billing period, so it falls on the day of the month you subscribed; on Free, the
signup anniversary. It is **not** the 1st of the calendar month. A date landing in a month
too short for it (the 31st, in February) moves to the last day of that month.

<Note>
  `current_month_ops` and the `line_items` counts are still measured over the **calendar
  month to date**, which is a different window from the credit period whenever your billing
  date is not the 1st. `credits_used` is the figure that matches `credits_reset_at`.
</Note>

```json theme={null}
{
  "plan_id": "starter",
  "plan_name": "Starter",
  "price_per_month_usd": 9.0,
  "credits_limit": 6000,
  "credits_used": 1240,
  "credits_remaining": 4760,
  "credits_reset_at": "2026-09-20T14:32:11Z",
  "rate_limit_per_min": 30,
  "has_subscription": true,
  "current_month_ops": 620,
  "memories_added": 400,
  "searches": 200,
  "insights": 20,
  "line_items": [
    { "op_type": "Memories Added", "count": 400, "unit_price": 2.0, "subtotal": 800.0 },
    { "op_type": "Searches", "count": 200, "unit_price": 1.0, "subtotal": 200.0 },
    { "op_type": "Insights", "count": 20, "unit_price": 5.0, "subtotal": 100.0 }
  ],
  "available_plans": [
    { "plan_id": "free", "name": "Free", "price_per_month_usd": 0.0, "credits": 500, "rate_limit_per_min": 10, "max_spaces": 1 },
    { "plan_id": "pro", "name": "Pro", "price_per_month_usd": 49.0, "credits": 40000, "rate_limit_per_min": 150, "max_spaces": 20 }
  ]
}
```

<Note>
  `line_items` is credit-cost accounting, not a Stripe invoice. The amount you are actually
  billed comes from Stripe.
</Note>

## Change plan

### Free tiers

```http theme={null}
POST /v1/billing/upgrade

{ "plan_id": "free" }
```

Valid only for plans priced at zero. Any paid plan returns `400 payment_required` and
directs you to `/v1/billing/checkout` instead.

### Paid plans

```http theme={null}
POST /v1/billing/checkout

{ "plan_id": "pro" }
```

```json theme={null}
{ "checkout_url": "https://checkout.stripe.com/c/pay/..." }
```

Redirect the user to `checkout_url`. Stripe's webhook flips the organization onto the
new plan and syncs its quota once payment completes, so polling `/v1/billing` right
after the redirect may still show the old plan for a few seconds.

## Manage an existing subscription

`POST /v1/billing/portal` requires an existing Stripe customer, meaning you have
completed checkout at least once.

```json theme={null}
{ "portal_url": "https://billing.stripe.com/p/session/..." }
```

Redirect the user there to update a payment method, view invoices, switch between paid
plans, or cancel. Plan switches are prorated by Stripe, which is why they belong in the
portal rather than in a second checkout.

An **upgrade** applies immediately, and the organization's unused credits are added on top
of the new plan's allowance for the current period. A **downgrade** is scheduled for the
end of the period already paid for, exactly like a cancellation: the organization keeps
the larger plan and its limits until then, and `GET /v1/billing` keeps reporting the
larger plan until the change lands.

## Cancel

```http theme={null}
POST /v1/billing/cancel
```

```json theme={null}
{ "message": "Subscription will cancel at end of billing period." }
```

Cancellation takes effect at the end of the current billing period, the same date as
`credits_reset_at`. The organization keeps its paid plan and credits until then.

## Error responses

| Status | Code                     | Cause                                                                                                                                                                                                                                                              |
| ------ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 400    | `payment_required`       | `/upgrade` was called with a paid plan. Use `/checkout`.                                                                                                                                                                                                           |
| 400    | `invalid_plan`           | `/checkout` was called with a free or unknown `plan_id`.                                                                                                                                                                                                           |
| 400    | `no_subscription`        | `/portal` or `/cancel` was called with no Stripe subscription on file.                                                                                                                                                                                             |
| 403    | `insufficient_role`      | The caller's role does not permit the billing action.                                                                                                                                                                                                              |
| 409    | `subscription_exists`    | A subscription is already active. A second checkout would create a **second** subscription and bill twice, so plan changes go through `/portal`. Also returned by `/upgrade` when downgrading to Free while Stripe is still billing. Cancel in the portal instead. |
| 409    | `checkout_in_progress`   | A checkout for a *different* plan is already open for this organization. Finish or abandon it first; retrying the **same** plan is allowed and returns the original session.                                                                                       |
| 503    | `billing_not_configured` | Stripe is not configured on this deployment.                                                                                                                                                                                                                       |
| 503    | `price_not_configured`   | No Stripe price is configured for that plan.                                                                                                                                                                                                                       |

See the full [error reference](/api-reference/errors).
