GoAuth
API Reference

API Endpoints

All paths are prefixed with your BasePath (default: /auth). Endpoints depend on which modules you register. These examples describe the current checkout.…

All paths are prefixed with your BasePath (default: /auth). Endpoints depend on which modules you register. These examples describe the current checkout. Timestamps and token strings are illustrative; fields omitted by a DTO may be absent. The OpenAPI document generated from your initialized instance is available through the Auth API when enabled.

Most JSON responses use the standard APIResponse envelope. OAuth redirects and the raw JWKS response are exceptions:

{
  "data": { ... },
  "message": "optional message"
}

Handler errors use the response status and structured fields inside data. Runtime guards (such as unavailable authentication or admission exhaustion) can return plain-text 503 responses:

{
  "data": {
    "code": "ERROR_CODE",
    "message": "Human-readable message"
  }
}

List endpoints return a ListResponse inside data:

{
  "data": {
    "list": [ ... ],
    "sort_field": "created_at",
    "sort_dir": "desc",
    "total": 42
  }
}

Core Module (auto-registered)

The core module is always present. It handles user registration, profile management, password flows, and verification. Login/logout/refresh are provided by either the Session or Stateless module.

POST /signup — Register a new user

Request:

{
  "email": "user@example.com",
  "password": "SecurePassword123!",
  "name": "John Doe",
  "first_name": "John",
  "last_name": "Doe",
  "username": "johndoe",
  "phone_number": "+1234567890"
}

Only email (or username) and password are required. The other fields are optional.

Success Response 201 Created:

{
  "data": {
    "user": {
      "id": "01961abc-...",
      "email": "user@example.com",
      "name": "John Doe",
      "first_name": "John",
      "last_name": "Doe",
      "username": "johndoe",
      "phone_number": "+1234567890",
      "active": true,
      "email_verified": false,
      "phone_number_verified": false,
      "created_at": "2025-01-01T00:00:00Z",
      "updated_at": null
    },
    "message": "User registered successfully"
  }
}

Signup only creates the user; it does not issue credentials. Complete any required contact verification, then authenticate using /login or another enabled method. The default password policy accepts 8–72 bytes without trimming or normalization.

Error 400: validation errors (email already exists, password policy violation, etc.)

GET /me — Get current user Auth

Headers: Authorization: Bearer <access_token>

Response 200:

{
  "data": {
    "id": "01961abc-...",
    "email": "user@example.com",
    "name": "John Doe",
    "first_name": "John",
    "last_name": "Doe",
    "username": "johndoe",
    "avatar": "https://example.com/avatar.png",
    "phone_number": "+1234567890",
    "active": true,
    "email_verified": true,
    "phone_number_verified": false,
    "created_at": "2025-01-01T00:00:00Z",
    "updated_at": "2025-01-15T00:00:00Z",
    "last_login_at": "2025-01-20T10:30:00Z"
  }
}
PUT /profile — Update profile Auth

Headers: Authorization: Bearer <access_token>

Request:

{
  "name": "Jane Doe",
  "phone": "+9876543210",
  "avatar": "https://example.com/new-avatar.png"
}

All fields are optional. phone is validated in E.164 format. avatar must be a valid URL.

Response 200:

{
  "data": {
    "id": "01961abc-...",
    "email": "user@example.com",
    "name": "Jane Doe",
    "phone_number": "+9876543210",
    "avatar": "https://example.com/new-avatar.png",
    "active": true,
    "email_verified": true,
    "phone_number_verified": false,
    "created_at": "2025-01-01T00:00:00Z",
    "updated_at": "2025-01-20T12:00:00Z"
  }
}
PUT /change-password — Change password Auth

Headers: Authorization: Bearer <access_token>

Request:

{
  "old_password": "OldPassword123!",
  "new_password": "NewPassword456!"
}

Both fields are required. New password must be at least 8 characters and different from the old password.

Response 200:

{
  "data": {
    "message": "Password changed successfully"
  }
}
POST /is-available — Check availability

Check whether an email, username, or phone number is already taken. Provide exactly one of the three fields.

Request (email):

{"email": "newuser@example.com"}

Request (username):

{"username": "johndoe"}

Request (phone):

{"phone": "+1234567890"}

Response 200:

{
  "data": {
    "available": true,
    "field": "email",
    "message": "email is available"
  }
}
POST /forgot-password — Request password reset

Request:

{"email": "user@example.com"}

Or by phone:

{"phone": "+1234567890"}

Response 200:

{
  "data": {
    "message": "Password reset email sent"
  }
}
POST /reset-password — Reset password with token or code

Request (token-based, from email link):

{
  "token": "reset-token-from-email",
  "email": "user@example.com",
  "new_password": "NewPassword456!"
}

Request (code-based, from SMS):

{
  "code": "123456",
  "phone": "+1234567890",
  "new_password": "NewPassword456!"
}

Either token or code is required, along with the corresponding email or phone.

Response 200:

{
  "data": {
    "message": "Password reset successfully"
  }
}
POST /send-verification-email — Send verification email

Request:

{"email": "user@example.com"}

Response 200:

{
  "data": {
    "message": "Verification email sent"
  }
}
POST /resend-verification-email — Resend verification email

Request:

{"email": "user@example.com"}

Same as /send-verification-email but intended for resend flows.

GET /verify-email?token=...&email=... — Verify email

Called from the email verification link. Redirects to FrontendConfig.VerifyEmailCallbackPath with ?status=success or ?status=error.

Falls back to a JSON response if FrontendConfig is not set.

POST /send-verification-phone — Send phone verification SMS

Request:

{"phone": "+1234567890"}

Requires the Notification module with an SMS sender configured.

POST /resend-verification-phone — Resend phone verification SMS

Request:

{"phone": "+1234567890"}
POST /verify-phone — Verify phone number

Request:

{
  "phone": "+1234567890",
  "code": "123456"
}

Code must be exactly 6 digits.

Response 200:

{
  "data": {
    "message": "Phone number verified successfully"
  }
}

Session Module

Requires session.New(...). Mutually exclusive with the Stateless module.

POST /login — Login (create session)

Request:

{
  "email": "user@example.com",
  "password": "SecurePassword123!"
}

Or login by username:

{
  "username": "johndoe",
  "password": "SecurePassword123!"
}

Success Response 200:

{
  "data": {
    "access_token": "eyJhbGciOi...",
    "refresh_token": "eyJhbGciOi...",
    "expires_in": 900,
    "user": {
      "id": "01961abc-...",
      "email": "user@example.com",
      "name": "John Doe",
      "active": true,
      "email_verified": true,
      "phone_number_verified": false,
      "created_at": "2025-01-01T00:00:00Z",
      "updated_at": "2025-01-15T00:00:00Z"
    },
    "message": "Login successful"
  }
}

If the Two-Factor module is registered and the user has 2FA enabled, the response will contain a challenges array instead of tokens:

{
  "data": {
    "user": { ... },
    "challenges": [
      {
        "type": "2fa",
        "data": {
          "requires_2fa": true,
          "temp_token": "eyJhbGciOi...",
          "user_id": "01961abc-...",
          "message": "Two-factor authentication required. Please provide your 2FA code."
        }
      }
    ],
    "message": "Login requires additional verification"
  }
}
POST /logout — Logout (end session) Auth

Headers: Authorization: Bearer <access_token>

Ends the current session.

Response 200:

{
  "data": {
    "message": "Logged out successfully"
  }
}
POST /refresh — Refresh tokens

Request:

{"refresh_token": "eyJhbGciOi..."}

Response 200:

{
  "data": {
    "access_token": "eyJhbGciOi...",
    "refresh_token": "eyJhbGciOi...",
    "expires_in": 900,
    "message": "Token refreshed successfully"
  }
}
GET /sessions — List sessions Auth

Headers: Authorization: Bearer <access_token>

Returns all active sessions for the authenticated user.

Response 200:

{
  "data": [
    {
      "id": "sess-01961abc-...",
      "user_agent": "Mozilla/5.0...",
      "ip_address": "192.168.1.1",
      "created_at": "2025-01-01T00:00:00Z",
      "expires_at": "2025-01-02T00:00:00Z",
      "current": true
    },
    {
      "id": "sess-01961def-...",
      "user_agent": "Chrome/120...",
      "ip_address": "10.0.0.1",
      "created_at": "2025-01-01T12:00:00Z",
      "expires_at": "2025-01-02T12:00:00Z",
      "current": false
    }
  ]
}
GET /sessions/{session_id} — Get session Auth

Headers: Authorization: Bearer <access_token>

Returns details for a specific session.

Response 200:

{
  "data": {
    "id": "sess-01961abc-...",
    "user_agent": "Mozilla/5.0...",
    "ip_address": "192.168.1.1",
    "created_at": "2025-01-01T00:00:00Z",
    "expires_at": "2025-01-02T00:00:00Z",
    "current": false
  }
}
DELETE /sessions/{session_id} — Revoke session Auth

Headers: Authorization: Bearer <access_token>

Revokes a specific session by ID.

Response 200:

{
  "data": {
    "message": "Session revoked successfully"
  }
}
DELETE /sessions — Revoke all sessions Auth

Headers: Authorization: Bearer <access_token>

Revokes all of the user’s sessions, including the current session.

Response 200:

{
  "data": {
    "message": "All other sessions revoked"
  }
}

Stateless Module

Default if no auth module is registered. Or register explicitly with stateless.New(...). Mutually exclusive with Session module.

POST /login — Login (JWT tokens)

Request:

{
  "email": "user@example.com",
  "password": "SecurePassword123!"
}

Or login by username:

{
  "username": "johndoe",
  "password": "SecurePassword123!"
}

Success Response 200:

{
  "data": {
    "access_token": "eyJhbGciOi...",
    "refresh_token": "eyJhbGciOi...",
    "expires_in": 900,
    "user": {
      "id": "01961abc-...",
      "email": "user@example.com",
      "name": "John Doe",
      "active": true,
      "email_verified": true,
      "phone_number_verified": false,
      "created_at": "2025-01-01T00:00:00Z",
      "updated_at": "2025-01-15T00:00:00Z"
    },
    "message": "Login successful"
  }
}

If the Two-Factor module is registered and the user has 2FA enabled, the response includes challenges instead (see Session module login for shape).

POST /logout — Revoke user credentials Auth

Headers: Authorization: Bearer <access_token>

Increments the user’s token version and deletes their stored token records. This invalidates existing credentials for that user across devices; it is not a single-token blacklist.

Response 200:

{
  "data": {
    "message": "Logged out successfully"
  }
}
POST /refresh — Refresh tokens

Request:

{"refresh_token": "eyJhbGciOi..."}

Returns new access and refresh credentials after conditionally consuming the stored nonce. Rotation is always applied by the current service, including when the legacy RefreshTokenRotation flag is false. Reusing the old refresh credential fails.

Response 200:

{
  "data": {
    "access_token": "eyJhbGciOi...",
    "refresh_token": "eyJhbGciOi...",
    "expires_in": 900,
    "message": "Token refreshed successfully"
  }
}

Two-Factor Module

Requires twofactor.New(...).

POST /2fa/setup — Start 2FA setup Auth

Headers: Authorization: Bearer <access_token>

Generates a TOTP secret, QR code URL, and backup codes. The user must verify with a code before 2FA is enabled.

Response 200:

{
  "data": {
    "secret": "JBSWY3DPEHPK3PXP",
    "qr_url": "otpauth://totp/GoAuth:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=GoAuth",
    "backup_codes": ["A1B2C3D4", "E5F6G7H8", "..."],
    "message": "Scan the QR code with your authenticator app, then verify with a code to enable 2FA"
  }
}
POST /2fa/verify — Verify and enable 2FA Auth

Headers: Authorization: Bearer <access_token>

Verifies a TOTP code against the pending secret and enables 2FA.

Request:

{"code": "123456"}

Response 200:

{
  "data": {
    "message": "Two-factor authentication enabled successfully"
  }
}
POST /2fa/disable — Disable 2FA Auth

Headers: Authorization: Bearer <access_token>

Requires a current TOTP code for verification before disabling.

Request:

{"code": "123456"}

Response 200:

{
  "data": {
    "message": "Two-factor authentication disabled successfully"
  }
}
GET /2fa/status — Get 2FA status Auth

Headers: Authorization: Bearer <access_token>

Response 200:

{
  "data": {
    "enabled": true,
    "method": "totp"
  }
}

When 2FA is not enabled, method is an empty string.

POST /2fa/verify-login — Complete login with 2FA

Called after login returns a challenges array with type: "2fa". No auth header required -- uses temp_token from the challenge data.

Request:

{
  "temp_token": "eyJhbGciOi...",
  "code": "123456"
}

The code can be a TOTP code or a backup code. temp_token is required and is bound to a persisted, expiring 2fa_pending continuation. Attempts are counted; successful completion consumes the continuation atomically. It cannot be used as an access token.

Success Response 200:

{
  "data": {
    "access_token": "eyJhbGciOi...",
    "refresh_token": "eyJhbGciOi...",
    "user": {
      "id": "01961abc-...",
      "email": "user@example.com"
    }
  }
}

OAuth Module

Requires oauth.New(...) with configured providers and APIURL set in config.

GET /oauth/{provider} — Start OAuth flow

Redirects the user to the OAuth provider's consent screen.

Built-in provider types: google, github, microsoft, discord. The {provider} segment is a configured connection ID; a named connection can select a type with OAuthProviderConfig.Type. Microsoft requires an explicit tenant UUID.

GET /oauth/{provider}/callback — OAuth callback

The callback requires the matching one-use state and browser binding cookie from the start endpoint. It verifies the provider identity, applies account-linking policy, and runs shared completion. A 2FA challenge returns JSON without redirecting or issuing final credentials. A completed flow:

  • Redirects to DefaultRedirectURL; cookie-only mode omits token fragments, while bearer/both modes include them.
  • Returns JSON when no redirect URL is configured:
{
  "data": {
    "access_token": "eyJhbGciOi...",
    "refresh_token": "eyJhbGciOi...",
    "expires_in": 900,
    "token_type": "Bearer",
    "user": {
      "id": "01961abc-...",
      "email": "user@gmail.com",
      "name": "John Doe",
      "first_name": "John",
      "last_name": "Doe",
      "avatar": "https://lh3.googleusercontent.com/...",
      "email_verified": true,
      "active": true,
      "created_at": "2025-01-01T00:00:00Z"
    },
    "is_new_user": false,
    "provider": "google"
  }
}
DELETE /oauth/{provider} — Unlink provider Auth

Headers: Authorization: Bearer <access_token>

Unlinks an OAuth provider from the user's account.

Response 200:

{
  "data": {
    "message": "Provider unlinked successfully"
  }
}
GET /oauth/providers — List available providers

Response 200:

{
  "data": {
    "providers": [
      {"name": "google", "enabled": true},
      {"name": "github", "enabled": true}
    ]
  }
}
GET /oauth/linked — List linked providers Auth

Headers: Authorization: Bearer <access_token>

Response 200:

{
  "data": {
    "providers": ["google", "github"]
  }
}

Admin Module

Requires admin.New(...). All routes require auth + admin middleware.

GET /admin/users — List users Admin

Headers: Authorization: Bearer <access_token>

Query Parameters:

ParamDefaultDescription
offset0Number of records to skip
limit20Max records to return (max 100)
sort_fieldcreated_atSort by: created_at, email, username, name
sort_dirdescSort direction: asc or desc
querySearch by name, email, or username

Response 200:

{
  "data": {
    "list": [
      {
        "id": "01961abc-...",
        "email": "user@example.com",
        "name": "John Doe",
        "first_name": "John",
        "last_name": "Doe",
        "username": "johndoe",
        "avatar": "",
        "phone_number": "+1234567890",
        "active": true,
        "email_verified": true,
        "phone_number_verified": false,
        "is_super_admin": false,
        "token_version": 1,
        "created_at": "2025-01-01T00:00:00Z",
        "updated_at": "2025-01-15T00:00:00Z",
        "last_login_at": "2025-01-20T10:30:00Z"
      }
    ],
    "sort_field": "created_at",
    "sort_dir": "desc",
    "total": 42
  }
}
GET /admin/users/{id} — Get user Admin

Headers: Authorization: Bearer <access_token>

Returns full user details including admin-only fields (is_super_admin, token_version).

Response 200:

{
  "data": {
    "id": "01961abc-...",
    "email": "user@example.com",
    "name": "John Doe",
    "first_name": "John",
    "last_name": "Doe",
    "username": "johndoe",
    "avatar": "",
    "phone_number": "+1234567890",
    "active": true,
    "email_verified": true,
    "phone_number_verified": false,
    "is_super_admin": false,
    "token_version": 1,
    "created_at": "2025-01-01T00:00:00Z",
    "updated_at": "2025-01-15T00:00:00Z",
    "last_login_at": "2025-01-20T10:30:00Z"
  }
}
PUT /admin/users/{id} — Update user Admin

Headers: Authorization: Bearer <access_token>

All fields are optional (use JSON null or omit). At least one field must be provided.

Request:

{
  "first_name": "Jane",
  "last_name": "Smith",
  "name": "Jane Smith",
  "email": "jane@example.com",
  "username": "janesmith",
  "avatar": "https://example.com/avatar.png",
  "phone_number": "+9876543210",
  "active": false,
  "email_verified": true,
  "phone_number_verified": false,
  "is_super_admin": false
}

Response 200:

{
  "data": {
    "message": "User updated successfully"
  }
}
DELETE /admin/users/{id} — Delete user Admin

Headers: Authorization: Bearer <access_token>

Permanently deletes the user.

Response 200:

{
  "data": {
    "message": "User deleted successfully"
  }
}

Audit Module

Requires audit.New(...).

User self-service routes

GET /me/audit — My audit logs Auth

Headers: Authorization: Bearer <access_token>

Query Parameters:

ParamDefaultDescription
offset0Number of records to skip
limit20Max records to return (max 100)
sort_fieldcreated_atSort by: created_at, action, severity, actor_id
sort_dirdescSort direction: asc or desc

Response 200:

{
  "data": {
    "list": [
      {
        "id": "01961abc-...",
        "action": "auth.login.success",
        "actor_id": "01961abc-...",
        "actor_type": "user",
        "severity": "info",
        "details": "Action: auth.login.success",
        "metadata": "{...}",
        "ip_address": "192.168.1.1",
        "user_agent": "Mozilla/5.0...",
        "created_at": "2025-01-20T10:30:00Z"
      }
    ],
    "sort_field": "created_at",
    "sort_dir": "desc",
    "total": 15
  }
}
GET /me/audit/logins — My login history Auth

Headers: Authorization: Bearer <access_token>

Returns login-related audit events for the authenticated user. Same query parameters and response shape as /me/audit.

GET /me/audit/changes — My profile changes Auth

Headers: Authorization: Bearer <access_token>

Returns profile update events. Same query parameters and response shape as /me/audit.

GET /me/audit/security — My security events Auth

Headers: Authorization: Bearer <access_token>

Returns security-related events (2FA changes, password changes, etc.). Same query parameters and response shape as /me/audit.

Admin audit routes

GET /admin/audit — All audit logs Admin

Headers: Authorization: Bearer <access_token>

Returns all audit logs across all users. Same query parameters and response shape as /me/audit.

GET /admin/audit/users/{id} — User audit logs Admin

Headers: Authorization: Bearer <access_token>

Returns audit logs for a specific user. Same query parameters and response shape as /me/audit.

GET /admin/audit/actions/{action} — Logs by action Admin

Headers: Authorization: Bearer <access_token>

Returns audit logs filtered by action type (e.g., auth.login.success, auth.password.changed). Same query parameters and response shape as /me/audit.

POST /admin/audit/cleanup — Cleanup old logs Admin

Headers: Authorization: Bearer <access_token>

Triggers manual cleanup of old audit logs based on retention policy.

Response 200:

{
  "data": {
    "message": "Audit logs cleaned up successfully"
  }
}

Requires magiclink.New(...) and the Notification module.

POST /magic-link/send — Send magic link

Request:

{"email": "user@example.com"}

Sends an email with a magic link. If AutoRegister: true, creates a new user if the email does not exist.

Response 200:

{
  "data": {
    "message": "Magic link sent"
  }
}
GET /magic-link/verify?token=... — Verify magic link

Called from the email link. Verifies the token and either:

  • Redirects to CallbackURL with tokens in the URL fragment: #access_token=xxx&refresh_token=xxx
  • Or returns a JSON auth response if no callback URL is configured
POST /magic-link/verify-code — Verify by code

For mobile apps -- verifies using the numeric code from the email.

Request:

{"email": "user@example.com", "code": "123456"}

Response 200:

{
  "data": {
    "access_token": "eyJhbGciOi...",
    "refresh_token": "eyJhbGciOi...",
    "expires_in": 900,
    "user": {
      "id": "01961abc-...",
      "email": "user@example.com"
    }
  }
}
POST /magic-link/resend — Resend magic link

Request:

{"email": "user@example.com"}

Response 200:

{
  "data": {
    "message": "Magic link sent"
  }
}

CSRF Module

Requires csrf.New(...).

GET /csrf-token — Get CSRF token

Response 200:

{
  "data": {
    "csrf_token": "hmac-signed-token"
  }
}

Also sets a cookie (default name: __goauth_csrf) with HttpOnly: false so client JavaScript can read it.

Include the token in the X-CSRF-Token header for state-changing requests (POST, PUT, DELETE, PATCH). The CSRF middleware validates the header token against the cookie automatically.


Invitation Module

Standalone platform invitations. For org-scoped invitations, see the Organization Module below.

Authenticated routes

POST /invitations — Send invitation Auth

Headers: Authorization: Bearer <access_token>

Request:

{
  "email": "invitee@example.com",
  "purpose": "beta",
  "metadata": "{\"referral_code\": \"ABC123\"}"
}

purpose defaults to "platform". metadata is optional.

Response 201:

{
  "data": {
    "id": "01961abc-...",
    "email": "invitee@example.com",
    "purpose": "beta",
    "inviter_id": "01961def-...",
    "status": "pending",
    "expires_at": "2025-01-09T00:00:00Z",
    "created_at": "2025-01-02T00:00:00Z"
  }
}
GET /invitations — List sent invitations Auth

Lists invitations sent by the authenticated user. Supports ?status=pending&purpose=beta&limit=20&offset=0&sort_field=created_at&sort_dir=desc.

GET /invitations/my — My pending invitations Auth

Returns pending invitations for the authenticated user's email.

DELETE /invitations/{invId} — Cancel invitation Auth

Only the inviter can cancel their own invitations.

Public routes (no auth required)

POST /invitations/accept — Accept invitation Public

No authentication required. The invitation token is the authorization. If the invited email has no account, provide name and password to create one.

Request:

{
  "token": "invitation-token-from-email",
  "name": "John Doe",
  "password": "securepassword123"
}

name and password are only required when the invited email has no existing account.

Response 200:

{
  "data": {
    "access_token": "eyJ...",
    "refresh_token": "eyJ...",
    "user": {
      "id": "01961abc-...",
      "email": "invitee@example.com",
      "name": "John Doe",
      "email_verified": true
    },
    "is_new_user": true
  }
}

The invitation mutation commits before authentication completion. The response may contain challenges instead of final credentials when the account requires 2FA. Consume that challenge response rather than retrying the already-accepted invitation.

POST /invitations/decline — Decline invitation Public

No authentication required.

Request:

{"token": "invitation-token-from-email"}

Response 200:

{"data": null}

Organization Module

Requires organization.New(...).

User-level routes (no org context)

POST /org — Create organization Auth

Headers: Authorization: Bearer <access_token>

Request:

{
  "name": "Acme Inc",
  "slug": "acme-inc"
}

slug is optional -- auto-generated from name if omitted. Must be lowercase alphanumeric with hyphens, 2-64 characters.

Response 201:

{
  "data": {
    "id": "01961abc-...",
    "name": "Acme Inc",
    "slug": "acme-inc",
    "owner_id": "01961abc-...",
    "active": true,
    "created_at": "2025-01-01T00:00:00Z"
  }
}
GET /org/my — List my organizations Auth

Headers: Authorization: Bearer <access_token>

Returns all organizations the authenticated user is a member of.

Response 200:

{
  "data": [
    {
      "id": "01961abc-...",
      "name": "Acme Inc",
      "slug": "acme-inc",
      "owner_id": "01961abc-...",
      "active": true,
      "created_at": "2025-01-01T00:00:00Z"
    }
  ]
}
POST /org/switch — Switch active organization Auth

Headers: Authorization: Bearer <access_token>

Requires a verified principal and current membership in an active organization. Shared completion preserves the original authentication time and methods, and returns new credentials with active_org_id and org_role. No first organization is selected automatically and no org_memberships claim is added. The current switch handler rejects results that require another challenge.

Request:

{"org_id": "01961abc-..."}

Response 200:

{
  "data": {
    "access_token": "eyJhbGciOi...",
    "refresh_token": "eyJhbGciOi..."
  }
}
GET /org/my/invitations — My pending invitations Auth

Headers: Authorization: Bearer <access_token>

Returns pending invitations for the authenticated user.

Response 200:

{
  "data": [
    {
      "id": "01961abc-...",
      "org_id": "01961def-...",
      "email": "user@example.com",
      "role": "member",
      "inviter_id": "01961ghi-...",
      "status": "pending",
      "expires_at": "2025-01-08T00:00:00Z",
      "created_at": "2025-01-01T00:00:00Z"
    }
  ]
}
POST /org/invitations/accept — Accept org invitation Public

No authentication required. The invitation token is the authorization. If the invited email has no account, provide name and password to create one.

Request:

{
  "token": "invitation-token-from-email",
  "name": "John Doe",
  "password": "securepassword123"
}

name and password are only required when the invited email has no existing account.

Response 200:

{
  "data": {
    "access_token": "eyJ...",
    "refresh_token": "eyJ...",
    "user": {
      "id": "01961abc-...",
      "email": "john@example.com",
      "name": "John Doe",
      "email_verified": true
    },
    "member": {
      "id": "01961def-...",
      "org_id": "01961ghi-...",
      "user_id": "01961abc-...",
      "role": "member",
      "joined_at": "2025-01-02T00:00:00Z"
    },
    "is_new_user": true
  }
}

The invitation mutation commits before authentication completion. The response may contain challenges instead of final credentials when the account requires 2FA. Consume that challenge response rather than retrying the already-accepted invitation.

POST /org/invitations/decline — Decline org invitation Public

No authentication required.

Request:

{"token": "invitation-token-from-email"}

Response 200:

{
  "data": null
}

Org-scoped routes (require org membership)

These routes require the org auth middleware, which verifies the user is a member of the specified organization.

GET /org/{orgId} — Get organization Auth Org

Headers: Authorization: Bearer <access_token>

Response 200:

{
  "data": {
    "id": "01961abc-...",
    "name": "Acme Inc",
    "slug": "acme-inc",
    "owner_id": "01961abc-...",
    "logo_url": "https://example.com/logo.png",
    "metadata": "{\"plan\": \"pro\"}",
    "active": true,
    "created_at": "2025-01-01T00:00:00Z",
    "updated_at": "2025-01-15T00:00:00Z"
  }
}
PUT /org/{orgId} — Update organization Auth Org

Headers: Authorization: Bearer <access_token>

At least one field must be provided.

Request:

{
  "name": "Acme Corp",
  "logo_url": "https://example.com/new-logo.png",
  "metadata": "{\"plan\": \"enterprise\"}"
}

Response 200:

{
  "data": {
    "id": "01961abc-...",
    "name": "Acme Corp",
    "slug": "acme-inc",
    "owner_id": "01961abc-...",
    "logo_url": "https://example.com/new-logo.png",
    "metadata": "{\"plan\": \"enterprise\"}",
    "active": true,
    "created_at": "2025-01-01T00:00:00Z",
    "updated_at": "2025-01-20T12:00:00Z"
  }
}
DELETE /org/{orgId} — Delete organization Auth Org

Headers: Authorization: Bearer <access_token>

Permanently deletes the organization. Requires owner role.

Response 200:

{
  "data": {
    "message": "Organization deleted"
  }
}
GET /org/{orgId}/members — List members Auth Org

Headers: Authorization: Bearer <access_token>

Response 200:

{
  "data": [
    {
      "id": "01961abc-...",
      "org_id": "01961def-...",
      "user_id": "01961ghi-...",
      "role": "owner",
      "joined_at": "2025-01-01T00:00:00Z",
      "user": {
        "id": "01961ghi-...",
        "name": "John Doe",
        "email": "john@example.com",
        "username": "johndoe",
        "avatar": "",
        "active": true,
        "email_verified": true,
        "created_at": "2025-01-01T00:00:00Z"
      }
    }
  ]
}
GET /org/{orgId}/members/{userId} — Get member Auth Org

Headers: Authorization: Bearer <access_token>

Returns a specific member with their user details. Same shape as a single item in the list above.

PUT /org/{orgId}/members/{userId} — Update member role Auth Org

Headers: Authorization: Bearer <access_token>

Request:

{"role": "admin"}

Valid roles: owner, admin, member.

Response 200:

{
  "data": {
    "message": "Member role updated"
  }
}
DELETE /org/{orgId}/members/{userId} — Remove member Auth Org

Headers: Authorization: Bearer <access_token>

Response 200:

{
  "data": {
    "message": "Member removed"
  }
}
POST /org/{orgId}/invite — Invite member Auth Org

Headers: Authorization: Bearer <access_token>

Request:

{
  "email": "newmember@example.com",
  "role": "member"
}

role is optional (defaults to member). Valid values: owner, admin, member.

Response 201:

{
  "data": {
    "id": "01961abc-...",
    "org_id": "01961def-...",
    "email": "newmember@example.com",
    "role": "member",
    "inviter_id": "01961ghi-...",
    "status": "pending",
    "expires_at": "2025-01-08T00:00:00Z",
    "created_at": "2025-01-01T00:00:00Z"
  }
}
GET /org/{orgId}/invitations — List invitations Auth Org

Headers: Authorization: Bearer <access_token>

Returns all invitations for the organization.

Response 200:

{
  "data": [
    {
      "id": "01961abc-...",
      "org_id": "01961def-...",
      "email": "newmember@example.com",
      "role": "member",
      "inviter_id": "01961ghi-...",
      "status": "pending",
      "expires_at": "2025-01-08T00:00:00Z",
      "created_at": "2025-01-01T00:00:00Z"
    }
  ]
}
DELETE /org/{orgId}/invitations/{invId} — Cancel invitation Auth Org

Headers: Authorization: Bearer <access_token>

Response 200:

{
  "data": {
    "message": "Invitation cancelled"
  }
}

Public Signing Keys

GET /.well-known/jwks.json — Public JWT verification keys

Returns a raw JSON Web Key Set with active and unexpired retained asymmetric public keys. It is not wrapped in data. An HS256-only configuration returns 404; HMAC secrets are never published. Successful responses set a one-hour cache lifetime. See Security for key rotation and custom-manager support.

Passkeys Module

Register passkeys.New(...) with an RP ID and allowed origins. See Passkeys for browser serialization and complete ceremony examples.

POST /passkeys/register/begin — Begin registration (Auth)

Returns data.challenge_token and data.public_key creation options. Authenticate the existing account, then call navigator.credentials.create with converted options.

POST /passkeys/register/finish — Finish registration (Auth)

Send challenge_token, optional name, and the actual browser-generated credential_response object. The record is consumed before verification, and a successful registration returns 201 with the new credential summary.

POST /passkeys/login/begin — Begin discoverable login

No email or user ID is required. Returns data.challenge_token and data.public_key request options for navigator.credentials.get.

POST /passkeys/login/finish — Finish login

Send challenge_token and the serialized assertion as credential_response. After origin, RP, identity, signature, and counter checks, shared completion returns credentials or challenges. Cookie transport follows AuthMode.

GET /passkeys — List owned passkeys (Auth)

Returns an array in data containing id, name, created_at, optional transports, and optional last_used_at. The current login path does not update last-used timestamps.

PUT /passkeys/{id} — Rename an owned passkey (Auth)

Send {"name":"Work laptop"}. Ownership is verified; success returns 200.

DELETE /passkeys/{id} — Delete an owned passkey (Auth)

Ownership is verified; success returns 200. Deleting a passkey does not delete the user account.

Passkey finish requests are one-use and purpose-bound. A failed finish needs a new begin request. credential_response must be a JSON object with base64url-encoded binary fields, not a stringified object. Current challenge expiry is capped at five minutes.


Authentication

Protected endpoints (marked with Auth) require:

Authorization: Bearer <access_token>

This header is the bearer example. Cookie or both mode can accept the configured access cookie instead. Conflicting sources are rejected; a refresh token, pending 2FA continuation, or passkey challenge is not an access credential. Both strategies check current user/version state, and session and selected-organization state where applicable.

Get credentials from /login or another enabled method. Password, OAuth, magic-link, passkey, and invitation completion may return data.challenges instead of final tokens. Send the continuation’s temp_token and a factor code to /2fa/verify-login; a user ID alone is never sufficient. Refresh via /refresh using the configured transport. Cookie-only JSON responses still include token fields.

Admin endpoints (marked with Admin) additionally require the is_super_admin flag on the user account.

Org endpoints (marked with Org) additionally require the user to be a member of the specified organization.

Edit this page on GitHub

On this page