Authentication API
AuthProxy exposes its public REST surface under /auth/v1/* and /auth/v2/*. All responses use the canonical ApiResponse<T> envelope (see Module integration).
This page is a curated map of the most useful endpoints with the actual DTO shapes. The authoritative, always-up-to-date reference is the built-in Swagger UI at https://yourdomain.com/docs/swagger/.
Route prefixes
| Prefix | Controller | Purpose |
|---|---|---|
/auth/v1/* | AuthController | Login, sessions, logout, OTP/magic, MCP login |
/auth/v1/social/{provider} | SocialController | OAuth providers (see OAuth Providers) |
/auth/v1/identity/* | IdentityController | Confirming phone / email contacts after login |
/auth/v1/keys/* | KeysController | Key registration / listing / deletion (FIDO2 + Ed25519 passkeys) |
/auth/v1/file/*, /auth/v2/file/* | FileUploadController, FileDownloadController, FileStreamController | File upload / download (see File Service) |
/auth/v1/app/* | AppController | user_app registration, webhook URL, scope management |
/auth/v1/federated/* | FederatedAuthController | Federation v2 browser login |
/auth/v1/notifications/* | PushNotificationsController | Web Push subscription lifecycle |
/auth/v1/subscribe | NotificationsController | SSE event stream |
/auth/v1/stun/* | StunDiagnosticsController | Embedded STUN diagnostics |
/mcp | McpController | JSON-RPC 2.0 MCP gateway (see MCP Protocol) |
/private/v1/* | PrivateController | Inter-module API guarded by X-API-Key |
Response envelope
All endpoints return ApiResponse<T>:
{
"id": 0,
"result": { /* T or scalar; null on error */ },
"error": null
}
On error result is null and error carries an integer code and human-readable message:
{
"id": 0,
"result": null,
"error": { "code": 1101, "message": "The session is not in the system. Please, login." }
}
Error codes live in ApiGateError (ItBuild.Shared/Api/ApiGateError.cs). Selected entries (full list in source):
| Code | Name | Meaning |
|---|---|---|
| -11001 | GateError | Gateway / unexpected server failure |
| 1001 | InvalidInputFormat | Malformed input |
| 1002 | AccessRateLimit | Rate limit exceeded |
| 1003 | RouteNotFound | No route_map match |
| 1004 | ResourceNotFound | Resource missing |
| 1101 | SessionNotFound | No active session |
| 1102 | AccountNotFound | User unknown |
| 1103 | KeyNotFound | Key missing |
| 1104 | KeysLimitReached | ActiveKeysLimit exhausted |
| 1106 | AccessDenied | Authorisation refused |
| 1108 | AppNotFound | user_app missing |
| 1220 | ChallengeExpired | login_options challenge expired |
| 1221 | DeviceIdMissing | Browser device GUID missing |
| 1226 | AuthMethodDisabled | Method disabled in admin settings |
| 1227 | TooManyAuthAttempts | OTP retry exhausted |
| 1231 | InvalidSignature | Ed25519 signature failed verification |
JSON-RPC error codes returned by /mcp (-32xxx) live in MCP Protocol.
Health and feature discovery
GET /auth/v1/get_info
Returns project name, build version, UTC time, available login methods bitmask (LoginMethod flags AND-ed with active configuration), licensed domain, and PWA versions.
{
"id": 0,
"result": {
"projectName": "Acme",
"version": "2026.05.01",
"utcTime": "2026-05-18T20:00:00Z",
"loginMethods": 36879,
"domain": "app.acme.com",
"proxyNode": "p1",
"pwaVersions": { "apg": "1.5.0", "chat": "3.2.1" },
"dbVersion": "20260301-1"
},
"error": null
}
loginMethods is an OR of LoginMethod flag values currently enabled and configured. domain is the application domain. proxyNode ("p1", "p2", …) identifies which AuthProxy node serves the request in a multi-node deployment — distinct from domain; see Reverse Proxy.
Browser login
GET /auth/v1/login_options
Returns a fresh challenge plus FIDO2 options. The full result is the AuthOptions object (challengeId, challenge, rpId, timeout, userVerification, fido2Options, optional phone).
{
"id": 0,
"result": {
"challengeId": "638412345678901234",
"challenge": "base64url_24_bytes",
"rpId": "app.acme.com",
"timeout": 120000,
"userVerification": "preferred",
"fido2Options": null
},
"error": null
}
POST /auth/v1/login
Body (LoginIn):
| Field | Type | When |
|---|---|---|
challengeId | string (long) | always |
credential | AuthenticatorAssertionRawResponse | FIDO2 flow |
signature | base64url byte[] | Ed25519 software passkey flow |
publicKey | base64url byte[] | password-derived key flow |
rememberDevice | bool | optional — true issues a sliding persistent cookie; false issues a browser-session cookie (no MaxAge) |
Success returns ApiResponse.Success (result: "Success") and sets the sid=… cookie. Failure returns one of ChallengeExpired, DeviceIdMissing, InvalidSignature, AccountNotFound, AccessDenied, etc.
POST /auth/v1/login_phone / POST /auth/v1/login_email
OTP / magic-link login. Bodies are LoginPhoneIn / LoginEmailIn; both require the method to be enabled in EnabledLoginMethods. The challengeId from login_options is mandatory — the OTP code is bound to that challenge.
// login_phone (LoginPhoneIn)
{ "challengeId": "638412345678901234", "phone": "+44...", "code": "123456" }
// login_email (LoginEmailIn) — omit "code" on the first call to request a code / magic link
{ "challengeId": "638412345678901234", "email": "user@acme.com", "code": "123456" }
GET /auth/v1/magic?token={base64url}
One-click email magic-link consumer. AES-256 decrypts the token, finds the matching challenge, creates a session, and redirects to the application URL.
POST /auth/v1/app_login
Body (AppLoginIn):
{ "appId": "638412345678901234", "saveAccess": true, "sessionCode": null }
Establishes a cross-application session bound to a verified user_app. When called outside an active session, supply sessionCode issued by the originating app.
POST /auth/v1/logout
Closes the current sid session. No body.
POST /auth/v1/close_sessions
Body (CloseSessionsIn?, optional):
{ "id": "638412345678901234" }
id is the session timetick. It is [LongToString], so the field accepts both a JS-safe string (recommended) and a number. Without a body, closes all sessions of the current user except the current one. With id, closes only that session.
GET /auth/v1/sessions?current=false
Returns the list of UserSession rows for the authenticated user. Each row has utcCreate, id (session timetick as string — int64 is serialised as a JSON string to avoid JS precision loss), userAgent, ip, loginType, current, flags, utcLastAccess, domain (application domain), and proxyNode ("pN" node label) — there is no country field. current=true returns only the active session.
GET /auth/v1/login_log
Recent login history (UserLoginLog): success / failure, login method (loginType), ipLocation, device, IP, domain, and proxyNode ("pN" node that handled the attempt).
GET /auth/v1/sign_log
Recent Ed25519 signature verification history for the authenticated user (UserSignLog, up to 100 rows, newest first):
{
"utcTime": "2026-07-01T12:00:00Z",
"coreId": "638412345678901234",
"coreType": "Wallet",
"keyId": "638412345678901235",
"signData": "base64url_payload",
"signature": "base64url_sig",
"isValid": true,
"proxyNode": "p1"
}
Requires an active session (401 without). Also exposed via MCP builtin tool sign_log.
GET /auth/v1/register_options?code=&attempt=1
Issues a registration challenge bound to the supplied verification code (sent earlier via SMS / email). Returns the same AuthOptions shape as login_options.
GET /auth/v1/reset_password?phone=...
Triggers password-reset flow on the given phone (delivered through the Chat module).
MCP login
POST /auth/v1/mcp_login
Authenticates an AI agent / programmatic client via Ed25519 keys. Inputs are HTTP headers only (X-PublicKey, optional X-Timestamp + X-Signature for McpVerified, optional X-App-Id for McpApp). Returns McpLoginResult (sessionId, loginType, expiresUtc, appId).
Detailed semantics, signature payload, and capability mode: MCP Protocol.
Key management
All endpoints under /auth/v1/keys/* operate on user_key rows owned by the current session.
| Endpoint | Body | Purpose |
|---|---|---|
POST /auth/v1/keys/register_key | AuthIn (challengeId + OTP code + credentialNew for FIDO2 or publicKey for Ed25519) | Enroll a new key. The OTP code (bound to challengeId + the user's crm_id) is required. |
GET /auth/v1/keys/user_keys | — | List the current user's active keys (returns List<UserKey>). |
POST /auth/v1/keys/remove_key | RemoveKeyIn ({ "keyId": "..." }) | Soft-delete the key. |
POST /auth/v1/keys/update_key | UpdateKeyIn ({ "keyId": "...", "mcpAccess": true, "cardDavAccess": false }) | Toggle McpAccess and CardDav flags. |
UserKey shape:
{
"utcCreate": "2026-04-01T12:00:00Z",
"id": "638412345678901234",
"publicKey": "base64url_32_bytes",
"keyType": "FIDO2",
"current": true,
"mcpAccess": false,
"cardDavAccess": false
}
keyType is one of PasswordKey, UserKey, FIDO2, Unknown. FIDO2 entries store the credential id in publicKey and the CBOR public key in the underlying user_key.fido2_key column.
Identity verification (signed-in user)
After login the user can attach additional verified contacts:
| Endpoint | Body | Purpose |
|---|---|---|
POST /auth/v1/identity/confirm_phone | ConfirmPhoneIn ({ "phone": "...", "code": "...", "attempt": 1 }) | Verify a phone contact via SMS code. Sets user_exid_flags.Verified. |
POST /auth/v1/identity/confirm_email | ConfirmEmailIn ({ "email": "...", "code": "..." }) | Verify an email contact. |
See Verified Contacts for the resulting flag semantics.
OAuth providers
/auth/v1/social/{provider} — Google, Telegram, Discord, Apple, Facebook, GitHub, VKontakte. Each provider has a slightly different shape because the upstream protocol differs. See OAuth Providers for the full table with endpoints, methods, and configuration.
?link=true on Google / Discord / Facebook attaches the social account to the currently signed-in user instead of creating a new one.
Federation v2 (browser)
| Endpoint | Method | Purpose |
|---|---|---|
GET /auth/v1/federated/providers | anonymous | List partner providers (FederatedProviderOut[]). |
GET /auth/v1/federated/start?providerAppId={long} | anonymous | Set partner state cookie and return the partner's login URL (FederatedStartOut.redirectUrl). |
POST /auth/v1/federated/login | anonymous (state cookie required) | Body FederatedLoginIn ({ "usr", "check" }) — partner-signed federation payload; returns FederatedLoginOut.returnUrl after minting the local sid. |
Full security model and key exchange: Federation v2 — Browser Login.
Embedded STUN diagnostics
Auth-only diagnostics for the embedded STUN responder; see Embedded STUN for the runtime details.
POST /auth/v1/stun/report
Body is ClientStunDiagnosticReport:
{
"callId": "...",
"stage": "checking",
"connectionState": "connecting",
"iceConnectionState": "checking",
"iceGatheringState": "gathering",
"signalingState": "stable",
"localCandidateTypes": ["host", "srflx"],
"remoteCandidateTypes": ["host"],
"selectedLocalCandidateType": "srflx",
"selectedRemoteCandidateType": "host",
"currentRoundTripTime": 0.042,
"bytesSent": 12345,
"bytesReceived": 67890
}
Stores the current client's WebRTC diagnostics for the active call and returns the merged server snapshot (server counters + client report). Throttled per-session to avoid hot-loop spam.
GET /auth/v1/stun/current?callId={id}
Returns the merged client + server snapshot without writing anything new.
Inbound email
The SMTP listener is documented in Inbound Email Bridge. It exposes no public HTTP endpoints — inbound mail arrives over SMTP on port 25 and is dispatched into Chat through private/v1/inbound_email_send (X-API-Key, internal call between AuthProxy and Chat).
File operations
Two upload generations are supported; the v2 streaming flow is recommended for new clients.
v1 — buffered, part-based
| Endpoint | Purpose |
|---|---|
POST /auth/v1/file/init_upload | Start a session. Body: { "FileName", "FileSize", "CRC32", "ChatId", "Message", "LinkedMessageId" }. Returns the numeric uploadId. |
POST /auth/v1/file/upload_part?uploadId=&chunkNumber=&chunkCrc32= | Send one chunk (raw binary body). Final chunk returns { "chunkNumber", "fileId", "fileCode" }. |
POST /auth/v1/file/cancel_upload?uploadId= | Abort an active upload. |
GET /auth/v1/file/{fileId} (HEAD also supported) | Session-authenticated download. Optional ?part_number=N for 256 KB slices. |
v2 — streaming
| Endpoint | Purpose |
|---|---|
POST /auth/v2/file/create_draft?fileName=&fileSize=&chatId=&message=&linkedMessageId=&crc32= | Create a pending Chat file message. Returns "fileId:fileCode". |
POST /auth/v2/file/stream_upload/{fileId} | Stream upload with Range: bytes=... and raw binary body. HTTP 200 with empty body on success. |
GET /auth/v2/file/{id_or_code} (HEAD, Range, ETag, If-None-Match, If-Match) | Download by obfuscated code or numeric id (id-based downloads are gated by FileDownloadByIdEnabled; code-based downloads optionally require a session via FileDownloadAuthRequired). |
Application management
| Endpoint | Method | Purpose |
|---|---|---|
GET /auth/v1/app/get_auth_param?appId={long} | anonymous | Authentication parameters for an external user_app. |
GET /auth/v1/app/get_app_info?appId={long} | anonymous | Public app metadata. |
GET /auth/v1/app/get_usr_apps | session | The current user's owned apps. |
POST /auth/v1/app/upsert_usr_app | session | Body UserAppIn — create/update an app, set webhookUrl, currencies (array), netTypes (array). Does not manage user scopes. |
POST /auth/v1/app/validate_webhook?appId={long} | session | Perform the webhook validation handshake. |
GET /auth/v1/app/list_app_access | session | List apps the current user has granted access to. |
POST /auth/v1/app/revoke_app_access | session | Body RevokeAppAccessIn — revoke a previously granted app access. |
POST /auth/v1/app/delete_usr_app?appId={string} | session | Delete an owned app. |
POST /auth/v1/app/verify_domain?appId={long} | session | Run DNS TXT verification. |
GET /auth/v1/app/get_dns_instruction?appId={long} | session | Returns the DNS TXT record the operator must publish. |
POST /auth/v1/app/federation_webhook | service-to-service | Federation broadcast hook used between paired AuthProxy projects. |
Push notifications
GET /auth/v1/subscribe remains the SSE real-time stream endpoint. Web Push subscription management uses the separate /auth/v1/notifications/* namespace.
For setup, platform limitations, and operational troubleshooting, see Browser Push Notifications.
Transport split
AuthProxy exposes two different notification transports:
- SSE —
GET /auth/v1/subscribe- live browser tabs
- reconnect with
?since= - capability mode for anonymous/private resources
- Web Push —
/auth/v1/notifications/*- browser subscription lifecycle
- service worker
pushdelivery - background notifications and closed-tab delivery while the browser push infrastructure is still able to wake the service worker
Web Push does not replace SSE. A typical deployment uses both:
- SSE for active sessions and live UI refresh
- Web Push for background delivery when the browser is not actively rendering the app shell
Important browser caveat:
- AuthProxy controls subscription storage and payload delivery, but it does not control whether a fully terminated browser process will be resumed by the OS/browser vendor push service.
- Treat Web Push as the background transport, not as a guarantee that every fully closed desktop browser will wake up on every machine.
GET /auth/v1/notifications/public_key
Returns the VAPID public key (PushPublicKeyOut).
GET /auth/v1/notifications/device_status?...
Reports whether the current device has an active subscription and whether its VAPID key hash is current.
POST /auth/v1/notifications/subscribe
Body (PushSubscribeIn):
{
"endpoint": "https://fcm.googleapis.com/...",
"keys": { "p256dh": "base64", "auth": "base64" },
"origin": "https://app.acme.com",
"appScope": "root",
"deviceGuid": "00000000-0000-0000-0000-000000000001",
"flags": 2047,
"userApp": "0",
"userAgent": "Mozilla/5.0 ...",
"clientVersion": "2.1.0",
"locale": "en-US",
"metaJson": "{\"pwa\":\"chat\"}"
}
Upsert by endpoint hash; repeated subscribe with the same browser endpoint is treated as refresh, not conflict. origin must match the current AuthProxy/browser origin; userApp is optional and reserved for customer app targeting.
appScope is opaque to the platform; common values: root (shell), chat, wallet, core:<app>.
POST /auth/v1/notifications/unsubscribe
Body (PushUnsubscribeIn) — either { "endpoint": "..." } or { "origin", "appScope", "deviceGuid" }. Idempotent: the subscription is marked disabled and cleaned up later.
POST /auth/v1/notifications/test
Body { "title": "...", "body": "..." } — send a real test push through the configured transport. Does not exercise event generation, PushDispatcher event mapping, or SSE-suppression rules; use /ProxyAdmin/PushSubscriptions for those.
GET /auth/v1/notifications/mute_status and POST /auth/v1/notifications/set_mute
Manage per-user Do Not Disturb (PushMuteIn / PushMuteOut).
Customer admin checklist
To make browser push work end-to-end, customer admins must configure all of the following:
- Enable Web Push in AuthProxy configuration (
WebPush.Enabled, VAPID keys, subject). - Enable
PushSendingon exactly one AuthProxy node in phase 1 deployments. - Deploy one canonical root
sw.json the application origin. - Ensure frontend apps call
public_key+subscribeafter notification permission is granted. - Keep SSE enabled as the live-tab transport; Web Push is the background channel, not a replacement for
/auth/v1/subscribe. - Use
/ProxyAdmin/PushSubscriptionsfor transport diagnostics; a successful admin test there proves sender transport only, not the full business-event pipeline.
Interactive Documentation
AuthProxy includes built-in Swagger UI documentation available at:
https://yourdomain.com/docs/swagger/AuthProxyOpenApi.json
This provides complete API documentation with request/response examples and the ability to test endpoints directly.