Admin API & CLI

Everything you can do in the admin console — create realms, register clients, configure federation — you can also do programmatically over the Admin API, authenticated with a bearer token. The API is hosted at https://api.self.semauth.cc (self is the reserved system-organization slug).

How access works

Admin API tokens are minted by your own organization's admin realm and carry an org_id claim. The API verifies the token against that realm's keys and scopes every query to your org — so a token for org A can never touch org B's data. Two ways to get a token:

Every token must carry the scope management:full; the audience is the Admin API URL.

The APIs catalog

The admin console's APIs page (/apis) lists the resource servers your clients can request tokens for — the platform Admin API (semauth-admin-api) plus any APIs your org owns. It's the source of truth for valid audience values when registering a client.

Creating a machine-to-machine client

In the admin console open API Access (/api-access) and click New Admin API client:

The confirmation screen shows the Client ID (sa_mgmt_…), the secret (once), the token endpoint, the Admin API base URL, and a ready-to-run curl example. Grant type, scope (management:full), and audience are fixed for you. From API Access you can also Rotate secret (client_secret clients) — the previous secret stops working immediately — or Revoke a client.

Getting a token and calling the API

# Mint a token from your admin realm's token endpoint (shown on the confirmation page):
TOKEN=$(curl -sS -X POST "$TOKEN_URL" \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=client_credentials \
  -d audience="$ADMIN_API_URL" \
  -d scope=management:full | jq -r .access_token)

# Use it against the Admin API:
curl -sS "$ADMIN_API_URL/v1/realms" -H "Authorization: Bearer $TOKEN"
sequenceDiagram participant App as Your backend participant Realm as Admin realm /token participant API as api.self.semauth.cc App->>Realm: client_credentials (id+secret, audience, scope) Realm->>App: JWT access token (org_id, management:full) App->>API: GET /v1/realms (Bearer token) API->>API: Verify signature, aud, iss, org_id, scope API->>App: Your org's realms (JSON)

The CLI client (human from a terminal)

Every admin realm also has a built-in public client semauth-cli for interactive use. It uses authorization-code + PKCE with loopback redirects (RFC 8252): a local tool opens your browser, you sign in with your normal realm methods (magic link / passkey / federation), approve the tool on a consent screen that names the exact local callback it will receive the code on — asked on every run, so a signed-in browser alone never hands a code to something listening on your machine — and the tool receives a user-scoped token — no secret on disk, and audit logs attribute actions to you rather than a service account. The callback must be http://localhost/callback or http://127.0.0.1/callback on any port. Scopes include openid email profile offline_access management:full; the refresh token rotates and keeps minting tokens for the Admin API audience.

Endpoints (v1)

All requests require Authorization: Bearer <token>. Every mutation is recorded as an event and shows up in your Activity feed, exactly like the equivalent console action.

MethodPathPurpose
POST/v1/realmsCreate a realm (body { "name": "…" }). Returns issuer_id, discovery URL, and defaults.
GET/v1/realmsList your org's realms.
GET/v1/realms/{issuer_id}Fetch one realm.
PATCH/v1/realms/{issuer_id}Update name, enabled, magic_link_enabled, passkey_enabled, min_aal (1–3), and custom_domain (with CNAME verification; null to clear).
POST/v1/realms/{issuer_id}/clientsCreate an OIDC client (application, label, auth_method, grant types, scopes, audiences, redirect URIs). Returns the secret once for client_secret clients.
POST/v1/realms/{issuer_id}/external-idpsConfigure a federation provider. You supply all endpoints (no server-side discovery).
POST/v1/realms/{issuer_id}/users/{user_id}/erasureErase a user. See below.
POST/v1/realms/{issuer_id}/erasuresErase an email address (body { "email": "…" }), whether or not it has an account.
POST/v1/realms/{issuer_id}/erasureErase a disabled realm.

Erasure

The three erasure endpoints answer data-subject deletion requests from your own tooling. They do exactly what the console's Erase actions do, and they are permanent: the person's account, sign-in methods and sessions are removed and their personal data in the audit history becomes unreadable. None takes a request body except erasure by address.

# Erase a user
curl -sS -X POST "$ADMIN_API_URL/v1/realms/$ISSUER_ID/users/$USER_ID/erasure" \
  -H "Authorization: Bearer $TOKEN"

{ "erased_at": "2026-10-24T09:12:03Z", "user_id": "4f1c…", "events": 37 }

A person's receipt carries erased_at, user_id (null when the address had no account) and events, the number of audit records that mentioned them. A realm's receipt carries erased_at and users, the number of accounts removed. Erasing a realm that is still enabled is refused — disable it first with PATCH /v1/realms/{issuer_id} — and so is erasing the admin realm your organization signs in to the console with.

Example: create a realm

curl -sS -X POST "$ADMIN_API_URL/v1/realms" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Production"}'

# 201 Created
{
  "issuer_id": "vctxj6g1egkv",
  "name": "Production",
  "enabled": true,
  "discovery_url": "https://vctxj6g1egkv.semauth.app/.well-known/openid-configuration",
  "magic_link_enabled": true,
  "passkey_enabled": true,
  "min_aal": 1
}

Error responses

The API uses RFC 6749-style JSON errors. Common cases: 401 with invalid_token (missing / invalid / wrong-issuer token), 403 with insufficient_scope (token lacks management:full), and 400 for validation failures (e.g. an unreachable min_aal or an unverifiable custom domain).