Authentication & Platform APIs
This page covers the authentication, setup, recovery, OAuth, and SCIM APIs. These endpoints are not under the /api/v1/admin prefix and have their own authentication requirements.
Common Error Response
All endpoints return errors in this format:
{
"error": "descriptive error message"
}
| Status | Meaning |
|---|---|
| 400 | Bad request or validation error |
| 401 | Missing or invalid authentication |
| 403 | Insufficient permissions or setup already complete |
| 404 | Resource not found |
| 409 | Conflict (duplicate resource) |
| 429 | Rate limit exceeded (check Retry-After header) |
| 500 | Internal server error |
| 503 | Service unavailable |
Setup API
These endpoints are only available before initial setup is complete (i.e., when zero admin users exist). After setup, they return 403.
Prefix: /api/v1/setup
Get Setup Status
GET /api/v1/setup/status
Returns whether the instance needs initial setup.
Response:
{
"needsSetup": true,
"devMode": false
}
needsSetup is true when no admin users exist (setup required), false after the first admin has been created.
Complete Setup
POST /api/v1/setup/complete
Creates the first admin user and tenant from a license key, and marks setup as complete.
Body:
{
"email": "admin@example.com",
"password": "secure-password-here",
"displayName": "Admin User",
"licenseKey": "payload.signature",
"externalBaseUrl": "https://surfacesec.example.com"
}
| Field | Required | Description |
|---|---|---|
email | Yes | Admin user email |
password | Yes | Admin password (min 12 chars, must include upper, lower, digit, special) |
displayName | Yes | Admin display name (2-100 chars) |
licenseKey | Yes | Signed license key in payload.signature format (obtained from portal) |
externalBaseUrl | Yes | Publicly routable HTTPS URL for this deployment (must start with http:// or https://) |
Response (201 Created):
{
"tenantId": "uuid",
"clientName": "My Organization",
"tier": "enterprise",
"expiresAt": "2027-04-19T00:00:00Z",
"maxUsers": 100,
"accessToken": "eyJhbGciOiJI...",
"refreshToken": "eyJhbGciOiJI...",
"user": { "...": "AdminUser" }
}
Authentication API
Prefix: /api/v1/auth
Tenant Discovery
GET /api/v1/auth/tenant
Auto-detects and returns the tenant for single-tenant deployments. No authentication required.
Response:
{
"tenantId": "uuid",
"name": "Organization Name",
"slug": "org-slug"
}
Login
POST /api/v1/auth/login
Authenticates an admin user and returns JWT tokens.
Headers: X-Tenant-ID (required unless tenantId is in the body)
Body:
{
"email": "admin@example.com",
"password": "password123",
"tenantId": "uuid"
}
| Field | Required | Description |
|---|---|---|
email | Yes | Admin user email |
password | Yes | Password (min 6 characters) |
tenantId | No | Can alternatively be passed via X-Tenant-ID header |
Response (success):
{
"accessToken": "eyJhbGciOiJI...",
"refreshToken": "eyJhbGciOiJI...",
"expiresAt": "2026-03-12T00:00:00Z",
"user": {
"id": "uuid",
"email": "admin@example.com",
"displayName": "Admin User",
"roleType": "super_admin"
}
}
Response (MFA required):
If the user has MFA enabled, the login endpoint returns a challenge instead:
{
"mfaRequired": true,
"mfaToken": "temp-token",
"message": "MFA verification required"
}
Use mfaToken with POST /api/v1/auth/mfa/verify to complete login.
Response (password change required):
{
"mustChangePassword": true,
"tempToken": "temp-token",
"userId": "uuid",
"message": "Password change required"
}
Use the temp token with POST /api/v1/auth/change-password to set a new password.
Verify MFA
POST /api/v1/auth/mfa/verify
Completes MFA verification during login. Accepts either a TOTP code or a one-time backupCode.
Headers: X-Tenant-ID
Body:
{
"mfaToken": "mfa-token-from-login",
"code": "123456"
}
| Field | Required | Description |
|---|---|---|
mfaToken | Yes | Token returned from the login response (mfaToken field) |
code | Conditional | 6-digit TOTP code. Required unless backupCode is provided. |
backupCode | Conditional | One of the backup codes issued during MFA setup (consumed on use). |
Response: Same as successful login response (returns accessToken, refreshToken, user).
Refresh Token
POST /api/v1/auth/refresh
Exchanges a refresh token for a new access token.
Headers: X-Tenant-ID
Body:
{
"refreshToken": "eyJhbGciOiJI..."
}
Response: Same format as login response.
Logout
POST /api/v1/auth/logout
Invalidates the current session.
Headers: X-Tenant-ID
Change Password
POST /api/v1/auth/change-password
Changes user password using a temporary token (from forced password change flow).
Headers: X-Tenant-ID
Body:
{
"tempToken": "temp-token-from-login",
"newPassword": "new-secure-password",
"confirmPassword": "new-secure-password"
}
| Field | Required | Description |
|---|---|---|
tempToken | Yes | Temporary token from login response |
newPassword | Yes | New password (min 12 characters) |
confirmPassword | Yes | Must match newPassword |
SSO (OpenID Connect)
Prefix: /api/v1/auth/sso
Get SSO Status
GET /api/v1/auth/sso/status
Returns whether SSO is configured for the tenant.
SSO Login
GET /api/v1/auth/sso/login
Redirects the user to the configured Identity Provider for authentication.
SSO Callback
GET /api/v1/auth/sso/callback
Handles the OAuth 2.0 callback from the Identity Provider. Returns JWT tokens on success.
SSO Logout
POST /api/v1/auth/sso/logout
Authentication: Requires JWT Bearer token (admin auth).
Terminates the SSO session.
SAML 2.0
Prefix: /api/v1/auth/saml
SP Metadata
GET /api/v1/auth/saml/metadata
Returns SAML Service Provider metadata (XML) for configuring your Identity Provider.
SAML Login
GET /api/v1/auth/saml/login
Initiates a SP-initiated SAML SSO flow. Redirects to the configured IdP.
SAML Callback (ACS)
POST /api/v1/auth/saml/callback
Assertion Consumer Service endpoint. Receives and validates the SAML Response from the IdP. Returns JWT tokens on success.
Recovery
Admin Reset (Break-Glass)
POST /api/v1/recovery/reset
Emergency admin account reset. Protected by the RECOVERY_KEY environment variable. The endpoint is disabled (returns 404) when RECOVERY_KEY is unset. When invoked successfully, it resets the admin password to a one-time generated value, unlocks the account, disables MFA, reactivates any suspended status, and revokes all existing sessions.
Body:
{
"recoveryKey": "shared-recovery-secret",
"email": "admin@example.com"
}
| Field | Required | Description |
|---|---|---|
recoveryKey | Yes | Must match the server's RECOVERY_KEY env var (constant-time compare) |
email | Yes | Email of the admin user to recover |
Response:
{
"success": true,
"tempPassword": "generated-temporary-password",
"message": "Account recovered. User must change password on next login. MFA has been disabled."
}
The returned tempPassword is shown only once. The user must change it on next login.
The recovery key should be stored securely (e.g., in a secrets vault) and rotated after use. If the environment variable is compromised, an attacker can reset admin access.
OAuth 2.0 (SCIM Token Management)
These endpoints implement the OAuth 2.0 Client Credentials grant for SCIM provisioning. SCIM clients (Entra ID, Okta, etc.) use these endpoints to obtain Bearer tokens.
Prefix: /oauth
Token
POST /oauth/token
Issues an access token using client credentials.
Accepts both application/json and application/x-www-form-urlencoded.
Body (JSON):
{
"grantType": "client_credentials",
"clientId": "scim-client-id",
"clientSecret": "scim-client-secret",
"scope": "scim:read scim:write"
}
Body (form-encoded):
grant_type=client_credentials&client_id=scim-client-id&client_secret=scim-client-secret&scope=scim:read+scim:write
Response:
{
"accessToken": "generated-token",
"tokenType": "Bearer",
"expiresIn": 3600,
"scope": "scim:read scim:write"
}
Error Response:
{
"error": "invalid_client",
"errorDescription": "Client authentication failed"
}
Token Introspection
POST /oauth/introspect
Checks whether an access token is active.
Body:
{
"token": "access-token-to-check"
}
Response:
{
"active": true,
"scope": "scim:read scim:write",
"clientId": "scim-client-id",
"tokenType": "Bearer",
"exp": 1700000000
}
Token Revocation
POST /oauth/revoke
Revokes an access token.
Body:
{
"token": "access-token-to-revoke"
}
SCIM 2.0
Implements RFC 7643 (Core Schema) and RFC 7644 (Protocol) for automated user and group provisioning from identity providers.
Prefix: /scim/v2
Authentication: OAuth 2.0 Bearer token (obtained via POST /oauth/token)
Rate Limit: 100 requests per 60 seconds per client.
Service Provider Configuration
GET /scim/v2/ServiceProviderConfig
Returns the SCIM service provider configuration (supported features, authentication schemes, etc.).
Schemas
GET /scim/v2/Schemas
Returns the supported SCIM resource schemas.
Users
List Users
GET /scim/v2/Users
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
filter | string | SCIM filter expression (e.g., userName eq "jane@example.com", externalId eq "12345") |
startIndex | integer | 1-based pagination start (default: 1) |
count | integer | Page size, max 1000 (default: 100) |
Create User
POST /scim/v2/Users
Body: SCIM User resource (RFC 7643 format)
Response: 201 Created with the created SCIM User resource.
Get User
GET /scim/v2/Users/{id}
Update User
PUT /scim/v2/Users/{id}
PATCH /scim/v2/Users/{id}
Both PUT (full replace) and PATCH (partial update with SCIM patch operations) are supported.
Delete User
DELETE /scim/v2/Users/{id}
Response: 204 No Content
Groups
List Groups
GET /scim/v2/Groups
Query Parameters: Same as Users (filter, startIndex, count).
Create Group
POST /scim/v2/Groups
Response: 201 Created
Get Group
GET /scim/v2/Groups/{id}
Update Group
PUT /scim/v2/Groups/{id}
PATCH /scim/v2/Groups/{id}
Delete Group
DELETE /scim/v2/Groups/{id}
Response: 204 No Content