HTTP API Reference
Source-aligned OAuth, hosted-auth, headless-auth, direct JSON, SSO setup, and operator HTTP surfaces installed by SqlOS.
SqlOS exposes several HTTP surfaces from the same package, but they are not interchangeable.
| Surface | Intended caller | Contract |
|---|---|---|
| OAuth protocol | OAuth clients, CLIs, and resource servers | Discoverable, standards-shaped endpoints such as /authorize, /token, device authorization, and JWKS |
| Hosted AuthPage | A user's browser after your app redirects to /authorize | HTML pages and form handlers owned by SqlOS; do not build a client around individual form routes |
| Headless AuthPage API | A host-owned custom sign-in UI | JSON state machine using requestId plus the SqlOS issuer session cookie |
| Direct JSON auth helpers | Deliberate host-owned integrations | Strongly typed SqlOS request/result records, but no single shared error envelope or blanket route authorization policy; review each route before exposing it |
| Dashboard/operator APIs | The bundled dashboards and trusted backend administration | UI implementation endpoints, not a stable browser management SDK |
With the normal builder.AddSqlOS<TContext>(...) setup, DashboardBasePath is the effective root. AddSqlOS derives the auth base as {DashboardBasePath}/auth after running the configuration callback. Setting AuthServer.BasePath independently inside that callback is therefore not an independent route-prefix override.
| Surface | Default base path | Configuration |
|---|---|---|
| Dashboard shell | /sqlos | SqlOSOptions.DashboardBasePath |
| OAuth, hosted auth, and direct JSON auth | /sqlos/auth | {DashboardBasePath}/auth |
| Headless AuthPage API | /sqlos/auth/headless | AuthServer.Headless.HeadlessApiBasePath |
| Auth dashboard | /sqlos/admin/auth | Derived from DashboardBasePath |
| FGA dashboard | /sqlos/admin/fga | SqlOSOptions.DashboardBasePath |
| Audit dashboard | /sqlos/admin/audit | SqlOSOptions.DashboardBasePath |
| Email dashboard | /sqlos/admin/email | SqlOSOptions.DashboardBasePath |
| Calendar dashboard | /sqlos/admin/calendar | Mapped only when calendar is enabled |
| Customer SSO setup API | /sqlos/admin/auth/sso-portal/api/setup | AuthServer.SsoPortal.HeadlessApiBasePath |
AuthServer.Issuer must be an absolute URI whose path matches the derived auth base. When PublicOrigin is configured, the issuer must be {PublicOrigin}{BasePath}.
The unified dashboard shell and the FGA component API/assets are middleware installed by AddSqlOS. FGA page routes are served by the unified shell; the FGA middleware does not expose a second dashboard document. The SqlOS startup filter maps AuthServer, audit, email, and—when enabled—calendar endpoints into the application's route table. This distinction matters if you compose the ASP.NET Core pipeline manually.
| Method | Route | Request | Response |
|---|---|---|---|
GET | /sqlos/auth/.well-known/oauth-authorization-server | None | OAuth authorization-server metadata JSON |
GET | /sqlos/auth/.well-known/openid-configuration | None | OIDC discovery document; the same JSON as the RFC 8414 document. Served while OpenID Provider mode is enabled (the default) and PublishDiscoveryDocument is on; otherwise 404 |
GET | /sqlos/auth/.well-known/jwks.json | None | JWKS containing active and grace-window validation keys |
Discovery is the authoritative source for endpoint URLs and advertised capabilities. Do not synthesize endpoint URLs in portable clients when discovery is available. Both documents always advertise response_modes_supported as ["query"] — SqlOS returns every authorization response in the redirect query string, never the fragment — and always advertise request_parameter_supported and request_uri_parameter_supported as false, because SqlOS does not accept request objects and the discovery default for request_uri_parameter_supported would otherwise claim it does. While OpenID Provider mode is enabled, both discovery documents also carry userinfo_endpoint, subject_types_supported (["public"]), id_token_signing_alg_values_supported (["RS256"]), and claims_supported.
scopes_supported is the sorted union of the scopes that at least one registered client can be granted, merged — while OpenID Provider mode is enabled (the default) — with the reserved OpenID Connect names openid, profile, and email. With the provider enabled, those three names are therefore always advertised, even in a deployment with no clients; only with the provider disabled does a zero-client deployment advertise an empty list. offline_access remains unadvertised because SqlOS does not gate refresh-token issuance on it — refresh tokens are always issued for code-flow clients — though it stays allowlistable for gateway compatibility. The list never includes internal auth:* credential-type strings. Requested scopes outside a client's allowlist are accepted by silent intersection. Operator-defined API scopes such as x.read appear when a client allowlist includes them.
| Method | Route | Content type | Purpose |
|---|---|---|---|
GET | /sqlos/auth/authorize | Query parameters | Start OAuth authorization code + PKCE |
POST | /sqlos/auth/token | application/x-www-form-urlencoded | Exchange an authorization code, refresh token, or device code |
| Parameter | Required | Behavior |
|---|---|---|
response_type | Yes | Must be code. |
client_id | Yes | A stored client ID or, when CIMD is enabled, an allowed HTTPS metadata-document URL. |
redirect_uri | Yes for a usable browser flow | Must match one of the resolved client's redirect URIs. |
state | Recommended | Optional (OIDC Core alignment; PKCE binds the exchange). When present, length must be 1–2048 characters and it is returned unchanged to the client; when absent, redirects omit it. |
scope | No | Space-separated. SqlOS always intersects the request with the client's registered allow-list and keeps only the overlap (RFC 6749 §3.3). An empty allow-list grants nothing. Unknown requested scopes are dropped, not rejected. openid is never always-allowed; when the granted set includes it and OpenID Provider mode is enabled, the token response carries an id_token. Hosted and headless warn when an allowlisted openid is omitted from the grant. |
code_challenge | For clients that require PKCE | Verified during token exchange. |
code_challenge_method | With PKCE | Only S256 is supported. |
resource | When using resource indicators | Bound to the authorization code and refresh-token family; it cannot be introduced or changed later. |
login_hint | No | Prefills or routes the hosted/headless login flow. |
prompt | No | login skips first-party silent reuse of the issuer session cookie and shows the hosted/headless form. Send this on an explicit Sign in or Sign up. select_account behaves like login (SqlOS has no account chooser yet). consent forces the consent screen for a non-first-party client even when a covering grant exists. none returns login_required when no reusable session exists or the session is older than max_age, and consent_required when a non-first-party client lacks a covering consent grant. |
nonce | No | Stored with the authorization request and copied onto the authorization code at issuance; the id_token minted at code exchange echoes the same nonce claim. |
max_age | No | Non-negative integer seconds. When the issuer session's original authentication is older than this, SqlOS re-challenges instead of silently reusing the session; max_age=0 always forces a fresh sign-in. Combined with prompt=none, a stale session returns login_required. |
SqlOS-specific presentation parameters include view (invite, login, signup, password, forgot-password, email-otp, phone-otp, or phone-otp-signup), ui_context for a headless UI, and invitation_token/invitationToken for an invitation-bound flow. Portable OAuth clients should rely only on discovery and standard parameters.
On success, SqlOS redirects to the exact registered redirect_uri with code, state when the request sent one, and the granted scope when that grant is non-empty. The scope query parameter is an intentional SqlOS extension: RFC 6749 §4.1.2 permits extra parameters, and clients can read the actual grant before exchanging the code. Errors that occur after a valid authorization request can redirect to the client; malformed requests handled before that point render the hosted error page or redirect to the configured headless UI. Do not assume every /authorize failure has a JSON body.
grant_type | Required fields | Notes |
|---|---|---|
authorization_code | code, client_id; code_verifier when PKCE-bound | redirect_uri, when supplied, must match. resource must match the original request. |
refresh_token | refresh_token | The refresh token identifies the client through its session. Public clients (token_endpoint_auth_method=none) do not authenticate at this gate; client_id is optional and, when sent, must match the token's client. Confidential clients must authenticate with their registered method (client_secret_basic or client_secret_post); this is the only route that can refresh a confidential client's token. resource is optional but cannot change the original resource binding. |
urn:ietf:params:oauth:grant-type:device_code | client_id, device_code | resource must match the device request. |
Authorization-code exchange:
curl -X POST https://app.example.com/sqlos/auth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_id=acme-web' \
--data-urlencode 'code=...' \
--data-urlencode 'redirect_uri=https://app.example.com/auth/callback' \
--data-urlencode 'code_verifier=...'Public refresh — client_id is optional because the refresh token already names the client:
curl -X POST https://app.example.com/sqlos/auth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=...'Confidential refresh still authenticates the client with its registered method. For client_secret_basic:
curl -X POST https://app.example.com/sqlos/auth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-u 'billing-server:$CLIENT_SECRET' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=...'Successful token responses use OAuth-style snake-case JSON fields such as access_token, refresh_token, token_type, expires_in, and scope. scope is the granted set after the same silent intersection used by /authorize, device authorization, and client_credentials. An empty grant is returned as an empty string rather than invalid_scope. Refresh-token responses echo the scope originally granted to the session; for sessions created before SqlOS recorded granted scope, the scope field is omitted rather than misreported as empty. OAuth errors use an error code and may include error_description.
The response carries an id_token field only when OpenID Provider mode is enabled and the granted scope includes openid. That applies to the authorization-code, refresh (refresh-minted ID tokens carry no nonce), and device grants; client_credentials responses never include one. When no ID token is minted, the field is absent — never null.
| Method | Route | Purpose |
|---|---|---|
POST | /sqlos/auth/device_authorization | Start a device authorization request from a client allowed to use device flow |
GET | /sqlos/auth/device | Open the user verification page |
GET | /sqlos/auth/device/approve | Open a resolved approval page |
POST | /sqlos/auth/device/verify | Resolve a user code in the hosted UI |
POST | /sqlos/auth/device/approve | Approve the resolved request |
POST | /sqlos/auth/device/deny | Deny the resolved request |
POST | /sqlos/auth/token | Poll with grant_type=urn:ietf:params:oauth:grant-type:device_code |
Device authorization start uses form data:
curl -X POST https://app.example.com/sqlos/auth/device_authorization \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=acme-cli' \
--data-urlencode 'scope=openid offline_access' \
--data-urlencode 'resource=https://app.example.com/api'Device authorization is enabled by default, but the client must explicitly allow the device grant. Seed it with SeedDeviceFlowClient/SeedCliClient or configure AllowDeviceAuthorization plus the device-code grant on the client.
A successful start response is:
{
"device_code": "opaque-device-secret",
"user_code": "ABCD-EFGH",
"verification_uri": "https://app.example.com/sqlos/auth/device",
"verification_uri_complete": "https://app.example.com/sqlos/auth/device?user_code=ABCD-EFGH",
"expires_in": 900,
"interval": 5
}Honor interval while polling /token. Device errors use the OAuth-style error and error_description fields and can include an updated interval.
| Error | Meaning |
|---|---|
authorization_pending | The user has not approved the request yet. |
slow_down | Polling is too frequent or the start rate limit was reached. |
access_denied | The user denied the request. |
expired_token | The device code expired. |
unauthorized_client / invalid_client | The client is not eligible for public device authorization. |
invalid_target | The requested resource is not allowed for this client. Unknown scopes are dropped by silent intersection rather than rejected. |
invalid_grant | The device code is invalid, mismatched, or already consumed. |
Mapped while OpenID Provider mode and its EnableUserInfoEndpoint option are enabled (both default to on).
| Method | Route | Purpose |
|---|---|---|
GET | /sqlos/auth/userinfo | Return OIDC claims for the presented access token |
POST | /sqlos/auth/userinfo | Same, accepting the token as a form field |
Authenticate with a SqlOS access token: an Authorization: Bearer header on either method, or an access_token form field on POST. The session's granted scope must include openid.
curl https://app.example.com/sqlos/auth/userinfo \
-H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...'A success response is a JSON claims document. sub is always present; the remaining claims follow the scope granted at authorization time:
| Claim | Released when |
|---|---|
sub | Always |
name, preferred_username, updated_at | Granted scope includes profile |
email, email_verified | Granted scope includes email |
amr | Always (authentication methods for the session) |
org_id | The session is organization-scoped |
Validation is session-aware, not just JWT validation: session revocation, idle or absolute expiry, and user or organization deactivation stop claim release immediately. Failures use RFC 6750 Bearer challenges:
401 with WWW-Authenticate: Bearer error="invalid_token", error_description="The access token is invalid, expired, or its session is no longer active." — bad, expired, or session-dead token;403 with WWW-Authenticate: Bearer error="insufficient_scope", error_description="The access token was not granted the openid scope." — a live token whose grant omits openid;401 with WWW-Authenticate: Bearer when no token was presented at all.These browser routes render or process the SqlOS-hosted sign-in experience. Applications normally begin at /authorize; they do not call each form handler directly. The form posts use application/x-www-form-urlencoded and can change with the bundled UI.
Every hosted POST requires the short-lived antiforgery field and matching browser cookie issued by a SqlOS GET page. The bundled UI supplies both automatically. Missing, expired, cross-browser, or cross-origin submissions return 400 before credentials or authorization state are processed. This requirement does not apply to the headless or direct JSON APIs.
| Method | Route family | Purpose |
|---|---|---|
GET | /sqlos/auth/login | Hosted sign-in page |
POST | /sqlos/auth/login/identify | Home-realm discovery |
POST | /sqlos/auth/login/password | Password sign-in form |
GET plus child POST routes | /sqlos/auth/login/email-otp | Email-code sign-in UI, start, and verify |
GET plus child POST routes | /sqlos/auth/login/phone-otp | Phone-code sign-in UI, start, and verify |
POST | /sqlos/auth/login/select-organization | Complete multi-organization selection |
GET | /sqlos/auth/login/oidc/{connectionId} | Start a configured social/OIDC provider from the hosted UI |
GET plus child POST routes | /sqlos/auth/signup | Password, email-code, phone-code, or invitation signup |
GET | /sqlos/auth/signup/phone-otp | Phone-code signup page |
POST | /sqlos/auth/mfa/verify | Verify a hosted MFA challenge |
POST | /sqlos/auth/mfa/totp/enroll/verify | Complete required TOTP enrollment |
POST | /sqlos/auth/consent/approve | Approve the consent screen for a non-first-party client |
POST | /sqlos/auth/consent/deny | Deny consent; redirects to the client with access_denied |
GET / POST | /sqlos/auth/password/forgot and /password/forgot/submit | Hosted password-reset request |
GET / POST | /sqlos/auth/password/reset and /password/reset/submit | Hosted password-reset form |
GET | /sqlos/auth/invitations/accept?token=... | Open an email invitation |
GET / POST | /sqlos/auth/device and child routes | Resolve, approve, or deny a device request |
GET | /sqlos/auth/logout | End the reusable issuer session cookie and redirect safely. This is the browser Sign out. Clearing app tokens alone is not enough. |
GET | /sqlos/auth/logged-out | Hosted post-logout page |
Integrate through /authorize, /token, and the documented AuthPage configuration unless you are intentionally building a headless UI. POST /sqlos/auth/signup and POST /sqlos/auth/logout are JSON helpers described next; they are not hosted form handlers.
These routes expose the same strongly typed contracts used by SqlOSAuthService. They are for a first-party host integration: they are not a second OAuth protocol and they do not share one normalized error envelope. Some handlers return a plain 400 message while others allow service exceptions to flow through the host's exception pipeline.
The sign-in routes (signup, password, email code, magic link, organization selection, and the MFA challenge completions) return tokens in the response body, so there is no page where a consent screen could appear. They accept only first-party clients. Any other client, including DCR and CIMD clients, gets 400 with { "error": "invalid_client", "message": "..." } before SqlOS checks a credential, sends an email, or mints a session, and SqlOS records an oauth.direct_login.rejected audit event. Third-party clients use /authorize.
| Method and route | JSON request | Success response |
|---|---|---|
POST /sqlos/auth/signup | SqlOSSignupRequest | 200 SqlOSLoginResult |
POST /sqlos/auth/password/login | SqlOSPasswordLoginRequest | 200 SqlOSLoginResult |
POST /sqlos/auth/email-otp/start | SqlOSEmailOtpStartRequest | 200 SqlOSEmailOtpStartResult |
POST /sqlos/auth/email-otp/verify | SqlOSEmailOtpVerifyRequest | 200 SqlOSLoginResult |
POST /sqlos/auth/magic-link/start | SqlOSMagicLinkStartRequest | 200 SqlOSMagicLinkStartResult |
POST /sqlos/auth/magic-link/complete | SqlOSMagicLinkCompleteRequest | 200 SqlOSLoginResult |
POST /sqlos/auth/select-organization | SqlOSSelectOrganizationRequest | 200 SqlOSLoginResult, preserving possible MFA state |
POST /sqlos/auth/mfa/challenge/verify | SqlOSMfaChallengeVerifyRequest | 200 SqlOSMfaChallengeVerifyResult |
POST /sqlos/auth/mfa/challenge/totp/enroll/start | SqlOSTotpChallengeEnrollmentStartRequest | 200 SqlOSTotpEnrollmentStartResult |
POST /sqlos/auth/mfa/challenge/totp/enroll/verify | SqlOSTotpEnrollmentVerifyRequest | 200 SqlOSTotpEnrollmentVerifyResult |
POST /sqlos/auth/token/exchange | SqlOSExchangeCodeRequest | 200 SqlOSTokenResponse |
POST /sqlos/auth/token/refresh | SqlOSRefreshRequest | 200 SqlOSTokenResponse for a public client's refresh token; 401 invalid_client for a confidential client's (see below) |
POST /sqlos/auth/logout | { refreshToken? } | 204 No Content; unknown or missing tokens are idempotent no-ops |
POST /sqlos/auth/logout-all | { refreshToken } | 204 No Content; 401 when the refresh token is missing, inactive, expired, or already consumed |
POST /sqlos/auth/account/grants | { refreshToken } | 200 with data: the user's active consent grants. The refresh token must belong to a first-party client's session; 401 when it resolves no active session or the session's client is not first-party |
POST /sqlos/auth/account/grants/revoke | { refreshToken, grantId } | 200 with the revoked grant's id and revokedAt; 401 without an active first-party session (third-party refresh tokens get the same generic 401); 400 for an unknown or already-revoked grant |
POST /sqlos/auth/password/forgot | SqlOSForgotPasswordRequest | 200 SqlOSPasswordResetRequestResult |
POST /sqlos/auth/password/reset-email | SqlOSSendPasswordResetEmailRequest | 200 SqlOSPasswordResetRequestResult |
POST /sqlos/auth/password/reset | SqlOSResetPasswordRequest | 204 No Content |
POST /sqlos/auth/email/verification-email | SqlOSCreateVerificationTokenRequest | 200 SqlOSEmailVerificationRequestResult; the same generic response is returned for known and unknown emails |
POST /sqlos/auth/email/verification-token | SqlOSCreateVerificationTokenRequest | Compatibility alias for /email/verification-email; it sends email and never returns a token |
GET /sqlos/auth/email/verify?token=... | Query-string token from the verification email | One-time browser verification page |
POST /sqlos/auth/email/verify | SqlOSVerifyEmailRequest | 204 No Content |
POST /token/refresh is a public-client compatibility route. It has no client-authentication channel, so it ignores any Authorization header or secret field. When the refresh token belongs to a confidential client, it returns 401 with { "error": "invalid_client", "error_description": "Client authentication failed." } before rotation. The token is not consumed, no cached grace-window response is released, and SqlOS records an oauth.client_authentication.failed audit event. Confidential clients refresh through POST /sqlos/auth/token with grant_type=refresh_token, the canonical refresh flow for every client. In-process SqlOSAuthService.RefreshAsync enforces the same rule and throws SqlOSClientAuthenticationException.
POST /token/exchange consumes the temporary code created by the direct SAML request-token flow. An OAuth authorization-code client must use the form-encoded POST /token endpoint, which enforces the OAuth redirect URI, PKCE verifier, and optional resource binding.
Account-management routes are secure by default without adding a blanket ASP.NET Core authorization policy to /sqlos/auth. Logout accepts only refresh-token proof of session ownership; the HTTP route ignores a caller-supplied sessionId. Logout-all derives the user from an active, unconsumed refresh token and never accepts a userId. Email-verification requests send a one-time link through the configured SqlOS email pipeline, use the trusted PublicOrigin/issuer rather than request host headers, suppress rapid resends, and return the same public shape for known, unknown, already verified, and delivery-failure cases.
Trusted backend code can still call SqlOSAuthService.LogoutAsync(..., sessionId: ...), LogoutAllAsync(userId), or CreateEmailVerificationTokenAsync(...) after applying its own ownership/admin policy. Do not expose those service methods by mapping caller-supplied IDs or raw tokens directly into a public route.
The dashboard admin API has two authenticated platform-operator routes. Unauthorized requests receive the same not-found response used by the rest of the admin control plane.
| Method and route | JSON request | Success response |
|---|---|---|
POST /sqlos/admin/auth/api/sessions/revocation/preview | SqlOSAdminSessionRevocationRequest | Match and active refresh-token counts; no mutation |
POST /sqlos/admin/auth/api/sessions/revocation | Same request with confirm: true | Newly/already-revoked session counts and revoked refresh-token count |
Supply at least one of sessionId, userId, organizationId, or clientApplicationId. Multiple values are combined with AND semantics. For a broad operation, copy the preview's matchedSessions into expectedMatchedSessions; execution rejects a changed count so the operator must preview and confirm the current scope again. reason and operationId are bounded strings, operation IDs cannot be reused for a different selector/reason, and operations matching more than 10,000 sessions are rejected so callers must narrow the incident scope. The API returns a generic not-found result when execution matches no records and does not expose cross-tenant record details.
The same authenticated dashboard admin API manages consent grants and scope display names.
| Method and route | JSON request | Success response |
|---|---|---|
GET /sqlos/admin/auth/api/scope-display-names | — | data: catalog entries with id, scope, displayName, description, and the standard configuration ownership projection |
POST /sqlos/admin/auth/api/scope-display-names | { scope, displayName, description? } | The created dashboard-owned entry |
PUT /sqlos/admin/auth/api/scope-display-names/{id} | { displayName, description? } | The updated entry; 400 for code-owned entries, which must change in source control |
DELETE /sqlos/admin/auth/api/scope-display-names/{id} | — | { deleted: true }; 400 for code-owned entries |
GET /sqlos/admin/auth/api/users/{userId}/grants | — | data: the user's active consent grants (id, clientId, clientName, scopes, grantedAt, updatedAt) |
POST /sqlos/admin/auth/api/users/{userId}/grants/{grantId}/revoke | — | The revoked grant's id and revokedAt; 400 for an unknown or already-revoked grant |
The default headless base is /sqlos/auth/headless. It can be moved with AuthServer.Headless.HeadlessApiBasePath. AuthServer.Headless.EnableApi defaults to true; disabling it leaves the routes unavailable with 404. These endpoints use JSON and operate on SqlOS authorization-request state.
| Method | Relative route | Purpose |
|---|---|---|
POST | /start | Start/load a headless authorization flow |
GET | /requests/{requestId} | Read the current view model |
POST | /identify | Run home-realm discovery |
POST | /password/login | Password sign-in |
POST | /password/forgot | Request a password reset email |
POST | /password/reset | Complete a password reset |
POST | /email-otp/start | Start email-code sign-in |
POST | /email-otp/verify | Verify email-code sign-in |
POST | /phone-otp/start | Start phone-code sign-in |
POST | /phone-otp/verify | Verify phone-code sign-in |
POST | /signup | Password signup |
POST | /signup/email-otp/start | Start email-code signup |
POST | /signup/email-otp/verify | Verify email-code signup |
POST | /signup/phone-otp/start | Start phone-code signup |
POST | /signup/phone-otp/verify | Verify phone-code signup |
POST | /invitations/resolve | Resolve an invitation for the current flow |
POST | /invitations/signup | Accept an invitation through signup |
POST | /organization/select | Select an organization |
POST | /mfa/verify | Verify TOTP or recovery code |
POST | /mfa/totp/enroll/start | Start required TOTP enrollment |
POST | /mfa/totp/enroll/verify | Verify required TOTP enrollment |
POST | /consent/approve | Approve the consent screen with { requestId, consentToken } |
POST | /consent/deny | Deny consent with { requestId, consentToken }; returns the access_denied redirect |
POST | /provider/start | Start a configured social/OIDC provider |
POST | /device/resolve | Resolve a device code request |
POST | /device/approve | Approve a device request |
POST | /device/deny | Deny a device request |
Start a flow with the same OAuth values you would send to /authorize:
curl -X POST https://app.example.com/sqlos/auth/headless/start \
-H 'Content-Type: application/json' \
-d '{
"responseType": "code",
"clientId": "acme-web",
"redirectUri": "https://app.example.com/auth/callback",
"state": "client-generated-state",
"scope": "openid offline_access",
"codeChallenge": "...",
"codeChallengeMethod": "S256",
"resource": "https://app.example.com/api",
"loginHint": "jane@example.com",
"view": "login",
"uiContext": { "returnTo": "/settings" }
}'prompt, nonce, maxAge, and invitationToken are also accepted as optional JSON fields with the same semantics as their /authorize query parameters. Headless /start never silently reuses an issuer session, so maxAge is not a re-challenge gate here; the value persists on the authorization request, and a positive maxAge is still enforced at code issuance when interstitials such as organization selection or MFA outlast it.
Actions return SqlOSHeadlessActionResult. This abridged response shows the control-flow fields:
{
"type": "view",
"redirectUrl": null,
"viewModel": {
"view": "login",
"requestId": "req_...",
"settings": {},
"providers": [],
"organizationSelection": []
}
}Keep requestId from the view model and send it in subsequent action requests. A completed action returns type: "redirect" with the registered client redirect URL; organization selection and MFA instead return another view. Validation failures currently use route-specific 400 bodies rather than one universal error object.
For browser clients on a different origin, send credentialed requests so the reusable issuer session cookie is preserved. The cookie is HttpOnly; the response view model and action result are the UI contract. See Build your own login and signup UI for the complete browser, PKCE, callback, CORS, and cookie walkthrough, or Headless Auth for feature details.
| Method | Route | Purpose |
|---|---|---|
GET | /sqlos/auth/oidc/providers | List enabled social/OIDC providers |
POST | /sqlos/auth/oidc/authorization-url | Create a provider authorization URL |
GET / POST | /sqlos/auth/oidc/callback | Complete the provider callback |
POST | /sqlos/auth/oidc/exchange | Exchange the SqlOS PKCE result |
POST | /sqlos/auth/sso/authorization-url | Create a state- and S256 PKCE-bound SAML authorization request |
POST | /sqlos/auth/saml/acs/{connectionId} | SAML assertion consumer service |
/oidc/authorization-url and /oidc/exchange are the app-owned social provider chooser. Like the direct sign-in routes above, they return tokens without a consent screen and accept only first-party clients; any other client gets 400 invalid_client before SqlOS creates provider state. A third-party client offers social sign-in through /authorize, whose hosted or headless page shows the provider buttons and runs consent.
Prefer canonical /sqlos/auth/authorize plus /sqlos/auth/token over invoking callback routes manually. The SAML authorization helper requires state, codeChallenge, and codeChallengeMethod: "S256"; its code is exchanged only at the standard token endpoint with the exact redirect URI and verifier. The former /token/exchange and /saml/login/{connectionId} compatibility routes are not mapped.
POST /sqlos/auth/register is mapped only when AuthServer.ClientRegistration.Dcr.Enabled is true. It accepts a SqlOSDynamicClientRegistrationRequest JSON document and applies the configured DCR redirect, client-type, rate-limit, scope-ceiling, and policy constraints.
| Request field | Behavior |
|---|---|
client_name | Display name for the registered client |
redirect_uris | Required HTTPS or loopback redirect URIs |
grant_types | Limited to the supported public-client grant set |
response_types | code for authorization-code clients |
token_endpoint_auth_method | none; confidential client secrets are not issued |
scope | Optional space-delimited allow-list persisted as the client's AllowedScopesJson. Later grants intersect requested scopes with this registered set. When omitted, SqlOS registers an empty allow-list. The response always echoes scope (an empty string when none were registered) so the client can predict later grants from the response alone. |
client_uri / logo_uri | Optional client presentation metadata |
software_id / software_version | Optional software metadata |
When AuthServer.ClientRegistration.Dcr.AllowedScopes is non-empty, it is the operator ceiling: requested scopes outside that set are invalid_client_metadata. When the ceiling list is empty, requested scopes are registered as-is, still bounded by MaxScopeCount (default 32) and MaxScopeLength (default 128). Exceeding those limits is a registration error.
Success returns 201 Created with SqlOSDynamicClientRegistrationResponse, including scope. Rejections use { error, error_description } with the status selected by SqlOSClientRegistrationException. SqlOS does not implement an RFC 7592 client-management endpoint.
CIMD does not add a registration route. When enabled, SqlOS resolves URL-shaped client IDs through client metadata documents subject to its trust configuration. See Preregistration vs CIMD vs DCR.
Self-serve SAML setup has a narrower trust boundary than the full dashboard:
| Surface | Default path | Authorization |
|---|---|---|
| Create/list/revoke setup sessions | /sqlos/admin/auth/api/sso-portal/sessions and organization-scoped variants | Full dashboard/operator authorization |
| Open hosted setup portal | /sqlos/admin/auth/sso-portal/start?token=... | One-time opaque setup-link token, exchanged for the portal cookie |
| Hosted portal API | /sqlos/admin/auth/sso-portal/api/* | Organization-scoped portal session cookie |
| Host-owned headless setup API | /sqlos/admin/auth/sso-portal/api/setup/* | The same organization-scoped portal session cookie |
The portal API can configure only the session's organization and SAML connection; it is not a general admin API. For a product-owned backend, inject SqlOSSsoPortalService to create setup sessions and expose only the setup URL. See SAML SSO.
SqlOSOptions.Calendar.Enabled defaults to true. While enabled, SqlOS adds:
| Method | Route | Purpose |
|---|---|---|
GET | /sqlos/auth/calendar/callback | Complete Google/Microsoft calendar consent and redirect to the request's return URI |
Applications start the flow through SqlOSCalendarService.StartConnectAsync; there is no public HTTP start endpoint supplied by the package.
The callback consumes the one-time state and redirects to the exact ReturnUri from SqlOSStartCalendarConnectRequest. Success appends calendarConnectionId; provider or completion failure appends error. Treat both values as callback input and verify the resulting connection belongs to the expected user or organization before using it.
SqlOS also exposes APIs used by the embedded auth, FGA, audit, email, SSO-setup, and optional calendar dashboards. Endpoint groups are mapped by the SqlOS startup filter; the unified shell and FGA component API/assets are middleware installed by AddSqlOS. These surfaces:
For trusted backend administration, inject the corresponding .NET service (SqlOSAdminService, ISqlOSAuditLogService, SqlOSEmailAdminService, SqlOSCalendarService, or SqlOSSsoPortalService) rather than coupling application code to dashboard JSON shapes.
Use path-specific network/edge rules in production. Restrict the exact dashboard root (/sqlos or /sqlos/) and dashboard/operator paths under /sqlos/admin/*, but allow /sqlos/admin/auth/sso-portal* when customers use delegated SSO setup. Keep the /sqlos/auth/* routes required by hosted OAuth/authentication reachable; do not apply blanket /sqlos/* or /sqlos/admin/* blocks without those intentional exceptions.
Routes such as the following are defined under examples/ and are not mounted by SqlOS:
/api/v1/auth/*/api/todos/api/sso-portal-links/sample/config/.well-known/oauth-protected-resource/swagger and /swagger/v1/swagger.jsonThey demonstrate how a consuming application can wrap SqlOS services, publish protected-resource metadata, or expose its own product API. Copy the pattern only after checking the example source against your authorization and trust boundary.
Mapped SqlOS endpoint groups call ExcludeFromDescription(), while dashboard middleware is not an endpoint surface at all. A consuming app's Swagger/OpenAPI document therefore does not automatically become the canonical SqlOS protocol specification. Use OAuth discovery for machine-readable OAuth endpoint metadata and this source-aligned route reference for the remaining package routes. Swagger shown by the example stack describes the example application's endpoints, not the complete SqlOS package surface.