openapi: 3.0.3
info:
  title: Authentication Service API
  description: "Centralized authentication for the UpStart Commerce platform. Issues short-lived\nJWT access tokens for consumers and management users, and rotates them via\nrefresh tokens.\n\n## Quick start\n\nAnonymous browsing needs no credentials — ask for a guest token:\n\n    curl -X POST https://api.upstartcommerce.com/api/v1/auth/guest \\\n      -H 'x-upstart-tenant: your-tenant'\n\nSigning a user in returns an access/refresh pair:\n\n    curl -X POST https://api.upstartcommerce.com/api/v1/auth/login \\\n      -H 'x-upstart-tenant: your-tenant' \\\n      -H 'Content-Type: application/json' \\\n      -d '{\"username\":\"user@example.com\",\"password\":\"…\"}'\n\nSend the access token on every subsequent call:\n\n    Authorization: Bearer <accessToken>\n\n## When login needs a second factor\n\n`/auth/login` does not always return tokens. If the account requires MFA it\nreturns `mfaRequired: true` with an `mfaChallengeId`, the delivery `mfaMethod`,\na masked destination and the challenge's own expiry — and no tokens. Complete it\nwith `/auth/mfa/verify`, or ask for a new code with `/auth/mfa/resend`. Treat\n`mfaRequired` as a branch in your login flow, not an error.\n\n## Token lifetimes and refresh\n\nAccess tokens last one hour and refresh tokens seven days by default. Both are\nenvironment-configurable, so read `expiresIn` from the response rather than\nhard-coding either.\n\nRefresh **rotates**: `/auth/refresh` returns a new pair and invalidates the one\nyou presented, so store the new refresh token every time. `/auth/logout` revokes\nit and ends the session.\n\n## Choosing a tenant and site\n\nEvery call carries an `x-upstart-tenant` header. Where a user belongs to more\nthan one tenant, `/auth/tenants` lists what they can reach — use it to drive a\ntenant switcher — and `/auth/refresh` can move an existing session to another\ntenant without re-authenticating. `/auth/sites` does the same for sites within\nthe active tenant.\n\n## How services consume the token\n\nTokens are deliberately thin (~500 bytes): a user id plus role references, not a\npermission list. Consumer traffic is validated on signature alone, so there is\nno per-request lookup. Management traffic resolves authorization separately\nagainst profile-permissions-svc, which is why granting a role takes effect\nwithout reissuing tokens.\n"
  version: 1.0.0
  contact:
    name: UpStart Commerce, Inc.
    email: support@upstartcommerce.com
    url: https://upstartcommerce.atlassian.net/servicedesk/customer/portal/1
  license:
    name: Proprietary
servers:
- url: https://api.upstartcommerce.com
  description: Production
tags:
- name: Authentication
  description: Sign in, issue and rotate tokens, and discover the tenants and sites a session can use.
paths:
  /api/v1/auth/guest:
    post:
      operationId: guestToken
      summary: Issue or refresh an anonymous consumer token
      description: "Issues a short-lived JWT access token for anonymous browsing without credentials.\n\nThe endpoint serves both new and returning guests through a single call:\n\n- **No cookie** (new visitor): creates a new guest session, returns access token,\n  sets `guest_session` HttpOnly cookie.\n- **Valid cookie** (returning visitor): rotates the cookie atomically, returns a\n  new access token with the same stable session identity.\n- **Unknown/expired cookie** (reuse conflict or expiry): silently creates a new\n  session (logs a warning server-side), returns access token, sets new cookie.\n\n**Access token** — short-lived JWT (1 hour), hold in JS memory, send on every API call.\n\n**Guest session cookie** — long-lived opaque UUID (30 days by default), stored as\n`HttpOnly; Secure; SameSite=Strict`, invisible to JavaScript.\n\n**JWT claims**: `sub = \"guest:<stableSessionId>\"`, `type = \"consumer\"`, `roles = []`,\n`tenant` from the `x-upstart-tenant` header. The `stableSessionId` never changes for the\nlifetime of the guest session and can be used as a consistent identity key by downstream\nservices (e.g. cart-svc).\n\n**Rate limiting**: requests are rate-limited by client IP (`guest:<ip>`).\n"
      tags:
      - Authentication
      parameters:
      - $ref: '#/components/parameters/TenantHeader'
      - name: Cookie
        in: header
        description: Optional `guest_session` cookie from a previous call
        schema:
          type: string
        example: guest_session=3fa85f64-5717-4562-b3fc-2c963f66afa6
      responses:
        '200':
          description: 'Guest access token issued. The `guest_session` cookie in the response replaces

            any previously presented cookie — always store the latest value.

            '
          headers:
            Set-Cookie:
              description: 'Rotated guest session cookie. Always `HttpOnly; Secure; SameSite=Strict`.

                '
              schema:
                type: string
              example: guest_session=7fd113e1-1d30-48d2-88e0-50ef573ecef0; Max-Age=2592000; Secure; HTTPOnly; SameSite=Strict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GuestTokenResponse'
              example:
                accessToken: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
                expiresIn: 3600
                tokenType: Bearer
        '400':
          description: Missing required `x-upstart-tenant` header
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: tenant_required
                message: x-upstart-tenant header is required
                code: BAD_REQUEST
        '429':
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: rate_limited
                message: Too many guest token requests. Retry in 900s.
                code: RATE_LIMITED
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/auth/login:
    post:
      operationId: login
      summary: Authenticate user and issue tokens
      description: 'Validates user credentials and issues JWT access and refresh tokens.


        Tenant context is resolved in priority order:

        1. `Authorization: Bearer <guest-token>` — tenant extracted from the guest token''s `tenant` claim (preferred)

        2. `x-upstart-tenant` header — explicit tenant selection (fallback, deprecated)


        The guest token is issued by the `/api/v1/auth/guest` endpoint and carries the tenant

        (and site) context for the current browsing session. Clients should always present the

        guest token; the `x-upstart-tenant` header is retained for backwards compatibility only.


        The token includes `siteRoles` containing all sites the user can access.

        Site context is determined at runtime via the `x-upstart-site` header on

        subsequent API calls, not at login time.


        The response includes both an access token (short-lived, for API calls) and

        a refresh token (long-lived, for obtaining new access tokens).

        '
      tags:
      - Authentication
      parameters:
      - name: Authorization
        in: header
        description: 'Guest token issued by `/api/v1/auth/guest`, carrying tenant (and site) context for the session.

          The `tenant` claim is extracted and used as the active tenant for this login.

          Falls back to `x-upstart-tenant` if absent or if the token cannot be parsed.

          '
        schema:
          type: string
          pattern: ^Bearer .+$
        example: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
      - $ref: '#/components/parameters/TenantHeader'
      requestBody:
        description: User credentials for authentication
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
            examples:
              consumerLogin:
                summary: Consumer login
                value:
                  username: customer@example.com
                  password: securePassword123
                  tokenType: consumer
              managementLogin:
                summary: Management login
                value:
                  username: admin@company.com
                  password: adminPassword456
                  tokenType: management
      responses:
        '200':
          description: 'Authentication result. Either:

            - Successful authentication with tokens (mfaRequired=false or absent)

            - MFA challenge required (mfaRequired=true)

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
              examples:
                successfulLogin:
                  summary: Successful login (no MFA)
                  value:
                    mfaRequired: false
                    accessToken: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
                    refreshToken: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
                    expiresIn: 3600
                    tokenType: Bearer
                mfaRequired:
                  summary: MFA verification required
                  value:
                    mfaRequired: true
                    mfaChallengeId: ch_abc123xyz
                    mfaMethod: totp
                    mfaExpiresIn: 300
        '400':
          description: Bad request - invalid JSON, missing fields, or tenant selection required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalidRequest:
                  summary: Invalid request body
                  value:
                    error: invalid_request
                    message: Failed to read request body
                    code: BAD_REQUEST
                tenantRequired:
                  summary: Tenant selection required
                  value:
                    error: tenant_required
                    message: Multiple tenants available; tenant selection required via x-upstart-tenant header
                    code: TENANT_REQUIRED
                invalidTokenType:
                  summary: Invalid token type
                  value:
                    error: invalid_request
                    message: Invalid token type. Must be 'consumer' or 'management'
                    code: BAD_REQUEST
        '401':
          description: Invalid credentials
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: unauthorized
                message: Invalid username or password
                code: UNAUTHORIZED
        '403':
          description: Forbidden - unauthorized tenant or no authorized tenants
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                unauthorizedTenant:
                  summary: Unauthorized tenant
                  value:
                    error: unauthorized_tenant
                    message: 'User is not authorized for tenant: tenant-xyz'
                    code: UNAUTHORIZED_TENANT
                noAuthorizedTenants:
                  summary: No authorized tenants
                  value:
                    error: forbidden
                    message: User has no authorized tenants
                    code: FORBIDDEN
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/auth/logout:
    post:
      operationId: logout
      summary: Logout and revoke refresh token
      description: 'Revokes the provided refresh token, ending the user''s session.


        After logout:

        - The refresh token can no longer be used to obtain new access tokens

        - Existing access tokens remain valid until they expire (stateless validation)

        - For immediate access revocation, services should implement token blacklisting

        '
      tags:
      - Authentication
      requestBody:
        description: Refresh token to revoke
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LogoutRequest'
            example:
              refreshToken: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
      responses:
        '204':
          description: Logout successful - refresh token revoked
        '400':
          description: Invalid request body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: invalid_request
                message: Failed to read request body
                code: BAD_REQUEST
        '401':
          description: Invalid or already revoked refresh token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: unauthorized
                message: Invalid or revoked refresh token
                code: UNAUTHORIZED
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/auth/mfa/resend:
    post:
      operationId: resendMfa
      summary: Resend SMS OTP code
      description: 'Resends the SMS OTP code for an existing MFA challenge.

        Only available for SMS MFA method.

        '
      tags:
      - Authentication
      requestBody:
        description: Challenge ID for resending OTP
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MfaResendRequest'
            example:
              challengeId: ch_abc123xyz
      responses:
        '200':
          description: OTP resent successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MfaChallengeResponse'
              example:
                challengeId: ch_abc123xyz
                method: sms
                expiresIn: 300
                maskedPhoneNumber: '***1234'
        '400':
          description: Invalid request or not SMS MFA
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: invalid_request
                message: Resend only available for SMS MFA
                code: BAD_REQUEST
        '401':
          description: Challenge expired
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: unauthorized
                message: MFA challenge has expired
                code: UNAUTHORIZED
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/auth/mfa/verify:
    post:
      operationId: verifyMfa
      summary: Verify MFA code and complete authentication
      description: 'Verifies the MFA code (TOTP, SMS OTP, or backup code) and completes the

        authentication process started by the login endpoint.


        Upon successful verification, returns access and refresh tokens.

        '
      tags:
      - Authentication
      requestBody:
        description: MFA verification details
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MfaVerifyRequest'
            example:
              challengeId: ch_abc123xyz
              code: '123456'
      responses:
        '200':
          description: MFA verification successful - tokens issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
              example:
                accessToken: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
                refreshToken: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
                expiresIn: 3600
                tokenType: Bearer
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: invalid_request
                message: Challenge ID and code are required
                code: BAD_REQUEST
        '401':
          description: Invalid MFA code or challenge expired
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalidCode:
                  summary: Invalid MFA code
                  value:
                    error: unauthorized
                    message: Invalid MFA code. 4 attempts remaining.
                    code: UNAUTHORIZED
                challengeExpired:
                  summary: Challenge expired
                  value:
                    error: unauthorized
                    message: MFA challenge has expired. Please login again.
                    code: UNAUTHORIZED
        '403':
          description: Account locked due to too many failed attempts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: forbidden
                message: Account locked due to too many failed MFA attempts
                code: FORBIDDEN
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/auth/refresh:
    post:
      operationId: refreshToken
      summary: Refresh access token
      description: 'Exchange a valid refresh token for a new access token and refresh token pair.


        **Token Rotation**: Each refresh operation issues a new refresh token and

        invalidates the previous one. This limits the window of exposure if a

        refresh token is compromised.


        **Tenant Switching**: Optionally switch to a different authorized tenant

        by including the `tenant` parameter. The new tenant must be in the user''s

        list of authorized tenants.

        '
      tags:
      - Authentication
      requestBody:
        description: Refresh token for obtaining new access token
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefreshRequest'
            examples:
              basicRefresh:
                summary: Basic token refresh
                value:
                  refreshToken: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
              refreshWithTenantSwitch:
                summary: Refresh with tenant switch
                value:
                  refreshToken: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
                  tenant: tenant-456
      responses:
        '200':
          description: Token refresh successful
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
              example:
                accessToken: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
                refreshToken: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
                expiresIn: 3600
                tokenType: Bearer
        '400':
          description: Invalid request body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: invalid_request
                message: Failed to read request body
                code: BAD_REQUEST
        '401':
          description: Invalid, expired, or revoked refresh token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalidToken:
                  summary: Invalid token
                  value:
                    error: unauthorized
                    message: Invalid refresh token
                    code: UNAUTHORIZED
                expiredToken:
                  summary: Expired token
                  value:
                    error: unauthorized
                    message: Refresh token has expired
                    code: UNAUTHORIZED
                revokedToken:
                  summary: Revoked token
                  value:
                    error: unauthorized
                    message: Refresh token has been revoked
                    code: UNAUTHORIZED
        '403':
          description: Unauthorized tenant switch
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: unauthorized_tenant
                message: 'User is not authorized for tenant: tenant-xyz'
                code: UNAUTHORIZED_TENANT
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/auth/sites:
    get:
      operationId: getSites
      summary: Get available sites for current session
      description: 'Returns the current active site and list of all authorized sites

        for the user associated with the provided refresh token within the current tenant.


        Use this endpoint to populate a site switcher UI.


        **Authentication**: Provide the refresh token via either:

        - `Authorization: Bearer <refresh-token>` header

        - `refresh_token` query parameter

        '
      tags:
      - Authentication
      parameters:
      - name: Authorization
        in: header
        description: Bearer token containing the refresh token
        schema:
          type: string
          pattern: ^Bearer .+$
        example: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
      - name: refresh_token
        in: query
        description: Alternative way to provide refresh token (if not using Authorization header)
        schema:
          type: string
        example: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
      responses:
        '200':
          description: Site information retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SiteInfoResponse'
              example:
                availableSites:
                - site-123
                - site-456
        '400':
          description: Refresh token not provided
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: invalid_request
                message: Refresh token required via Authorization header or refresh_token query parameter
                code: BAD_REQUEST
        '401':
          description: Invalid or revoked refresh token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: unauthorized
                message: Invalid or revoked refresh token
                code: UNAUTHORIZED
        '500':
          $ref: '#/components/responses/InternalServerError'
  /api/v1/auth/tenants:
    get:
      operationId: getTenants
      summary: Get available tenants for current session
      description: 'Returns the current active tenant and list of all authorized tenants

        for the user associated with the provided refresh token.


        Use this endpoint to populate a tenant switcher UI.


        **Authentication**: Provide the refresh token via either:

        - `Authorization: Bearer <refresh-token>` header

        - `refresh_token` query parameter

        '
      tags:
      - Authentication
      parameters:
      - name: Authorization
        in: header
        description: Bearer token containing the refresh token
        schema:
          type: string
          pattern: ^Bearer .+$
        example: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
      - name: refresh_token
        in: query
        description: Alternative way to provide refresh token (if not using Authorization header)
        schema:
          type: string
        example: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
      responses:
        '200':
          description: Tenant information retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenantInfoResponse'
              example:
                currentTenant: tenant-123
                availableTenants:
                - tenant-123
                - tenant-456
                - tenant-789
        '400':
          description: Refresh token not provided
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: invalid_request
                message: Refresh token required via Authorization header or refresh_token query parameter
                code: BAD_REQUEST
        '401':
          description: Invalid or revoked refresh token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: unauthorized
                message: Invalid or revoked refresh token
                code: UNAUTHORIZED
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  parameters:
    TenantHeader:
      name: x-upstart-tenant
      in: header
      description: 'Tenant identifier for multi-tenant authentication.


        Required when user is authorized for multiple tenants.

        Optional when user has exactly one authorized tenant (auto-selected).

        '
      schema:
        type: string
      example: tenant-123
  schemas:
    AuthResponse:
      type: object
      description: Successful authentication response containing tokens
      required:
      - accessToken
      - refreshToken
      - expiresIn
      - tokenType
      properties:
        accessToken:
          type: string
          description: 'JWT access token for API authorization.


            Include in API requests as: `Authorization: Bearer <accessToken>`


            **Claims**:

            - `sub`: User ID (UUID)

            - `type`: Token type (consumer/management)

            - `roles`: List of role identifiers

            - `tenant`: Active tenant ID

            - `iat`: Issued at (Unix timestamp)

            - `exp`: Expiration (Unix timestamp)

            - `iss`: Issuer (auth-svc)

            '
          example: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NTBl...
        refreshToken:
          type: string
          description: 'Refresh token for obtaining new access tokens.


            Store securely (e.g., httpOnly cookie or secure storage).

            Use with `/api/v1/auth/refresh` when access token expires.

            '
          example: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NTBl...
        expiresIn:
          type: integer
          format: int64
          description: Access token validity duration in seconds
          example: 3600
        tokenType:
          type: string
          description: Token type (always "Bearer")
          example: Bearer
    ErrorResponse:
      type: object
      description: Standard error response format
      required:
      - error
      - message
      - code
      properties:
        error:
          type: string
          description: 'Error type identifier:

            - `invalid_request`: Malformed request or missing required fields

            - `unauthorized`: Invalid credentials or token

            - `account_not_verified`: Account exists but email/phone not verified

            - `session_expired`: Maximum session lifetime exceeded, full re-login required

            - `unauthorized_tenant`: User not authorized for requested tenant

            - `tenant_required`: Multi-tenant user must specify tenant

            - `unauthorized_site`: User not authorized for requested site

            - `site_required`: Site context required for this operation

            - `no_authorized_sites`: User has no authorized sites

            - `forbidden`: Access denied (e.g., no authorized tenants)

            - `rate_limited`: Too many requests, try again later

            - `account_locked`: Account temporarily locked due to failed attempts

            - `internal_error`: Server-side error

            '
          example: unauthorized
        message:
          type: string
          description: Human-readable error description
          example: Invalid username or password
        code:
          type: string
          description: HTTP status code name
          enum:
          - BAD_REQUEST
          - UNAUTHORIZED
          - UNAUTHORIZED_TENANT
          - TENANT_REQUIRED
          - FORBIDDEN
          - RATE_LIMITED
          - ACCOUNT_LOCKED
          - INTERNAL_SERVER_ERROR
          example: UNAUTHORIZED
    GuestTokenResponse:
      type: object
      description: 'Response from `POST /api/v1/auth/guest`. The access token is returned in the body

        (store in JS memory, send on every API call). The opaque session token is set as an

        `HttpOnly` cookie by the server and is never exposed in the response body.

        '
      required:
      - accessToken
      - expiresIn
      - tokenType
      properties:
        accessToken:
          type: string
          description: 'JWT access token for anonymous API calls.


            Include in requests as: `Authorization: Bearer <accessToken>`


            **Claims**:

            - `sub`: `guest:<stableSessionId>` — stable across session rotations

            - `type`: `consumer`

            - `roles`: `["customer.anonymous"]` by default (configurable via `guest-session.guestRoles`)

            - `tenant`: active tenant ID

            - `iat` / `exp` / `iss`: standard JWT fields

            '
          example: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
        expiresIn:
          type: integer
          format: int64
          description: Seconds until the access token expires
          example: 3600
        tokenType:
          type: string
          description: Token type (always "Bearer")
          example: Bearer
    LoginRequest:
      type: object
      description: Request body for user authentication
      required:
      - username
      - password
      - tokenType
      properties:
        username:
          type: string
          description: User's username or email address
          example: user@example.com
        password:
          type: string
          format: password
          description: User's password
          example: securePassword123
        tokenType:
          type: string
          enum:
          - consumer
          - management
          description: 'Type of authentication session:

            - `consumer`: High-volume consumer traffic with fixed role mapping

            - `management`: Admin/management traffic with dynamic permission queries

            '
          example: consumer
    LoginResponse:
      type: object
      description: 'Login response that indicates either successful authentication or MFA requirement.


        When mfaRequired is false or absent: accessToken, refreshToken, expiresIn, tokenType are present

        When mfaRequired is true: mfaChallengeId, mfaMethod, mfaExpiresIn are present

        '
      required:
      - mfaRequired
      properties:
        mfaRequired:
          type: boolean
          description: Whether MFA verification is required before tokens are issued
          example: false
        accessToken:
          type: string
          description: JWT access token (present when mfaRequired is false)
          example: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
        refreshToken:
          type: string
          description: Refresh token (present when mfaRequired is false)
          example: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
        expiresIn:
          type: integer
          format: int64
          description: Access token validity in seconds (present when mfaRequired is false)
          example: 3600
        tokenType:
          type: string
          description: Token type, always "Bearer" (present when mfaRequired is false)
          example: Bearer
        mfaChallengeId:
          type: string
          description: MFA challenge ID for verification (present when mfaRequired is true)
          example: ch_abc123xyz
        mfaMethod:
          type: string
          enum:
          - totp
          - sms
          description: MFA method for verification (present when mfaRequired is true)
          example: totp
        mfaExpiresIn:
          type: integer
          format: int64
          description: Seconds until MFA challenge expires (present when mfaRequired is true)
          example: 300
        mfaMaskedPhoneNumber:
          type: string
          description: Masked phone number for SMS MFA (present when mfaMethod is sms)
          example: '***1234'
    LogoutRequest:
      type: object
      description: Request body for logout
      required:
      - refreshToken
      properties:
        refreshToken:
          type: string
          description: Refresh token to revoke
          example: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
    MfaChallengeResponse:
      type: object
      description: MFA challenge information
      required:
      - challengeId
      - method
      - expiresIn
      properties:
        challengeId:
          type: string
          description: Unique identifier for the MFA challenge
          example: ch_abc123xyz
        method:
          type: string
          enum:
          - totp
          - sms
          description: MFA method
          example: sms
        expiresIn:
          type: integer
          format: int64
          description: Seconds until challenge expires
          example: 300
        maskedPhoneNumber:
          type: string
          description: Masked phone number (for SMS only)
          example: '***1234'
    MfaResendRequest:
      type: object
      description: Request to resend SMS OTP code
      required:
      - challengeId
      properties:
        challengeId:
          type: string
          description: MFA challenge ID from login response
          example: ch_abc123xyz
    MfaVerifyRequest:
      type: object
      description: Request to verify MFA code and complete authentication
      required:
      - challengeId
      - code
      properties:
        challengeId:
          type: string
          description: MFA challenge ID from login response
          example: ch_abc123xyz
        code:
          type: string
          description: MFA verification code (TOTP, SMS OTP, or backup code)
          minLength: 6
          maxLength: 10
          example: '123456'
    RefreshRequest:
      type: object
      description: Request body for token refresh
      required:
      - refreshToken
      properties:
        refreshToken:
          type: string
          description: Valid refresh token from previous login or refresh
          example: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
        tenant:
          type: string
          description: 'Optional tenant to switch to during refresh.

            Must be in the user''s list of authorized tenants.

            '
          example: tenant-456
        site:
          type: string
          description: 'Optional site to switch to during refresh.

            Must be in the user''s list of authorized sites for the target tenant.

            '
          example: site-123
    SiteInfoResponse:
      type: object
      description: 'Information about user''s site access within current tenant.

        Site context is determined at runtime via the x-upstart-site header,

        not embedded in the token.

        '
      required:
      - availableSites
      properties:
        availableSites:
          type: array
          description: List of all sites the user is authorized to access within the current tenant
          items:
            type: string
          example:
          - site-123
          - site-456
    TenantInfoResponse:
      type: object
      description: Information about user's tenant access
      required:
      - currentTenant
      - availableTenants
      properties:
        currentTenant:
          type: string
          description: Currently active tenant for this session
          example: tenant-123
        availableTenants:
          type: array
          description: List of all tenants the user is authorized to access
          items:
            type: string
          example:
          - tenant-123
          - tenant-456
          - tenant-789
  responses:
    InternalServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            tokenGenerationFailed:
              summary: Token generation failed
              value:
                error: internal_error
                message: Token generation failed
                code: INTERNAL_SERVER_ERROR
            serviceError:
              summary: Upstream service error
              value:
                error: internal_error
                message: Failed to communicate with authentication backend
                code: INTERNAL_SERVER_ERROR
