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.

Standing and access

Standing is held in the tenancy. It is user, admin or owner, and it decides what a member may do to the organisation.

StandingCan
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 the tenancy and transfer ownership

Access is narrower, and it is what a grant carries: one member, one namespace, and either use or manage. It is how a user comes to be able to use prod but not staging. The two vocabularies never mix. user, admin and owner are standings; use and manage are accesses; neither is ever written where the other belongs.

AccessCan
useSubmit to the namespace and retrieve from it
manageThat, plus routing rules, backend attachments, usage and the request log

An admin or the owner manages every namespace in the tenancy and needs no grant for any of them.

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}Admin
{ "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, base_url and credential_source and nothing else.

The address a backend may name

base_url may name anything by default, including an address inside the deployment's own network. That is deliberate: a dedicated deployment usually reaches its model provider there.

A deployment configured with CTX_BACKEND_HOSTS=public narrows it. A backend may then only name an address the internet routes to. The rule is every range the IANA special-purpose address registries mark as not globally reachable, plus the machine's own name. localhost, any name under .localhost, an address in a loopback, this-network, private, shared or link-local range, and a name that resolves to one of those, are each refused with 422 and code backend_host_refused, and nothing is created. The address is checked again when a call is made, so a name that starts pointing inside the network later is refused there too. A deployment serving tenants it does not trust sets it.

A backend that holds no credential

credential_source says where a call through this backend gets its provider credential. It defaults to stored, which is the shape above. The other value is caller, a backend that holds no key of its own and forwards the one each submission brought:

{
  "name": "anthropic-passthrough",
  "kind": "anthropic",
  "credential_source": "caller"
}

A caller backend takes no credential, and sending both is refused with 422, because a backend that holds a credential and ignores it is one nobody can reason about.

A caller backend needs the deployment to allow it

Creating one, and submitting to one, are both refused with 422 and code passthrough_disabled unless the process was started with --credential-passthrough. It is a process argument rather than a setting, because it decides whose terms the traffic runs under, and the hosted service does not pass it. Claude Code and the Agent SDK has what it is for and Anthropic's position on it.

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

Routing rules

All namespace-scoped, and all needing manage on the namespace. An admin or the owner holds that everywhere; a user holds it where granted. The same layer covers /requests, /usage, /storage and /execution.

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 standing in the tenancy
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}/grantsGive a member use or manage on 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