Authentication
Plan routes use provider dashboard session auth only: a logged-in session whose user is the app owner, a platform admin, or aproviderAdmins team member. Confidential M2M / Basic auth is not accepted on plan mutation routes.
GET /plans also accepts M2M Basic auth for integrators who need to read plan configuration.
POST, PUT, and DELETE additionally require canEditProviderApp (same session, with edit rights).
Base path
{clientId} is the public app_… OAuth client id.
List plans
Query parameters
Default response shape (no apiVersion)
Each plan includes: id, clientId, name, type (free | subscription | usage), priceAmount, priceCurrency, status, isNetworkDefault, isStarterDefault, includedUnits, includedUsdMicros, overageRateWei, billingCycle, openmeterPlanId, lastSyncedAt, syncError, and capabilities (array).
Billing API v2 response (apiVersion=2)
Returns { apiVersion: 2, products: [BillingProduct…] }.
Create a plan
201 Created body:
{ "id": "<new-plan-uuid>" }.
is_network_default cannot be set on custom plans. The Network Price plan is managed separately via the Plans UI.Update a plan
id (plan UUID). Omitted fields keep existing values. If capabilities is present, it replaces all bundles for that plan; omit the key to leave bundles unchanged. PUT on the Network Price plan id returns 400 — edit exclusions via the Plans UI.
200 OK: { "success": true }.
Delete a plan
status: inactive via PUT if subscribers might still reference the plan. Deleting the Network Price default plan returns 409.
200 OK: { "success": true }. 404 if the plan is missing or not owned by this app.
Sync a plan to OpenMeter
{ "success": true, "openmeterPlanId": "om_plan_...", "syncedAt": "..." }.
Starter plan
Every app has a Starter plan (isStarterDefault: true) separate from custom billing plans. It carries an includedUsdMicros allowance and is automatically subscribed to by new end-users.
Providers update the Starter allowance separately from the plans list:
PUT body: { "includedUsdMicros": "5000000" }. This triggers an immediate OpenMeter plan sync.
The Starter plan also appears in the GET /plans list (with isStarterDefault: true) but cannot be deleted or replaced by a custom plan.
Errors
OpenMeter sync behavior
When a plan is published withstatus: active and OPENMETER_URL is configured:
- A plan keyed
{clientId}:{planId}is created or updated in OpenMeter. - Flat subscription fee, included allowance on
network_fee_usd_micros, and usage rate cards are provisioned. - Per-capability
retailRateUsdentries create filtered OpenMeter features and rate cards. - Stale
openmeterPlanIdvalues are recreated automatically when OpenMeter returns plan-not-found.
Examples
Call GET/POST/PUT/DELETE from a trusted context that holds the provider dashboard session cookie.Implementation reference
Canonical contract:pymthouse docs/builder-api.md (Billing API → Plans). Code: src/app/api/v1/apps/[id]/plans/route.ts, src/app/api/v1/apps/[id]/plans/[planId]/sync/route.ts, src/app/api/v1/apps/[id]/starter-plan/route.ts.