openapi: 3.1.0
info:
  title: Qeet ID — Identity Management 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: Users
    description: User CRUD, password set, and soft-delete recycle bin (restore/purge).
  - name: Tenants
    description: Tenant (workspace) CRUD.
  - name: Invites
    description: Tenant member invitations.
  - name: Groups
    description: User groups and membership.
  - name: Verification
    description: Email and phone verification flows.
  - name: Branding
    description: Per-tenant hosted-login branding.
paths:
  /v1/groups:
    post:
      operationId: post_v1_groups
      summary: Create group
      tags:
        - Groups
      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/groups/{id}:
    delete:
      operationId: delete_v1_groups_ById
      summary: Delete group
      tags:
        - Groups
      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/groups/{id}/members:
    get:
      operationId: get_v1_groups_ById_members
      summary: List members
      tags:
        - Groups
      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:
        '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/groups/{id}/members/{userID}:
    delete:
      operationId: delete_v1_groups_ById_members_ByUserID
      summary: Remove member
      tags:
        - Groups
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: userID
          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'
    post:
      operationId: post_v1_groups_ById_members_ByUserID
      summary: Add member
      tags:
        - Groups
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: userID
          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/invites:
    post:
      operationId: post_v1_invites
      summary: Create invite
      tags:
        - Invites
      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/invites/accept:
    post:
      operationId: post_v1_invites_accept
      summary: Accept invite (public)
      tags:
        - Invites
      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/invites/{id}:
    delete:
      operationId: delete_v1_invites_ById
      summary: Revoke invite
      tags:
        - Invites
      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/tenants:
    get:
      operationId: get_v1_tenants
      summary: List tenants
      tags:
        - Tenants
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Tenant page
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenantPage'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: post_v1_tenants
      summary: Create tenant
      tags:
        - Tenants
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TenantCreate'
      responses:
        '201':
          description: Created; caller becomes owner (token pair included)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenantCreateResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/tenants/{id}:
    delete:
      operationId: delete_v1_tenants_ById
      summary: Delete tenant
      tags:
        - Tenants
      parameters:
        - 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'
    get:
      operationId: get_v1_tenants_ById
      summary: Get tenant
      tags:
        - Tenants
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Tenant
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tenant'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      operationId: patch_v1_tenants_ById
      summary: Update tenant
      tags:
        - Tenants
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TenantUpdate'
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tenant'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/tenants/{tenantID}/branding:
    get:
      operationId: get_v1_tenants_ByTenantID_branding
      summary: Get branding
      tags:
        - Branding
      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_branding
      summary: Update branding
      tags:
        - Branding
      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:
    get:
      operationId: get_v1_tenants_ByTenantID_groups
      summary: List groups
      tags:
        - Groups
      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'
  /v1/tenants/{tenantID}/invites:
    get:
      operationId: get_v1_tenants_ByTenantID_invites
      summary: List invites
      tags:
        - Invites
      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'
  /v1/users:
    get:
      operationId: get_v1_users
      summary: List users
      tags:
        - Users
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: User page
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserPage'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: post_v1_users
      summary: Create user
      tags:
        - Users
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserCreate'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/users/deleted:
    get:
      operationId: get_v1_users_deleted
      summary: List deleted users
      tags:
        - Users
      parameters:
        - $ref: '#/components/parameters/Limit'
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Soft-deleted users
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/users/{id}:
    delete:
      operationId: delete_v1_users_ById
      summary: Delete user
      tags:
        - Users
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '204':
          description: Soft-deleted
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    get:
      operationId: get_v1_users_ById
      summary: Get user
      tags:
        - Users
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: User
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      operationId: patch_v1_users_ById
      summary: Update user
      tags:
        - Users
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserUpdate'
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/users/{id}/password:
    post:
      operationId: post_v1_users_ById_password
      summary: Set password
      tags:
        - Users
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - password
              properties:
                password:
                  type: string
                  minLength: 8
                  maxLength: 256
      responses:
        '204':
          description: Password set
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/users/{id}/mfa:
    delete:
      operationId: delete_v1_users_ById_mfa
      summary: Admin reset user MFA
      description: Account-recovery for a locked-out user. Requires the `user.write` permission; audited as `mfa.admin_reset`.
      tags:
        - Users
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: MFA 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'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/users/{id}/purge:
    delete:
      operationId: delete_v1_users_ById_purge
      summary: Purge user
      tags:
        - Users
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '204':
          description: Permanently purged
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/users/{id}/restore:
    post:
      operationId: post_v1_users_ById_restore
      summary: Restore user
      tags:
        - Users
      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.
      responses:
        '200':
          description: Restored
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/users/{id}/verify/email/confirm:
    post:
      operationId: post_v1_users_ById_verify_email_confirm
      summary: Confirm email verification
      tags:
        - Verification
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - code
              properties:
                code:
                  type: string
      responses:
        '200':
          description: Email verified; returns a confirmation message
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '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/users/{id}/verify/email/start:
    post:
      operationId: post_v1_users_ById_verify_email_start
      summary: Start email verification
      description: Sends a 6-digit code to the address on file. Pass `email` only to verify a different address.
      tags:
        - Verification
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  format: email
                  description: Optional; defaults to the user's email on file.
      responses:
        '200':
          description: Code sent; returns a confirmation message
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '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/users/{id}/verify/phone/confirm:
    post:
      operationId: post_v1_users_ById_verify_phone_confirm
      summary: Confirm phone verification
      tags:
        - Verification
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - code
              properties:
                code:
                  type: string
      responses:
        '200':
          description: Phone verified; returns a confirmation message
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '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/users/{id}/verify/phone/start:
    post:
      operationId: post_v1_users_ById_verify_phone_start
      summary: Start phone verification
      description: Sends a 6-digit code to the number on file. Pass `phone` only to verify a different number.
      tags:
        - Verification
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                phone:
                  type: string
                  description: Optional; defaults to the user's phone on file.
      responses:
        '200':
          description: Code sent; returns a confirmation message
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
        '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}/domains:
    get:
      operationId: get_v1_tenants_ByTenantID_domains
      summary: List domains
      tags:
        - Domains
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Domains with their DNS verification records
          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_domains
      summary: Claim domain
      tags:
        - Domains
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - domain
              properties:
                domain:
                  type: string
      responses:
        '201':
          description: Domain claimed; publish the returned TXT record then verify
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/tenants/{tenantID}/domains/{id}/verify:
    post:
      operationId: post_v1_tenants_ByTenantID_domains_ById_verify
      summary: Verify domain
      tags:
        - Domains
      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: Verification result (verified_at set on success)
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/tenants/{tenantID}/domains/{id}:
    delete:
      operationId: delete_v1_tenants_ByTenantID_domains_ById
      summary: Remove domain
      tags:
        - Domains
      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: Domain removed
        '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.
  parameters:
    Limit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 200
      description: Maximum items to return.
    Cursor:
      name: cursor
      in: query
      required: false
      schema:
        type: string
      description: Opaque pagination cursor from a prior response's next_cursor.
  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
    Page:
      type: object
      description: Cursor-paginated list wrapper.
      properties:
        items:
          type: array
          items: {}
        next_cursor:
          type: string
          nullable: true
          description: Cursor for the next page; empty/absent at the end.
    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
    UserCreate:
      type: object
      required:
        - tenant_id
        - email
      properties:
        tenant_id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 8
          maxLength: 256
        display_name:
          type: string
          maxLength: 200
        phone:
          type: string
          description: E.164
        metadata:
          type: object
          additionalProperties: true
    UserUpdate:
      type: object
      properties:
        display_name:
          type: string
          maxLength: 200
          nullable: true
        phone:
          type: string
          nullable: true
        status:
          type: string
          enum:
            - active
            - suspended
        metadata:
          type: object
          additionalProperties: true
    UserPage:
      allOf:
        - $ref: '#/components/schemas/Page'
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/User'
        next_cursor:
          type: string
          nullable: true
    Tenant:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
        name:
          type: string
        status:
          type: string
          enum:
            - active
            - suspended
        plan:
          type: string
          enum:
            - free
            - pro
            - enterprise
        region:
          type: string
        metadata:
          type: object
          additionalProperties: true
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    TenantCreate:
      type: object
      required:
        - slug
        - name
      properties:
        slug:
          type: string
          minLength: 2
          maxLength: 64
        name:
          type: string
          minLength: 1
          maxLength: 200
        plan:
          type: string
          enum:
            - free
            - pro
            - enterprise
        region:
          type: string
          maxLength: 64
        metadata:
          type: object
          additionalProperties: true
    TenantUpdate:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 200
        status:
          type: string
          enum:
            - active
            - suspended
        plan:
          type: string
          enum:
            - free
            - pro
            - enterprise
        region:
          type: string
          maxLength: 64
        metadata:
          type: object
          additionalProperties: true
    TenantCreateResponse:
      type: object
      properties:
        tenant:
          $ref: '#/components/schemas/Tenant'
        tenant_id:
          type: string
          format: uuid
        roles:
          type: array
          items:
            type: string
          example:
            - owner
        access_token:
          type: string
        token_type:
          type: string
        expires_at:
          type: string
          format: date-time
        refresh_token:
          type: string
        session_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
    TenantPage:
      allOf:
        - $ref: '#/components/schemas/Page'
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Tenant'
        next_cursor:
          type: string
          nullable: true
