Skip to content

API Reference

The PandaStack REST API is the programmatic control plane behind everything you can do in the dashboard and the CLI: create projects, provision databases, deploy edge functions, schedule cronjobs, wire up monitors, and manage your organization and billing.

Every resource is documented in this section, grouped by the thing it manages:

  • Authentication — tokens, headers, the card wall, and rate limits
  • Projects — static sites, container apps, deploys, build logs, domains, routing
  • Databases — PostgreSQL and MySQL provisioning, credentials, backups, tiers
  • Functions — edge functions and scheduled functions
  • Cronjobs — scheduled container runs
  • Metrics & analytics — performance metrics, audience analytics, live SSE streams
  • Monitors — uptime monitors, heartbeats, alerts
  • Organizations & teams — orgs, teams, members, invitations, account
  • Usage & limits — plan allowances and metered usage
  • Billing — plans, subscriptions, invoices, cards
  • Integrations — GitHub App and the marketplace catalog

All endpoints live under a single host and are versioned by path prefix:

https://api.pandastack.io

Every documented route is prefixed with /v1 (for example /v1/projects), except the unauthenticated liveness probe /health and the two signature-verified webhook receivers (/webhook and /api/v1/webhook/github-events).

The simplest authenticated call is GET /v1/me, which returns the current user and the organizations they belong to. It requires only a valid token — no active organization needs to be selected first.

GET /v1/me

Terminal window
curl https://api.pandastack.io/v1/me \
-H "Authorization: Bearer $PANDASTACK_TOKEN"
{
"success": true,
"data": {
"id": 42,
"email": "you@example.com",
"username": "you",
"firstName": "Ada",
"lastName": "Lovelace",
"avatarUrl": null,
"mfaEnabled": false,
"organizations": [
{ "organizationId": "1907", "name": "Acme", "role": "owner", "cardVerified": true }
]
}
}

See Authentication for how to obtain $PANDASTACK_TOKEN.

Every API response — success or failure — is wrapped in a consistent envelope.

A successful response always carries success: true and a data payload:

{ "success": true, "data": { /* resource */ } }

A handful of write endpoints (deletes, mark-as-read, and async-triggering mutations) return a bare acknowledgement instead of a full resource:

{ "success": true }
{ "success": true, "data": { "async": true } }

A failed response carries success: false and an error object:

{
"success": false,
"error": {
"code": "bad_request",
"message": "Human-readable, safe-to-display message.",
"fields": { "name": "Name is required" },
"traceId": "8f3c2a1e-…"
}
}
FieldTypeNotes
codeErrorCodeOne of the enum values in the table below.
messagestringSafe, user-facing message. Never contains a stack trace.
fieldsrecord<string,string>Optional. Per-field validation messages, keyed by field name.
traceIdstringOptional. Correlates the error to server logs when you contact support.

The error.code enum maps one-to-one to an HTTP status code:

codeHTTPMeaning
bad_request400The request was malformed or failed validation (see fields).
unauthorized401No credential, or the credential is invalid/expired.
forbidden403Authenticated, but not allowed (role too low, or the user is banned).
not_found404The resource does not exist, or you are not a member of the named org.
conflict409The request conflicts with existing state (e.g. name already taken).
quota_exceeded402A plan limit or the unverified-card wall was hit.
rate_limited429You exceeded the request rate limit.
internal500An unexpected server error. Retry; include traceId if it persists.

List endpoints return one of two paginated shapes, both nested inside data.

Projects, deployments, and the activity feed use opaque-cursor pagination:

{
"success": true,
"data": {
"items": [ /* … */ ],
"nextCursor": "eyJpZCI6MTIzfQ"
}
}

Pass ?cursor=<nextCursor> on the next request to fetch the following page. When nextCursor is null, you have reached the end. Most cursor lists also accept ?limit= (capped at 100).

Databases, functions, cronjobs, and their sub-lists use page-number pagination:

{
"success": true,
"data": {
"items": [ /* … */ ],
"total": 137
}
}

Pass ?page= (1-based) and ?pageSize= (default 20, max 100).

A global in-memory rate limit of 300 requests per minute applies, keyed by the last 16 characters of your bearer token (or by client IP when no token is present). /health is exempt.

When you exceed the limit you receive HTTP 429 with the standard error envelope:

{ "success": false, "error": { "code": "rate_limited", "message": "Too many requests" } }

The API is versioned in the URL path (/v1). New backward-compatible fields may be added to response bodies at any time, so clients should ignore unknown fields. Breaking changes ship under a new version prefix. Deprecated routes (for example POST /v1/databases/:name/upgrade) are kept for at least one release and documented as such.

Send Content-Type: application/json for JSON bodies. A few endpoints accept multipart/form-data instead — static ZIP uploads (POST /v1/projects/static-upload) and function code uploads (POST /v1/functions). These are called out on their pages.

Two endpoints receive signed callbacks from third parties and are not token-authenticated — they verify a cryptographic signature instead. They are documented on the pages of the features they drive:

Their responses are not enveloped.