Plans & Billing
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.
GET endpoints support format negotiation — append
?format=yaml or ?format=md for compact, agent-friendly output.
View your subscriptions
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
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
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
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:
{
"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
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.