Skip to content

Organizations & Teams API

Everything in PandaStack is scoped to an organization. Users belong to one or more orgs with a role (owner, admin, member, viewer), and orgs contain teams and members. BIGINT ids serialize as strings on the wire. Billing internals are deliberately absent from these shapes.

All routes require authentication. Some are identity-only (no active org needed); those are marked. Role floors are noted per endpoint.

viewer < member < admin < owner

See Authentication — roles for what each role can do.


GET /v1/me

The current user and the orgs they can act in. Called on boot, before an active org is selected — so it needs identity only.

Auth: authenticated (identity-only). Response (Me):

FieldTypeNotes
idnumber
emailstring
usernamestring | null
firstName / lastNamestring | null
avatarUrlstring | null
mfaEnabledboolean
organizationsOrgMembership[]Each { organizationId, name, role, cardVerified }.

Secrets (password, MFA secret, Firebase id) are never included.

Terminal window
curl https://api.pandastack.io/v1/me \
-H "Authorization: Bearer $PANDASTACK_TOKEN"

GET /v1/whoami

Resolve the current credential’s org and role (works for psk_ tokens and JWTs; used for CLI login discovery).

Auth: any member. Response: { userId, orgId, orgName, role }.


GET /v1/orgs

Orgs the caller belongs to.

Auth: authenticated (user-scoped). Response: Organization[].

Organization: { id, name, websiteLink, description, country, role, cardVerified }.

POST /v1/orgs

Creates a new org; the caller becomes owner. Enforces the free-org quota.

Auth: authenticated (user-scoped). Body (CreateOrgInput): { name } — trimmed, 1–120 chars. Response: Organization.

Terminal window
curl -X POST https://api.pandastack.io/v1/orgs \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "Acme Inc" }'

GET /v1/orgs/:id

The active org must match :id — no cross-org reads.

Auth: any member. Response: Organization.

GET /v1/orgs/:id/teams

Auth: any member. Response: Team[] — each { id, name, organizationId }.


GET /v1/account

Full org detail (Settings → Account).

Auth: any member. Response (OrgDetail): { id, name, websiteLink, description, country, plan, memberCount, createdAt } where plan is free | pro | premium | enterprise.

PATCH /v1/account

Auth: admin. Body (UpdateOrgInput) — all optional:

FieldTypeNotes
namestring1–120.
websiteLinkstring | nullValid URL, max 300.
descriptionstring | nullMax 1000.
countrystring | nullMax 80.

Response: OrgDetail.

POST /v1/account/delete

Deletes the organization. Destructive and guarded by a type-to-confirm exact name.

Auth: owner. Body (DeleteOrgInput): { confirmName } — must exactly match the org name. Response: { success: true }.

Terminal window
curl -X POST https://api.pandastack.io/v1/account/delete \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "confirmName": "Acme Inc" }'

POST /v1/teams

Auth: admin. Body (CreateTeamInput): { name } — 1–120 chars. Response: Team.

PATCH /v1/teams/:id

Auth: admin. Body: { name } (same as create). Response: Team.

DELETE /v1/teams/:id

Auth: admin. Response: { deleted: true }.

GET /v1/teams/:id/members

Members of a team in the active org (the team is org-checked).

Auth: any member (viewer+). Response: Member[].

Member: { userId, teamId, role, username, email, avatarUrl }.


POST /v1/teams/members

Invites a member; sends a Mailgun invite email best-effort.

Auth: admin. Body (InviteMemberInput):

FieldTypeRequiredNotes
emailstringyesValid email.
teamIdnumberyesTeam to invite into.
roleowner | admin | member | viewernoDefault member.

Response: Invitation.

Terminal window
curl -X POST https://api.pandastack.io/v1/teams/members \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "email": "new@example.com", "teamId": 3, "role": "member" }'

DELETE /v1/teams/:id/members/:userId

Auth: admin. Response: { removed: true }.

GET /v1/invitations

Invitations for the active org.

Auth: any member. Response: Invitation[].

Invitation: { id, email, teamId, role, organizationId, status (invited|accepted|rejected), organizationName?, inviterName? }.

GET /v1/invitations/:id

A single invitation’s details, from the invitee’s side (they are not yet a member).

Auth: authenticated (invitee). Response: Invitation.

POST /v1/invitations/:id/respond

Accept or decline an invitation (email-matched to the caller).

Auth: authenticated (invitee). Body (RespondInvitationInput): { status: "accepted" | "rejected" }. Response: { status, organizationId }.

Terminal window
curl -X POST https://api.pandastack.io/v1/invitations/57/respond \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "status": "accepted" }'

Manage the psk_ tokens used for programmatic access. See Authentication for how tokens work.

GET /v1/api-tokens

Auth: admin. Response: ApiToken[] (masked).

ApiToken: { id, name, description, status (active|revoked), preview ('psk_...a1b2'), lastUsed, expiresAt, createdAt }.

POST /v1/api-tokens

The raw token is returned once.

Auth: admin. Body (CreateApiTokenInput):

FieldTypeRequiredNotes
namestringyes1–80 chars.
descriptionstringnoMax 300.
expiresInDaysnumberno1–3650.
permissionsrecordnoScoped permissions.

Response (CreatedApiToken): { token (full psk_… secret, shown once), apiToken: ApiToken }.

Terminal window
curl -X POST https://api.pandastack.io/v1/api-tokens \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "ci-deploy", "expiresInDays": 90 }'

DELETE /v1/api-tokens/:id

Auth: admin. Response: { success: true }.


The org’s in-app notification feed (bell). Written by the control plane and worker-v3 lifecycle events (e.g. db.expiring, db.suspended, db.purge_warning, db.purged). This route group is allowlisted past the card wall.

GET /v1/notifications

Auth: any member. Query (NotificationListQuery): limit (default 30, max 100), unreadOnly (default false).

Response (NotificationListPage): { items: Notification[], unread (total unread count for the bell badge) }.

Notification: { id, type, title, body, resourceType, resourceId, read, createdAt }.

POST /v1/notifications/:id/read

Auth: any member. Response: { success: true }.

POST /v1/notifications/read-all

Auth: any member. Response: { success: true }.


GET /v1/overview

The landing-dashboard aggregate: resource counts, 30-day deploy stats, and a 90-day daily deployment calendar (GitHub-style heatmap). Org-scoped, computed from Postgres.

Auth: any member. Response (Overview):

FieldTypeNotes
countsobject{ projects, containers, static, databases, functions, monitors }.
deploymentsobject{ last30d, succeeded30d, failed30d, successRate }.
calendarDeploymentDay[]Each { date ('YYYY-MM-DD'), total, succeeded, failed }.
Terminal window
curl https://api.pandastack.io/v1/overview \
-H "Authorization: Bearer $PANDASTACK_TOKEN"