openapi: 3.1.0
info:
  title: Qeet ID — Authentication & Access API
  version: 0.2.0
  description: |-
    Qeet ID identity platform — full HTTP surface.

    This document is generated to cover EVERY route mounted by the chi router (internal/http/router.go) and is guarded by internal/http/openapi_coverage_test.go, which fails CI if any mounted route is undocumented. Launch-critical families (Auth, OIDC/OAuth, SCIM, SAML, Users, Tenants, RBAC, API Keys) carry fully-described request/response bodies; remaining admin CRUD routes carry accurate path/method/params/security with summarized bodies (noted in each operation's description).

    Auth modes: `bearerAuth` (user/service JWT), `apiKeyAuth` (`Authorization: ApiKey <key>`), `scimBearer` (per-tenant SCIM token), `clientCredentials` (OAuth M2M), and `ssoCookie` (hosted-login SSO session).
servers:
  - url: https://api.id.qeet.in
    description: Production
  - url: https://api.id.staging.qeet.in
    description: Staging
  - url: http://localhost:4001
    description: Local (docker compose)
tags:
  - name: Auth
    description: Login, signup, refresh, sessions, hosted-login SSO cookie.
  - name: RBAC
    description: Roles, permissions, assignments, and authorization checks.
  - name: MFA
    description: TOTP and OTP (email/SMS) second factors plus recovery codes.
  - name: Passkeys
    description: WebAuthn passkey registration and passwordless login.
  - name: IP Rules
    description: Per-tenant IP allow/deny CIDR rules.
  - name: Auth Policy
    description: Per-tenant password rules and allowed login methods.
  - name: Policy
    description: Per-tenant security policy.
paths:
  /v1/auth/forgot-password:
    post:
      operationId: post_v1_auth_forgot_password
      summary: Forgot password
      tags:
        - Auth
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
              properties:
                email:
                  type: string
                  format: email
      responses:
        '200':
          description: Accepted (always, to avoid account enumeration); returns a neutral confirmation message
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/auth/login:
    post:
      operationId: post_v1_auth_login
      summary: Sign in
      tags:
        - Auth
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: |
            Either a full token pair (no MFA) or, when the user has a second
            factor enrolled, an MFA challenge `{mfa_required:true, mfa_token,
            methods[]}` to complete at POST /v1/auth/mfa.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/TokenPair'
                  - type: object
                    properties:
                      mfa_required:
                        type: boolean
                      mfa_token:
                        type: string
                      methods:
                        type: array
                        items:
                          type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/auth/mfa:
    post:
      operationId: post_v1_auth_mfa
      summary: Complete MFA login
      description: Exchanges the mfa_token from /v1/auth/login plus a TOTP or recovery code for a token pair.
      tags:
        - Auth
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - mfa_token
                - code
              properties:
                mfa_token:
                  type: string
                code:
                  type: string
                  description: A TOTP code or a one-time recovery code.
      responses:
        '200':
          description: Second factor verified; token pair issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenPair'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/auth/logout:
    post:
      operationId: post_v1_auth_logout
      summary: Logout
      tags:
        - Auth
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      responses:
        '200':
          description: Signed out
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/auth/magic-link/consume:
    post:
      operationId: post_v1_auth_magic_link_consume
      summary: Magic link consume
      tags:
        - Auth
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - token
              properties:
                token:
                  type: string
      responses:
        '200':
          description: Token pair
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenPair'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/auth/magic-link/start:
    post:
      operationId: post_v1_auth_magic_link_start
      summary: Magic link start
      tags:
        - Auth
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
              properties:
                email:
                  type: string
                  format: email
      responses:
        '200':
          description: Accepted (always, to avoid account enumeration); returns a neutral confirmation message
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/auth/me:
    get:
      operationId: get_v1_auth_me
      summary: Me
      tags:
        - Auth
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Current principal
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Principal'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/auth/refresh:
    post:
      operationId: post_v1_auth_refresh
      summary: Refresh
      tags:
        - Auth
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefreshRequest'
      responses:
        '200':
          description: Rotated token pair
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenPair'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/auth/reset-password:
    post:
      operationId: post_v1_auth_reset_password
      summary: Reset password
      tags:
        - Auth
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - token
                - new_password
              properties:
                token:
                  type: string
                new_password:
                  type: string
                  minLength: 8
                  maxLength: 256
      responses:
        '200':
          description: Password reset; returns a confirmation message
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/auth/session:
    delete:
      operationId: delete_v1_auth_session
      summary: Destroy SSO session (logout)
      tags:
        - Auth
      security: []
      responses:
        '204':
          description: Session revoked, cookie cleared
    post:
      operationId: post_v1_auth_session
      summary: Create SSO session
      tags:
        - Auth
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: |
            Either the SSO cookie was set (`{user_id}`) or, when the user has a
            second factor enrolled, an MFA challenge `{mfa_required:true,
            mfa_token, methods[]}` to complete at POST /v1/auth/session/mfa
            (no cookie is set until then).
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      user_id:
                        type: string
                        format: uuid
                  - type: object
                    properties:
                      mfa_required:
                        type: boolean
                      mfa_token:
                        type: string
                      methods:
                        type: array
                        items:
                          type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/auth/session/mfa:
    post:
      operationId: post_v1_auth_session_mfa
      summary: Complete SSO session MFA
      description: Exchanges the mfa_token from POST /v1/auth/session plus a TOTP or recovery code for the SSO cookie (qe_ls).
      tags:
        - Auth
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - mfa_token
                - code
              properties:
                mfa_token:
                  type: string
                code:
                  type: string
                  description: A TOTP code or a one-time recovery code.
                remember:
                  type: boolean
                  description: Trust this device so future logins can skip the second factor (adaptive MFA). Honoured only when the tenant has enabled remember-device; sets the qe_td cookie.
      responses:
        '200':
          description: Second factor verified; SSO cookie set
          content:
            application/json:
              schema:
                type: object
                properties:
                  user_id:
                    type: string
                    format: uuid
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/auth/register:
    post:
      operationId: post_v1_auth_register
      summary: Register (hosted, B2C)
      description: |
        Creates a new end-user in the client's tenant and sets the SSO cookie
        (qe_ls) so they continue into the OAuth authorize flow. Gated by the
        tenant's self_registration_enabled policy (403 when disabled).
        Enumeration-safe: an existing email returns a neutral 422.
      tags:
        - Auth
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - tenant_id
                - email
                - password
              properties:
                tenant_id:
                  type: string
                  format: uuid
                email:
                  type: string
                  format: email
                password:
                  type: string
                display_name:
                  type: string
      responses:
        '201':
          description: User created and SSO cookie set
          content:
            application/json:
              schema:
                type: object
                properties:
                  user_id:
                    type: string
                    format: uuid
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/auth/sessions:
    get:
      operationId: get_v1_auth_sessions
      summary: List sessions
      tags:
        - Auth
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Active sessions
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/Session'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/auth/sessions/{id}:
    delete:
      operationId: delete_v1_auth_sessions_ById
      summary: Revoke session
      tags:
        - Auth
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Session revoked
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/auth/signup:
    post:
      operationId: post_v1_auth_signup
      summary: Sign up
      tags:
        - Auth
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignupRequest'
      responses:
        '201':
          description: Created; token pair + user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SignupResponse'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/auth/switch-tenant:
    post:
      operationId: post_v1_auth_switch_tenant
      summary: Switch tenant
      tags:
        - Auth
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - tenant_id
              properties:
                tenant_id:
                  type: string
                  format: uuid
      responses:
        '200':
          description: Token pair scoped to the target tenant
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenPair'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/check:
    get:
      operationId: get_v1_check
      summary: Permission check
      tags:
        - RBAC
      parameters:
        - in: query
          name: user_id
          required: true
          schema:
            type: string
            format: uuid
        - in: query
          name: tenant_id
          required: true
          schema:
            type: string
            format: uuid
        - in: query
          name: permission
          required: true
          schema:
            type: string
        - in: query
          name: explain
          required: false
          description: When "true", return the full authorization trace (allowed, the grant path(s), and a reason on denial) instead of the bare {allowed} object.
          schema:
            type: string
            enum:
              - 'true'
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Decision. The default shape is {allowed}. With explain=true the response is the Explanation object (allowed, paths, reason).
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      allowed:
                        type: boolean
                  - $ref: '#/components/schemas/AuthzExplanation'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/mfa/otp/factors:
    get:
      operationId: get_v1_mfa_otp_factors
      summary: List OTP factors
      tags:
        - MFA
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    post:
      operationId: post_v1_mfa_otp_factors
      summary: Add OTP factor
      tags:
        - MFA
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/mfa/otp/factors/{id}:
    delete:
      operationId: delete_v1_mfa_otp_factors_ById
      summary: Delete OTP factor
      tags:
        - MFA
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '204':
          description: No Content
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/mfa/otp/factors/{id}/challenge:
    post:
      operationId: post_v1_mfa_otp_factors_ById_challenge
      summary: Challenge OTP factor
      tags:
        - MFA
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/mfa/otp/factors/{id}/confirm:
    post:
      operationId: post_v1_mfa_otp_factors_ById_confirm
      summary: Confirm OTP factor
      tags:
        - MFA
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/mfa/otp/verify:
    post:
      operationId: post_v1_mfa_otp_verify
      summary: Verify OTP
      tags:
        - MFA
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/mfa/recovery-codes:
    get:
      operationId: get_v1_mfa_recovery_codes
      summary: Recovery codes status
      tags:
        - MFA
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/mfa/recovery-codes/regenerate:
    post:
      operationId: post_v1_mfa_recovery_codes_regenerate
      summary: Regenerate recovery codes (step-up)
      tags:
        - MFA
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/mfa/step-up/status:
    get:
      operationId: get_v1_mfa_step_up_status
      summary: Step-up status
      tags:
        - MFA
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      description: Reports whether the principal completed a second-factor verification within the step-up window, and when.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/mfa/totp:
    delete:
      operationId: delete_v1_mfa_totp
      summary: Disable TOTP
      tags:
        - MFA
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '204':
          description: No Content
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/mfa/totp/enroll/confirm:
    post:
      operationId: post_v1_mfa_totp_enroll_confirm
      summary: Confirm TOTP enrollment
      tags:
        - MFA
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/mfa/totp/enroll/start:
    post:
      operationId: post_v1_mfa_totp_enroll_start
      summary: Start TOTP enrollment
      tags:
        - MFA
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/mfa/totp/verify:
    post:
      operationId: post_v1_mfa_totp_verify
      summary: Verify TOTP
      tags:
        - MFA
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/mfa/webauthn/challenge:
    post:
      operationId: post_v1_mfa_webauthn_challenge
      summary: WebAuthn 2FA challenge
      tags:
        - MFA
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      description: Starts a WebAuthn assertion ceremony over the authenticated user's existing passkey credentials, used as a second factor (no token is issued).
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '501':
          description: WebAuthn second factor not enabled
  /v1/mfa/webauthn/verify:
    post:
      operationId: post_v1_mfa_webauthn_verify
      summary: WebAuthn 2FA verify
      tags:
        - MFA
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Verifies a WebAuthn assertion against the user's registered credentials and records a step-up verification on success.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '501':
          description: WebAuthn second factor not enabled
  /v1/passkeys:
    get:
      operationId: get_v1_passkeys
      summary: List passkeys
      tags:
        - Passkeys
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/passkeys/login/begin:
    post:
      operationId: post_v1_passkeys_login_begin
      summary: Login begin
      tags:
        - Passkeys
      security: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/passkeys/login/finish:
    post:
      operationId: post_v1_passkeys_login_finish
      summary: Login finish
      tags:
        - Passkeys
      security: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/passkeys/register/begin:
    post:
      operationId: post_v1_passkeys_register_begin
      summary: Register begin
      tags:
        - Passkeys
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/passkeys/register/finish:
    post:
      operationId: post_v1_passkeys_register_finish
      summary: Register finish
      tags:
        - Passkeys
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/passkeys/{id}:
    delete:
      operationId: delete_v1_passkeys_ById
      summary: Delete passkey
      tags:
        - Passkeys
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '204':
          description: No Content
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/permissions:
    get:
      operationId: get_v1_permissions
      summary: List permissions
      tags:
        - RBAC
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Permissions
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/Permission'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/roles/{roleID}/permissions/{permID}:
    delete:
      operationId: delete_v1_roles_ByRoleID_permissions_ByPermID
      summary: Revoke permission from role
      tags:
        - RBAC
      parameters:
        - name: roleID
          in: path
          required: true
          schema:
            type: string
        - name: permID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '204':
          description: Revoked
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    post:
      operationId: post_v1_roles_ByRoleID_permissions_ByPermID
      summary: Grant permission to role
      tags:
        - RBAC
      parameters:
        - name: roleID
          in: path
          required: true
          schema:
            type: string
        - name: permID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      responses:
        '204':
          description: Granted
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/tenants/{tenantID}/auth-policy:
    get:
      operationId: get_v1_tenants_ByTenantID_auth_policy
      summary: Get auth policy
      tags:
        - Auth Policy
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    put:
      operationId: put_v1_tenants_ByTenantID_auth_policy
      summary: Update auth policy
      tags:
        - Auth Policy
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/tenants/{tenantID}/groups/{groupID}/roles:
    get:
      operationId: get_v1_tenants_ByTenantID_groups_ByGroupID_roles
      summary: List group roles
      tags:
        - RBAC
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
        - name: groupID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Roles granted to the group
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/GroupRole'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /v1/tenants/{tenantID}/groups/{groupID}/roles/{roleID}:
    post:
      operationId: post_v1_tenants_ByTenantID_groups_ByGroupID_roles_ByRoleID
      summary: Grant role to group
      description: Grant a role to a group. Every member of the group inherits the role's permissions (effective permissions = direct user grants UNION group-derived grants). The role and group must belong to the tenant.
      tags:
        - RBAC
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
        - name: groupID
          in: path
          required: true
          schema:
            type: string
        - name: roleID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '204':
          description: Granted
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: delete_v1_tenants_ByTenantID_groups_ByGroupID_roles_ByRoleID
      summary: Revoke role from group
      tags:
        - RBAC
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
        - name: groupID
          in: path
          required: true
          schema:
            type: string
        - name: roleID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '204':
          description: Revoked
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/tenants/{tenantID}/security/bots:
    get:
      operationId: get_v1_tenants_ByTenantID_security_bots
      summary: Bot telemetry
      tags:
        - Threats
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Recent bot verdicts and stats
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /v1/tenants/{tenantID}/security/risk-settings:
    get:
      operationId: get_v1_tenants_ByTenantID_security_risk_settings
      summary: Get risk settings
      description: Returns the medium/high risk-score thresholds and the level at which step-up MFA is forced even for a trusted device.
      tags:
        - Threats
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Risk settings
          content:
            application/json:
              schema:
                type: object
                properties:
                  medium_threshold:
                    type: number
                  high_threshold:
                    type: number
                  force_mfa_at_level:
                    type: string
                    enum:
                      - medium
                      - high
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    put:
      operationId: put_v1_tenants_ByTenantID_security_risk_settings
      summary: Update risk settings
      tags:
        - Threats
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                medium_threshold:
                  type: number
                high_threshold:
                  type: number
                force_mfa_at_level:
                  type: string
                  enum:
                    - medium
                    - high
      responses:
        '200':
          description: Updated settings
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/tenants/{tenantID}/security/bots/settings:
    get:
      operationId: get_v1_tenants_ByTenantID_security_bots_settings
      summary: Get bot settings
      tags:
        - Threats
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Bot-detection settings
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    put:
      operationId: put_v1_tenants_ByTenantID_security_bots_settings
      summary: Update bot settings
      tags:
        - Threats
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        '200':
          description: Updated settings
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/tenants/{tenantID}/security/anomalies:
    get:
      operationId: get_v1_tenants_ByTenantID_security_anomalies
      summary: List anomalies
      tags:
        - Threats
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Recent anomalies
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /v1/tenants/{tenantID}/security/anomalies/summary:
    get:
      operationId: get_v1_tenants_ByTenantID_security_anomalies_summary
      summary: Anomaly summary
      tags:
        - Threats
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Open/resolved/affected/high-severity counts
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /v1/tenants/{tenantID}/security/anomalies/{id}/resolve:
    post:
      operationId: post_v1_tenants_ByTenantID_security_anomalies_ById_resolve
      summary: Resolve anomaly
      tags:
        - Threats
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Anomaly resolved
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/tenants/{tenantID}/relation-tuples:
    get:
      operationId: get_v1_tenants_ByTenantID_relation_tuples
      summary: List relation tuples
      tags:
        - ReBAC
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
        - name: object
          in: query
          required: true
          schema:
            type: string
          description: Object ref "type:id", e.g. document:readme
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Tuples on the object
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      operationId: post_v1_tenants_ByTenantID_relation_tuples
      summary: Write relation tuple
      tags:
        - ReBAC
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - object
                - relation
                - subject
              properties:
                object:
                  type: string
                  description: '"type:id"'
                relation:
                  type: string
                subject:
                  type: string
                  description: '"user:<id>" or "type:id#relation" (userset)'
      responses:
        '201':
          description: Tuple written
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/tenants/{tenantID}/relation-tuples/{id}:
    delete:
      operationId: delete_v1_tenants_ByTenantID_relation_tuples_ById
      summary: Delete relation tuple
      tags:
        - ReBAC
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '204':
          description: Deleted
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/tenants/{tenantID}/relation-tuples/check:
    post:
      operationId: post_v1_tenants_ByTenantID_relation_tuples_check
      summary: Check relation
      tags:
        - ReBAC
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - object
                - relation
                - user_id
              properties:
                object:
                  type: string
                relation:
                  type: string
                user_id:
                  type: string
      responses:
        '200':
          description: '{ allowed: bool }'
          content:
            application/json:
              schema:
                type: object
                properties:
                  allowed:
                    type: boolean
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/tenants/{tenantID}/ip-rules:
    get:
      operationId: get_v1_tenants_ByTenantID_ip_rules
      summary: List rules + enforcement
      tags:
        - IP Rules
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    post:
      operationId: post_v1_tenants_ByTenantID_ip_rules
      summary: Add rule
      tags:
        - IP Rules
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/tenants/{tenantID}/ip-rules/check:
    post:
      operationId: post_v1_tenants_ByTenantID_ip_rules_check
      summary: Check an address
      tags:
        - IP Rules
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/tenants/{tenantID}/ip-rules/config:
    put:
      operationId: put_v1_tenants_ByTenantID_ip_rules_config
      summary: Set enforcement
      tags:
        - IP Rules
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/tenants/{tenantID}/ip-rules/{ruleID}:
    delete:
      operationId: delete_v1_tenants_ByTenantID_ip_rules_ByRuleID
      summary: Delete rule
      tags:
        - IP Rules
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
        - name: ruleID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '204':
          description: No Content
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/tenants/{tenantID}/policy:
    get:
      operationId: get_v1_tenants_ByTenantID_policy
      summary: Get policy
      tags:
        - Policy
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    put:
      operationId: put_v1_tenants_ByTenantID_policy
      summary: Update policy
      tags:
        - Policy
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      description: Response body summarized; accurate path/method/params/security. Enrich the body later.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
  /v1/tenants/{tenantID}/roles:
    get:
      operationId: get_v1_tenants_ByTenantID_roles
      summary: List roles
      tags:
        - RBAC
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Roles
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/Role'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      operationId: post_v1_tenants_ByTenantID_roles
      summary: Create role
      tags:
        - RBAC
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RoleCreate'
      responses:
        '201':
          description: Role created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Role'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/users/{userID}/tenants/{tenantID}/permissions:
    get:
      operationId: get_v1_users_ByUserID_tenants_ByTenantID_permissions
      summary: Effective permissions
      tags:
        - RBAC
      parameters:
        - name: userID
          in: path
          required: true
          schema:
            type: string
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Effective permissions
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/users/{userID}/tenants/{tenantID}/roles/{roleID}:
    delete:
      operationId: delete_v1_users_ByUserID_tenants_ByTenantID_roles_ByRoleID
      summary: Unassign role from user
      tags:
        - RBAC
      parameters:
        - name: userID
          in: path
          required: true
          schema:
            type: string
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
        - name: roleID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '204':
          description: Unassigned
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    post:
      operationId: post_v1_users_ByUserID_tenants_ByTenantID_roles_ByRoleID
      summary: Assign role to user
      tags:
        - RBAC
      parameters:
        - name: userID
          in: path
          required: true
          schema:
            type: string
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
        - name: roleID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
        description: Request body summarized; see the handler for exact fields.
      responses:
        '204':
          description: Assigned
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'User or service JWT: `Authorization: Bearer <token>`.'
    apiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: 'API key credential. Send `Authorization: ApiKey <key>` (note the `ApiKey ` prefix). Accepted on the same authenticated routes as bearerAuth.'
    scimBearer:
      type: http
      scheme: bearer
      description: 'Per-tenant SCIM provisioning token presented by the IdP: `Authorization: Bearer <scim-token>`. Distinct from user JWTs; rotated via the SCIM admin endpoints.'
    clientCredentials:
      type: oauth2
      description: OAuth 2.0 client credentials. Clients authenticate at the token endpoint via form params (`client_id`/`client_secret`) or HTTP Basic. Revocation/introspection use the same client auth.
      flows:
        clientCredentials:
          tokenUrl: /v1/oauth/token
          scopes: {}
    ssoCookie:
      type: apiKey
      in: cookie
      name: qe_ls
      description: Hosted-login SSO session cookie (HttpOnly). Set by POST /v1/auth/session and consumed by the OAuth authorize/consent flow.
  responses:
    BadRequest:
      description: Malformed request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Missing or invalid credentials
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: Authenticated but not permitted
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Conflict:
      description: Resource conflict
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unprocessable:
      description: Validation failed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    TooManyRequests:
      description: Rate limit exceeded
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    Error:
      type: object
      required:
        - error
      description: Canonical error envelope (internal/platform/httpx.WriteError).
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Stable machine code
              example: unauthorized
            message:
              type: string
              example: authentication required
            detail:
              type: string
              description: Optional human detail
            request_id:
              type: string
              description: Correlates with the X-Request-Id header
    TokenPair:
      type: object
      properties:
        access_token:
          type: string
        token_type:
          type: string
          example: Bearer
        expires_at:
          type: string
          format: date-time
        refresh_token:
          type: string
        session_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
          nullable: true
    Principal:
      type: object
      properties:
        user_id:
          type: string
          format: uuid
          nullable: true
        tenant_id:
          type: string
          format: uuid
          nullable: true
        session_id:
          type: string
          format: uuid
          nullable: true
        actor:
          type: string
          example: user
        scopes:
          type: array
          items:
            type: string
    Session:
      type: object
      additionalProperties: true
      description: An active session (id, ip, user agent, timestamps).
    SignupRequest:
      type: object
      required:
        - email
        - password
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 8
          maxLength: 256
        display_name:
          type: string
          maxLength: 200
    SignupResponse:
      type: object
      properties:
        user:
          $ref: '#/components/schemas/User'
        access_token:
          type: string
        token_type:
          type: string
          example: Bearer
        expires_at:
          type: string
          format: date-time
        refresh_token:
          type: string
        session_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
    LoginRequest:
      type: object
      required:
        - email
        - password
      properties:
        email:
          type: string
          format: email
        password:
          type: string
    RefreshRequest:
      type: object
      required:
        - refresh_token
      properties:
        refresh_token:
          type: string
    User:
      type: object
      properties:
        id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        email_verified_at:
          type: string
          format: date-time
          nullable: true
        phone:
          type: string
          nullable: true
        phone_verified_at:
          type: string
          format: date-time
          nullable: true
        display_name:
          type: string
          nullable: true
        status:
          type: string
          enum:
            - active
            - suspended
        metadata:
          type: object
          additionalProperties: true
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    Role:
      type: object
      properties:
        id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
        is_system:
          type: boolean
        created_at:
          type: string
          format: date-time
    GroupRole:
      type: object
      description: A role granted to a group, with the role's name for display.
      properties:
        role_id:
          type: string
          format: uuid
        name:
          type: string
        granted_at:
          type: string
          format: date-time
    AuthzGrantStep:
      type: object
      description: 'One link in an authorization decision''s grant path: a role that confers the requested permission and how that role reaches the user.'
      properties:
        permission:
          type: string
        granted_by:
          type: string
          description: The conferring role, e.g. "role:admin".
        via:
          type: string
          description: '"direct" for a user grant, or "group:<name>" for a group-derived grant.'
        group_id:
          type: string
          format: uuid
          description: Set only when via is a group grant.
        role_id:
          type: string
          format: uuid
    AuthzExplanation:
      type: object
      description: 'The structured "why?" for a single permission check: the decision plus every distinct grant (direct and group-derived) that confers it. Returned from GET /check when explain=true.'
      properties:
        allowed:
          type: boolean
        paths:
          type: array
          items:
            $ref: '#/components/schemas/AuthzGrantStep'
        reason:
          type: string
          description: Set only on a denial.
    RoleCreate:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 64
        description:
          type: string
          maxLength: 500
    Permission:
      type: object
      properties:
        id:
          type: string
          format: uuid
        key:
          type: string
        description:
          type: string
