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) 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), and the tool receives a user-scoped token — no secret on disk, and audit logs attribute actions to you rather than a service account. 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).

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).