Errors
The exact JSON error bodies the PandaStack API returns, indexed by HTTP status and literal error string.
Every JSON error the API returns is flat. There is one required field, error,
holding a human-readable message:
{ "error": "not found" }Some responses add a detail sibling with a longer explanation:
{ "error": "unauthorized", "detail": "missing bearer token" }Quota and rate-limit responses add context fields alongside error (for example
workspace, tier, limit_per_hr). There is no code field, no nested
error object, and no details object.
Branch on the HTTP status, not on the message text. The error string is
written for humans and can change; the status is the contract.
Request IDs
The request ID is a response header, not a body field. Every API response
carries X-Request-Id. If you send your own X-Request-Id (up to 64
characters), the API reuses it and forwards it to the compute node, so one ID
correlates the whole request across services.
curl -sS -D- -o/dev/null https://api.pandastack.ai/v1/sandboxes \
-H "Authorization: Bearer $PANDASTACK_API_KEY" | grep -i x-request-idStatus codes
| HTTP | Literal error string | What it means | What to do |
|---|---|---|---|
| 400 | git_url is required, name is required, cmd required, invalid json, env must be a string map, idle_timeout_seconds must be >= 60 | The request body is missing or malformed | Fix the payload; do not retry unchanged |
| 401 | unauthorized (with detail: missing bearer token, invalid api token, or jwt required) | No credential, an unknown API key, or a token-auth call on a JWT-only route | Send Authorization: Bearer $PANDASTACK_API_KEY; see Authentication |
| 402 | free quota exceeded | Free credit is spent; the workspace is paused | Add a payment method or upgrade — below |
| 402 | monthly free credit exhausted | Same cap, enforced at the compute node with usage numbers attached | Same fix; the body includes credit_usd and cost_usd_used |
| 402 | subscription not active | Stripe subscription is past_due or canceled; running resources keep running, new creates are refused | Update payment; the body carries subscription_status |
| 402 | org limit reached: your <tier> plan allows <n> org(s). Upgrade to create more. | Plan org cap | Upgrade or reuse an existing org |
| 402 | seat limit reached: your <tier> plan allows <n> member(s). Upgrade to invite more. | Plan seat cap | Upgrade or remove a member |
| 403 | account suspended | The workspace is blocked from creating compute | Contact support (detail says so) |
| 404 | not found | Unknown ID, or the resource belongs to another workspace | Check the ID and your current org — Wrong workspace |
| 404 | sandbox not found, sandbox not found or not running | The sandbox ID does not exist on the node handling the request | Re-read GET /v1/sandboxes |
| 404 | no code deployed yet | Function exists but has never been deployed | POST /v1/functions/{id}/deploy first |
| 409 | sandbox not running (paused or hibernated) — resume/wake it first | You called exec (or another guest operation) on a paused sandbox | POST /v1/sandboxes/{id}/resume. Hibernated sandboxes wake automatically |
| 409 | managed sandbox: delete it from the feature that owns it (Databases or Apps) | You called DELETE /v1/sandboxes/{id} on a sandbox owned by an app or database | Delete the app or database instead |
| 409 | volume already exists | Volume name is taken | Pick another name |
| 429 | rate limit: 50 req/burst, 25/sec sustained per workspace | Per-workspace token bucket. Retry-After: 1 is set | Back off and retry |
| 429 | hourly create limit exceeded | Too many creates in the last hour; body has limit_per_hr and used_last_hr | Wait, or raise the workspace limit |
| 429 | max sandboxes reached | Concurrent-sandbox cap; body has max and active | Delete idle sandboxes |
| 429 | workspace resource quota exceeded | Aggregate CPU or memory cap; body has cpu_used/cpu_max/memory_mb_used/memory_mb_max | Delete idle sandboxes or raise the cap |
| 429 | monthly cpu-seconds quota exceeded, monthly budget exceeded | A workspace monthly ceiling you set was hit | Raise the ceiling or wait for the monthly reset |
| 500 | Varies — the underlying error text | A bug or an unhandled failure | Retry once; if it persists, report it with the X-Request-Id |
| 502 | no available compute node | No node could take the request. Under load this is capacity | Retry with backoff — below |
| 502 | agent unreachable | A node was chosen but the proxy could not reach it | Retry with backoff |
| 507 | insufficient host memory for sandbox | A specific node refused the placement on memory admission | Retry; the scheduler picks again |
402: free quota exceeded
The free tier includes $5.40 of credit per month. You get a warning email at 80%. At 100%, compute stops: running resources are paused and creates are refused. Nothing is deleted — your apps, databases, and data stay intact.
The control plane refuses the create before it reaches a node:
{
"error": "free quota exceeded",
"detail": "This account has used its free credit. Add a payment method to keep creating sandboxes."
}Upgrading lifts the pause on the next reconcile, roughly a minute later. You do not need to recreate anything.
A paused app's URL answers 503 with an HTML page (not JSON), plus
Retry-After: 3600 and an X-Pandastack-App: suspended header. See
App serves 503.
502: no available compute node
Fleet capacity is bounded by memory. When no node can hold a new VM, the create fails at routing time with a plain 502:
{ "error": "no available compute node" }This is transient. Retry with exponential backoff. If it repeats over minutes, delete sandboxes you are no longer using and contact support — see 502 Bad Gateway on create.
502 here does not mean the API is down. GET /healthz will still return 200 —
the failure is placement, not the control plane.
Retrying
There is no retryable flag and no retry_after_sec field. Decide from the
status:
- Retry with backoff:
429,500,502,507. Only429from the per-workspace rate limiter sets aRetry-Afterheader (1second). - Do not retry unchanged:
400,401,403,404,409— the request or the resource state has to change first. - 402 clears when billing is sorted, not on retry.
Creates are not idempotent. Retrying a POST /v1/sandboxes that actually
succeeded but whose response you lost will create a second sandbox; list and
reconcile rather than blind-retrying.
Reporting a problem
Include the X-Request-Id response header and the resource ID. Those two make
a request traceable end to end. Email hello@pandastack.ai, or open an
issue at
github.com/pandastack-io/pandastack-ai/issues.
For symptom-first debugging, start at Troubleshooting.