# Plans & billing

Source: https://opvs.ai/docs/guides/plans

> Customer-facing endpoints for viewing plans, managing a subscription, and reading billing history.

Customer-facing endpoints for viewing plans, managing a subscription, and reading billing history. All endpoints are **brand-scoped** — every request is resolved to your brand via session cookie or brand-scoped PAT, and only ever returns your own brand's data.

Base path: `/api/v1/dashboard/plans`

Authentication: dashboard session **or** brand-scoped PAT (the standard OPVS brand auth). See [Authentication](/developers/authentication).

GET endpoints support [format negotiation](/developers/guides/format-negotiation) — append `?format=yaml` or `?format=md` for compact, agent-friendly output.

## View your subscriptions

```http
GET /api/v1/dashboard/plans/me
```

Returns your active / trialing / past-due subscriptions, each with per-service monthly **entitlement bars** (`cap` = your monthly allowance, `used` = current usage). A `cancel_at_period_end` flag drives the "cancelling on …" state.

## Browse what you can buy

```http
GET /api/v1/dashboard/plans/browse           # tariffs (with annual pricing + savings)
GET /api/v1/dashboard/plans/browse/bundles   # multi-product discount bundles
```

`is_current` marks tariffs you already subscribe to. `stripe_annual_price_id` being present means annual billing is available for that tariff.

## Billing history

```http
GET  /api/v1/dashboard/plans/invoices        # your last 20 Stripe invoices
POST /api/v1/dashboard/plans/billing-portal  # → { url } to the Stripe Billing Portal
```

## Subscribe & change plan

```http
POST /api/v1/dashboard/plans/upgrade-preview # proration preview (no commit)
POST /api/v1/dashboard/plans/subscribe       # → { checkout_url } (Stripe Checkout)
POST /api/v1/dashboard/plans/change          # switch tier / interval in place
POST /api/v1/dashboard/plans/cancel          # schedule cancel at period end
POST /api/v1/dashboard/plans/resume          # undo a scheduled cancel
```

`subscribe` body:

```json
{
  "tariff_version_id": 10,
  "billing_interval": "month",
  "success_url": "https://app.opvs.ai/dashboard/plans?ok=1",
  "cancel_url": "https://app.opvs.ai/dashboard/plans"
}
```

`success_url` / `cancel_url` must point at an allowed dashboard origin (else `400`).

### Status codes

Code

When

`404`

Unknown `tariff_version_id`, or no active subscription to change/cancel/resume

`409`

Annual billing requested but not offered · resume when nothing is cancelling · **already subscribed to this product** (use `/change` or `/resume` instead of a second subscription)

`429`

A concurrent plan change for the same item is already in flight

`cancel` and `resume` schedule the change for the end of the current billing period — you keep access until then, and `resume` un-cancels cleanly.

## Recent denials

```http
GET /api/v1/dashboard/plans/recent-denies
```

Your recently denied requests (last 30 days) when a rate, cost, or quota limit was hit — `service_type`, `reason_code`, `retry_after_s`, and the usage-vs-limit that triggered it. Empty until usage enforcement is active for your services.
