PerdurancePerdurance

Administration API

Namespaces, backends, routing rules, members and keys — everything the console drives.

The console is the usual way to do all of this, and the CLI wraps the same routes. This page is the API underneath both, for anyone automating a deployment.

There are two scopes. Tenancy routes live under /{tenancy}/v1 and concern the organisation. Namespace routes live under /{tenancy}/{namespace}/v1 and concern one isolated workspace inside it.

Roles and what they carry

RoleCan
userFind the tenancy's namespaces, and use the ones granted to them
adminEverything a user can, plus manage namespaces, backends, rules, members and keys
ownerEverything an admin can, plus delete a namespace, delete the tenancy, and transfer ownership

A role is held in the tenancy. A grant is narrower: it gives one member a role inside one namespace, which is how a user comes to be able to use prod but not staging.

Getting in

RouteTakesGives
POST /v1/registrationsA registration token, or an identity provider'sA new tenancy and its owner
POST /v1/sessionsEmail and passwordA session key
POST /v1/sessions/exchangeAn identity provider's tokenA session key

A registration token is a file in the deployment's secret mount, one per client. Registration is the only way a tenancy comes to exist, and the first account created owns it.

Where the deployment signs people in through an identity provider, that provider's token is accepted in place of a registration token, and the owner's address is taken from it rather than from the body — a token whose address the provider has not confirmed is refused. Such an owner has no password on this service, because they do not sign in with one: they exchange the provider's token for a session at POST /v1/sessions/exchange, and everything below that point is the same for them as for anybody else. A deployment that runs no provider has neither route behaviour and is unchanged.

Namespaces

RouteScope
GET /{tenancy}/v1/namespacesAny member
POST /{tenancy}/v1/namespacesAdmin
DELETE /{tenancy}/v1/namespaces/{namespace}Owner
{ "slug": "prod", "name": "Production" }

The slug is the URL segment. name is for people, and defaults to the slug.

Backends

Created in a namespace, then optionally attached to others.

RouteDoes
POST /{tenancy}/{namespace}/v1/backendsCreate one here
GET /{tenancy}/{namespace}/v1/backendsList the ones this namespace can route to
GET /{tenancy}/{namespace}/v1/backends/{backend}Show one
POST /{tenancy}/{namespace}/v1/backends/{backend}Attach an existing backend to this namespace
DELETE /{tenancy}/{namespace}/v1/backends/{backend}Detach it from this namespace
GET /{tenancy}/v1/backendsList every backend in the tenancy
GET /{tenancy}/v1/backends/{backend}/namespacesWhich namespaces use one
DELETE /{tenancy}/v1/backends/{backend}Delete it everywhere
{
  "name": "anthropic-prod",
  "kind": "anthropic",
  "base_url": null,
  "credential": "sk-ant-…"
}

kind is openai_compat, anthropic or openrouter — see Routing. base_url may be omitted where the kind implies it. credential is write-only: it is encrypted before storage and never appears in any response, so BackendView carries id, name, kind and base_url and nothing else.

Note the two deletes. Detaching removes a backend from one namespace; deleting removes it from the tenancy.

Routing rules

All namespace-scoped, all admin.

RouteDoes
POST /{tenancy}/{namespace}/v1/routing-rulesCreate
GET /{tenancy}/{namespace}/v1/routing-rulesList
DELETE /{tenancy}/{namespace}/v1/routing-rules/{rule}Delete
{
  "priority": 100,
  "pattern": "claude-*",
  "backend_id": "…",
  "model_rewrite": "claude-sonnet-4-20250514"
}

Lowest priority wins. Routing has the semantics.

Members and grants

RouteDoes
GET /{tenancy}/v1/usersList members
GET /{tenancy}/v1/users/{user}One member, with the grants they hold
POST /{tenancy}/v1/usersInvite one
POST /{tenancy}/v1/users/{user}/roleChange their tenancy role
POST /{tenancy}/v1/users/{user}/passwordSet their password
DELETE /{tenancy}/v1/users/{user}Revoke them
GET /{tenancy}/v1/namespaces/{namespace}/grantsWho may use this namespace
POST /{tenancy}/v1/namespaces/{namespace}/grantsGrant a member a role in it
DELETE /{tenancy}/v1/namespaces/{namespace}/grants/{user}Take it back

An invited member given a password they did not choose must replace it before they can do anything else — that is the 403 password_change_required in Errors.

Keys

RouteDoes
POST /{tenancy}/v1/me/keysMint one for yourself
GET /{tenancy}/v1/me/keysList your own
DELETE /{tenancy}/v1/me/keys/{key}End one of your own
POST /{tenancy}/v1/users/{user}/keysMint one for a member (admin)
GET /{tenancy}/v1/users/{user}/keysList theirs (admin)
DELETE /{tenancy}/v1/keys/{key}Revoke any key (admin)

Minting answers with the only copy of the secret:

{ "id": "…", "name": "batch-worker", "prefix": "ab12cd34", "key": "sar_ab12cd34_…" }

Listing never does — ApiKeyView carries id, name, prefix, created_at and revoked_at. The secret is stored only as a keyed hash, so a lost key is replaced rather than recovered. The eight-character prefix is the public half, and it is what usage figures and log lines name a key by.

Ownership

RouteDoes
POST /{tenancy}/v1/ownershipTransfer the tenancy to another member
DELETE /{tenancy}/v1/tenancyDelete the tenancy

Both are the owner's alone.

On this page