Plan Not Found
plan_not_found422
The requested plan doesn't exist. Use a valid plan ID from the pricing page.
What this means
A request referenced a billing plan by ID, and we don't have a plan matching that identifier. Either the plan ID was mistyped, refers to a plan that's been retired, or refers to a plan from a different environment (staging vs production). The action (subscribe, upgrade, downgrade) was not performed.
When you'll see this
- A subscribe request used a plan ID that no longer exists (a retired tier).
- A typo introduced an incorrect plan ID.
- A staging plan ID was used in production code or vice versa.
- An internal mapping between Asterwise plans and a third-party catalog drifted.
Learn more about how this works
Plan IDs are stable strings tied to specific tier/pricing combinations: Sandbox (free), Builder, Launch, Scale. These IDs are documented on the pricing page and don't change as long as the plan is offered. When a plan is retired (rare), its ID stops being valid and requests using it return this error.
The most common gotcha: this error is sometimes confused with subscription_not_found. They're different layers. plan_not_found means the plan catalog doesn't have the requested entry. subscription_not_found means the account has no active subscription record. One is about what plans exist; the other is about what plans the account is on.
Example response
{
"success": false,
"error": "plan_not_found",
"message": "The specified plan does not exist.",
"details": [],
"retry_after": null,
"doc_url": "https://docs.asterwise.com/reference/errors/plan_not_found",
"request_id": "req_01HXYZABCDEFGH",
"timestamp": "2026-05-25T12:34:56Z"
}
- Verify the plan ID in your code matches one of the valid plan IDs (Sandbox, Builder, Launch, Scale — exact slugs from the pricing page).
- If you're using a staged or test plan ID, swap to the production plan ID before deploying.
- If you're sure the plan should exist and it doesn't, email [email protected] — retired plans without notice are unusual but worth flagging.
Hard validation error; surface clearly during integration. Should never reach production code with valid plan IDs.
Python:
Production handler
- Python
- TypeScript
import httpx
VALID_PLAN_IDS = {"sandbox", "builder", "launch", "scale"}
def subscribe(plan_id, base_url, headers):
if plan_id not in VALID_PLAN_IDS:
raise ValueError(f"Invalid plan_id: {plan_id}")
response = httpx.post(
f"{base_url}/v1/billing/subscribe",
headers=headers,
json={"plan_id": plan_id},
timeout=15,
)
if response.status_code == 422:
body = response.json()
if body.get("error") == "plan_not_found":
raise RuntimeError(
f"Plan {plan_id} not recognized by server. "
f"Check against asterwise.com/pricing."
)
response.raise_for_status()
return response.json()
const VALID_PLAN_IDS = new Set(["sandbox", "builder", "launch", "scale"]);
async function subscribe(planId: string, baseUrl: string, headers: HeadersInit) {
if (!VALID_PLAN_IDS.has(planId)) {
throw new Error(`Invalid plan_id: ${planId}`);
}
const response = await fetch(`${baseUrl}/v1/billing/subscribe`, {
method: "POST",
headers,
body: JSON.stringify({ plan_id: planId }),
});
if (response.status === 422) {
const body = await response.json();
if (body.error === "plan_not_found") {
throw new Error(
`Plan ${planId} not recognized by server. ` +
`Check against asterwise.com/pricing.`,
);
}
}
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
Avoid this error by
- Keep plan IDs in a constants file or enum on your side. Reference plans by named constant, not raw string.
- Don't hardcode plan IDs in user-facing forms. Fetch the current valid set if needed.
- In integration tests, verify each plan ID your code uses against a live request. Catches typos at CI time.
- When migrating environments, audit plan IDs as part of the migration. They're stable but not portable across providers.