Skip to content

Control plane API

The control plane is driven by the same HTTP API the cp-org-console uses. If you're automating tenant provisioning, scripting member invitations, or piping audit data into your own systems, this is the surface you'll be talking to. Everything the console does is reachable from this API; the console itself has no special endpoints.

Base URL: https://manage.<your-domain>/api/v1

Most operations are scoped to your organisation:

POST /api/v1/organizations/{org_id}/tenants
GET /api/v1/organizations/{org_id}/tenants/{tenant_id}
POST /api/v1/organizations/{org_id}/tenants/{tenant_id}/suspend
POST /api/v1/organizations/{org_id}/tenants/{tenant_id}/resume
POST /api/v1/organizations/{org_id}/tenants/{tenant_id}/decommission
GET /api/v1/organizations/{org_id}/members
POST /api/v1/organizations/{org_id}/invitations
GET /api/v1/organizations/{org_id}/events

Your org_id is visible from the URL when you're signed into the console (the slug is in the subdomain; the id is in API responses).

Every request needs a bearer token in the Authorization header:

Authorization: Bearer <your-token>

The token is issued when you sign in to the console; if you're scripting, you'll need to either:

  • Re-use a session token captured from a logged-in browser session (works for ad-hoc scripts, not for production automation).
  • Mint a service-account token through the API — coming when service accounts ship.

Until service accounts land, the common pattern for production automation is to run a small server that signs in with member credentials and re-mints tokens as needed.

Every successful response carries the same envelope:

{
"data": { ... },
"meta": { "request_id": "...", "timestamp": "..." }
}

For paginated responses, meta carries cursor + count:

{
"data": [ ... ],
"meta": {
"request_id": "...",
"timestamp": "...",
"cursor": "01HZX...",
"has_more": true
}
}

Pagination uses a cursor token, not page numbers. Pass ?cursor=<token> to fetch the next page.

Errors come back with HTTP 4xx or 5xx plus a JSON envelope:

{
"error": {
"code": "NOT_FOUND",
"message": "tenant not found: tnt_01...",
"field_errors": [ ... ]
},
"meta": { "request_id": "..." }
}

Common error codes:

CodeWhen
BAD_REQUESTThe request body or query params don't validate. Check field_errors for specifics.
UNAUTHENTICATEDMissing or invalid bearer token.
FORBIDDENRole doesn't allow the action. See Roles reference.
NOT_FOUNDThe resource (tenant, member, etc.) doesn't exist or has been removed.
CONFLICTThe request can't proceed in the current state (e.g. suspending an already-suspended tenant).
RATE_LIMITED429 — see Plans → Rate limits.
INTERNAL_ERRORServer-side issue. Surface the request_id to support.

Always include the request_id from the meta block when reporting an issue — it's the fastest way for support to find the trace.

Mutating operations support an Idempotency-Key header. If your client crashes between sending the request and receiving the response, retrying with the same key is safe — the platform either returns the original response or no-ops cleanly.

Idempotency-Key: <unique-uuid-or-similar>

The recommended scope of an idempotency key is one logical client-side action. Generate a fresh key per action; reuse the same key on retries of that action.

The complete list of endpoints, request bodies, response shapes, and status codes lives in the OpenAPI specification (machine-readable). Two paths to it:

  • Browse interactively — a Swagger-style explorer page is on the roadmap.
  • Download the spec — once the explorer ships, the YAML/JSON download link will live next to it.

Until then, the cp-org-console source itself is the de-facto reference — every action you can take in the console maps to one HTTP call you can reproduce.

The current version is v1. The v1 segment in the URL is part of the contract: breaking changes get a v2 namespace, not a silent change to v1.

Additive changes (new endpoints, new optional fields on responses) are made within v1 and don't break existing clients. Deprecations are announced in the changelog with at least 6 months of overlap before the deprecated endpoint is removed.