Docs
API

Platform admin API

The staff routes that set prices and plans.

Cross-team routes for the platform operator. Every route answers 401 without a session and 403 unless the user is a platform admin. GET /api/v1/user/me returns is_platform_admin so the dashboard can show the control panel.

A user is a platform admin when users.is_platform_admin is set, or when their email is listed in the API's PLATFORM_ADMIN_EMAILS (comma-separated). A listed email is flagged on its first request and cannot be revoked through the API.

MethodPathDoes
GET/api/v1/admin/teamsEvery team, suspended included, with owner_email, member_count, project_count
GET/api/v1/admin/teams/{teamID}One team with members and projects
POST/api/v1/admin/teams/{teamID}/suspendMembers get 403 team is suspended on every team route. Running services, webhook builds and scheduled jobs carry on
POST/api/v1/admin/teams/{teamID}/unsuspendReinstates the team
GET/api/v1/admin/projects?health=Every live project with its latest build and deployment and a derived health (failed, in_progress, running, idle), failed first
DELETE/api/v1/admin/projects/{projectID}Deletes through the same path as the team route: soft delete, webhook cleanup, teardown job
GET/api/v1/admin/usersEvery user with team_count, is_platform_admin, is_bootstrap_admin
PUT/api/v1/admin/users/{userID}/platform-adminGrants platform admin
DELETE/api/v1/admin/users/{userID}/platform-adminRevokes it. 409 for the last admin or a PLATFORM_ADMIN_EMAILS admin
GET/api/v1/admin/audit?limit=&offset=Actions taken through these routes
GET/api/v1/admin/pricesEvery plan with its fee, limits, usage rates and active; rates, the usage rates every plan charges (null when they differ); each bundle the platform ships with its apps, price_cents (null until it has one), its lower price (null until staff set one), offered, and guide: what its apps cost a month on their own, on the cheapest plan a team may choose; each add-on with its id, name, kind, days, price_cents and offered; limits; and waf_available
GET/api/v1/admin/prices/changes?limit=&offset=Every price change, newest first: the plan, bundle, add-on or limits, the field, old_value, new_value, who made it and when
PUT/api/v1/admin/prices/plans/{planID}Sets a plan's price_cents, ram_gb, vcpu and max_projects
PUT/api/v1/admin/prices/ratesSets gb_hour_cents and vcpu_hour_cents on every plan
PUT/api/v1/admin/prices/bundles/{bundleID}Sets a bundle's price_cents, its lower price and offered. A bundle without a price is not offered. A lower price left out keeps the one the bundle has, and once set it is never cleared
PUT/api/v1/admin/prices/addons/{addonID}Sets an add-on's price_cents, offered, and for a backup period days, which must be more than every app includes. The firewall cannot be offered while waf_available is false
PUT/api/v1/admin/prices/limitsSets smallest_topup_cents, largest_topup_cents and included_backup_days, which is never below the 7 days every copy is kept

Every mutating admin route writes an audit row with metadata.platform_admin = true. Rows about a team carry its team_id, so they also appear in that team's own audit log; grants, revokes and price changes carry none.

A price change also lands in the price history, one row per value it changed, with who made it. It applies from each team's next charge, and an add-on's reaches new purchases only: a purchase keeps the price it was bought at. The PUTs answer with the prices as GET /api/v1/admin/prices does, and a value the plans or bundle prices cannot hold with 422 and a fields object.