PLAN_RESTRICTED
The action requires a higher plan tier — e.g. purchasing credits on the Free tier.
| Code | HTTP status | Retryable? |
|---|---|---|
| PLAN_RESTRICTED | 403 | No |
What this means
PLAN_RESTRICTED fires when an action is gated behind a paid tier and the caller is on Free. The most common trigger is `/v1/billing/credits-checkout` — Free users can RECEIVE credits via admin grants but cannot PURCHASE them. Other gated actions include `/serve/inject` (Pro+) and bulk-export operations on the dashboard.
When you'll see it
HTTP 403. Body always includes code: "PLAN_RESTRICTED". Branch on code, never on the human-readable message — wording can change without notice; the code is the stable contract.
Example response
{
"status": "error",
"code": "PLAN_RESTRICTED",
"message": "Free tier cannot purchase credit packs; upgrade to Starter or above"
}How to handle
Upgrade from app.buildonto.dev/read/billing — the cheapest Read tier is Starter at $9/mo. If you've hit this on a Serve action, the relevant tier is Pro at $19/mo. The dashboard shows exactly which tier each capability requires, so you can match the upgrade to the action.
Suggested handling in a Node client:
if (data.code === 'PLAN_RESTRICTED') {
// Surface upgrade CTA — never retry blindly.
return res.status(402).json({ error: 'Upgrade required', upgradeUrl: 'https://app.buildonto.dev/read/billing' });
}Related errors
| Code | Status | What it means |
|---|---|---|
| PAYMENT_REQUIRED | HTTP 402 | You're past the plan cap AND your credit balance is zero. |
| UNAUTHORIZED | HTTP 401 | The Bearer token is missing, malformed, or has been revoked. |
See the full error index for the complete catalog with the handling switch statement covering every code at once.