Managing applications
Every OAuth 2.1 client you integrate with Auth Service is represented as an application record in the database. Applications hold the OIDC client configuration — redirect URIs, allowed scopes, MFA policy, and registration settings.
All application endpoints require an active session with the admin or superadmin role. Requests without a valid session cookie receive 401 AUTH_001; a session without one of those roles receives 403 AUTH_011.
The same operations are available in the admin interface (Applications, route /applications):

Create or edit in the admin interface
Create Application (and Edit Application on an existing one) opens a four-step form. Authorization and Advanced are optional: when creating, you can save from the Authorization step on; when editing, you can save from any step.
| Step | What you set |
|---|---|
| General | Name, Slug (Client ID, locked when editing), Description, URL, Icon URL. When you type a URL, the form tries to detect the site's favicon for the icon and fills the first redirect URI with <URL>/callback while that field is empty |
| Options | Active, Public client (PKCE only, no secret) (locked when editing), Allow registration, Require MFA, Skip consent screen, Hosted billing pages, Allow RP-Initiated Logout, and LiteLLM support (disabled unless the litellm and auth-service system applications exist) |
| Authorization | Allowed scopes as toggles in two groups, OpenID Connect (openid, profile, email, phone, offline_access) and Circle claims (roles, permissions, features, org), each with a description of the claims it adds; Redirect URIs; Post-logout redirect URIs (shown only while Allow RP-Initiated Logout is on); Allowed resources |
| Advanced | Back-channel logout endpoint and, once a URI is entered, Require session id (sid); Social providers; Metadata (JWT client_attrs) |
A new form starts with Allow registration on and with the scopes openid, profile, email, roles, permissions and features selected. The API default for allowRegister is false (see below), so applications created through the API without this field do not offer registration.
When a confidential application is created, the client secret is displayed once. Rotate Secret on the application detail page issues a new one. System applications are hidden from the list unless Show system applications is switched on.
Create an application
POST /api/admin/applications
Content-Type: application/json
{
"name": "My App",
"slug": "my-app",
"description": "Optional description",
"redirectUris": ["https://app.example.com/callback"],
"allowedScopes": ["openid", "profile", "email", "roles", "permissions", "features"],
"isActive": true,
"skipConsent": false,
"isMfaRequired": false,
"allowRegister": false,
"url": "https://app.example.com",
"icon": "https://app.example.com/icon.png"
}
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Display name (1–100 chars) |
slug | string | required | URL-safe identifier used as client_id (lowercase alphanumeric + hyphens, 1–64 chars) |
description | string | — | Optional description (max 500 chars) |
redirectUris | string | [] | Allowed redirect URIs for the OAuth flow |
allowedScopes | string | ["openid","profile","email"] | Scopes this application may request |
isActive | boolean | true | Whether the application accepts new authorizations |
skipConsent | boolean | false | Skip the consent screen for trusted first-party apps |
isMfaRequired | boolean | false | Block token issuance for users without MFA enabled |
allowRegister | boolean | false | Whether the application's sign-in page offers registration, and whether a sign-up that names it (client_id) may create an account. See Registration |
isPublic | boolean | false | true for a public client (browser or native app, PKCE only, no secret); false for a confidential client with a clientSecret. Set at creation, immutable afterwards |
isSystem | boolean | false | Infrastructure application (hidden from the admin list by default, cannot be deleted). Setting it requires the superadmin role (403 AUTH_011 otherwise) |
billingMode | "none" | "delegated" | "none" | With delegated, the service serves the subscribe, invoices and billing-usage pages for the application. In the admin form it is the Hosted billing pages option of the Options step |
url | string | — | Application home URL. Also the application's resource identifier (RFC 8707 resource) |
allowedResources | string | [] | Protected resources (URLs of other applications) this application may request tokens for. Unknown URLs are rejected with APP_001 |
enableEndSession | boolean | false | Form label Allow RP-Initiated Logout. Lets the application call /api/auth/oauth2/end-session. The call ends the central SSO session for all applications (there is no per-application variant), and applications with a back-channel logout endpoint are notified. Leave it off for applications that only sign the user out locally |
postLogoutRedirectUris | string | [] (seeded with <url>/ when url is set at creation and the list is empty) | URLs accepted as post_logout_redirect_uri by end-session. Matching is strict, query string included. Each entry must be a valid URL. An empty list means no post-logout redirect is allowed |
backchannelLogoutUri | string | null | null | OIDC Back-Channel Logout 1.0 endpoint. Must be a public https:// URL with no credentials or fragment, whose host does not resolve to a loopback or private address (any other value is rejected with APP_001). When set, a session end POSTs a signed logout token here. See Back-channel logout |
backchannelLogoutSessionRequired | boolean | false | Form label Require session id (sid), shown only once a back-channel URI is entered. It records that the application needs the sid claim in every logout token and ends only the session it names (spec §2.8). The service includes sid in logout tokens regardless; this flag does not change that |
enabledSocialProviders | string | null | null | null inherits the providers enabled on the server, [] disables social sign-in for this application, a list restricts it to those providers |
metadata | object | {} | String key/value pairs (keys must match ^[a-zA-Z_][a-zA-Z0-9_]*$, values up to 256 characters). They are added to the application's tokens inside a client_attrs object. Keys that collide with reserved token claims (sub, aud, iss, exp, iat, nbf, jti, scope, scopes, azp, client_id, token_type, auth_time, acr, amr) are rejected |
icon | string | — | Icon URL (displayed in admin and consent screen) |
The 201 response contains the created application, the clientId (same as slug) and, for confidential applications, the clientSecret. The secret is shown only once — store it immediately. Public clients have no secret.
Creating an application also sets up:
- two roles,
user(the default role) andadmin; - a default subscription plan named
free; - access for every existing superadmin and for the creating admin, each with the
adminrole and thefreeplan.
Invalid data returns 400 APP_001; a slug that already exists returns 409 APP_003.
Logout settings
enableEndSession, postLogoutRedirectUris, backchannelLogoutUri and backchannelLogoutSessionRequired are stored on the OAuth client and returned in every application payload.
- ID tokens carry the session id (
sid) only whenenableEndSessionis on or abackchannelLogoutUriis set. - End-session ends the central session for all applications. No per-application end-session exists; revoking one user's access to one application (below) revokes that user's refresh tokens for that application only and does not end the shared session or send a back-channel logout.
- Post-logout redirects are only accepted when the URL is listed in
postLogoutRedirectUris.
List applications
GET /api/admin/applications
Returns { "applications": [...] }, ordered by creation date. Each entry is the application row plus its logout settings (enableEndSession, postLogoutRedirectUris, backchannelLogoutUri, backchannelLogoutSessionRequired).
Get a single application
GET /api/admin/applications/:id
Returns { "application": { ... } }, or 404 APP_002 when the id is unknown.
Update an application
PATCH /api/admin/applications/:id
Content-Type: application/json
{
"isMfaRequired": true,
"skipConsent": true
}
All fields from the create schema are accepted, except slug and isPublic, which are immutable after creation. Every field is optional and omitted fields keep their stored value. The response is { "application": { ... } }.
Rotate the client secret
POST /api/admin/applications/:id/rotate-secret
Generates a new random clientSecret, hashes it with SHA-256, and stores the hash. The plaintext secret is returned once, as { "clientSecret": "..." }. All tokens issued to the application with the previous secret are revoked. Public clients have no secret: the call returns 400 APP_001.
Delete an application
DELETE /api/admin/applications/:id
Returns 204 on success. The application's roles, plans, user access records and OAuth client are deleted with it, and its Stripe catalogue objects are archived rather than deleted. System applications (isSystem) cannot be deleted and return 403 APP_007. Unknown ids return 404 APP_002.
Grant a user access to an application
POST /api/admin/applications/:id/users
Content-Type: application/json
{
"userId": "<user id>",
"roleId": "<uuid>", // optional — assigns this role; otherwise the default role is assigned
"subscriptionPlanId": "<uuid>", // optional — otherwise the default plan is assigned
"reason": "Onboarding" // optional audit reason, 1–500 chars
}
Returns 201 { "ok": true }. If no roleId is given, the application's default role is assigned. If no subscriptionPlanId is given, the default subscription plan is assigned. A role that belongs to another application is rejected with APP_001; a plan that does not belong to this application is rejected with SUB_001.
Grant access in bulk
POST /api/admin/applications/:id/users/bulk
Content-Type: application/json
{
"organizationId": "<organization id>"
}
Send exactly one of organizationId (every member of the organization) or userIds (1–1000 user ids). roleId, subscriptionPlanId and reason apply to every granted user. Users who already have access are skipped. The response is { "granted": <n>, "skipped": <n> }.
Update a user's access record
PATCH /api/admin/applications/:id/users/:userId
Content-Type: application/json
{
"isActive": false,
"roleId": "<uuid>",
"subscriptionPlanId": "<uuid>",
"reason": "Upgrade agreed with the customer"
}
roleId replaces the user's current role (null removes it). subscriptionPlanId (null revokes the subscription) and expiresAt (ISO 8601 date-time or null) are audited manual plan changes: a payload containing either one must also carry a reason, otherwise the request fails with 400 APP_001. Setting isActive to false also revokes the user's OAuth refresh tokens for this application. The response is { "ok": true }.
Revoke a user's access
DELETE /api/admin/applications/:id/users/:userId
Revokes the user's OAuth tokens for the application, then deletes the user's roles, subscription, consumption records and access record for it. Returns 204.