openapi: 3.1.0
info:
  title: Qeet ID — Developer API (keys, webhooks, secrets, agents)
  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: API Keys
    description: Long-lived API keys for machine access.
  - name: Webhooks
    description: Outbound webhook endpoint management.
  - name: Secrets
    description: Per-tenant encrypted secrets vault.
paths:
  /v1/api-keys:
    post:
      operationId: post_v1_api_keys
      summary: Create API key
      tags:
        - API Keys
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApiKeyCreate'
      responses:
        '201':
          description: Created; secret shown once
          content:
            application/json:
              schema:
                type: object
                properties:
                  api_key:
                    $ref: '#/components/schemas/ApiKey'
                  secret:
                    type: string
                  warning:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/api-keys/{id}:
    delete:
      operationId: delete_v1_api_keys_ById
      summary: Revoke API key
      tags:
        - API Keys
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '204':
          description: Revoked
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/tenants/{tenantID}/api-keys:
    get:
      operationId: get_v1_tenants_ByTenantID_api_keys
      summary: List API keys
      tags:
        - API Keys
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: API keys
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/ApiKey'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /v1/credentials/verify:
    post:
      operationId: post_v1_credentials_verify
      summary: Verify credential (public)
      description: Any relying party can present a JWT-serialized W3C Verifiable Credential issued by this server; the response reports validity (signature, issuer, expiry, and revocation status) plus the credential subject + claims. No session required.
      tags:
        - Credentials
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - credential
              properties:
                credential:
                  type: string
                  description: The JWT-VC string.
      responses:
        '200':
          description: '{ valid, subject, issuer, vc, ... }'
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '422':
          $ref: '#/components/responses/Unprocessable'
  /v1/tenants/{tenantID}/credentials:
    get:
      operationId: get_v1_tenants_ByTenantID_credentials
      summary: List credentials
      tags:
        - Credentials
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Issued credentials (registry view)
          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_credentials
      summary: Issue credential
      tags:
        - Credentials
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - subject
                - type
              properties:
                subject:
                  type: string
                type:
                  type: string
                claims:
                  type: object
                  additionalProperties: true
                ttl_seconds:
                  type: integer
      responses:
        '201':
          description: Signed JWT-VC + credential id
          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}/credentials/{id}/revoke:
    post:
      operationId: post_v1_tenants_ByTenantID_credentials_ById_revoke
      summary: Revoke credential
      tags:
        - Credentials
      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: Revoked
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/agents/token:
    post:
      operationId: post_v1_agents_token
      summary: Mint agent token
      description: An AI agent authenticates with its agent_id + secret (JSON body or HTTP Basic) and receives an ephemeral access token marked actor_type="agent" (+ agent_id claim), verifiable on the standard JWKS path. No user session.
      tags:
        - Agents
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - agent_id
                - secret
              properties:
                agent_id:
                  type: string
                secret:
                  type: string
      responses:
        '200':
          description: Short-lived agent access token
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/tenants/{tenantID}/agents:
    get:
      operationId: get_v1_tenants_ByTenantID_agents
      summary: List agents
      tags:
        - Agents
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Agents (secrets are write-only, not returned)
          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_agents
      summary: Create agent
      tags:
        - Agents
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                scopes:
                  type: array
                  items:
                    type: string
                token_ttl_seconds:
                  type: integer
      responses:
        '201':
          description: Agent created (secret returned once)
          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}/agents/{id}:
    delete:
      operationId: delete_v1_tenants_ByTenantID_agents_ById
      summary: Delete agent
      tags:
        - Agents
      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'
    patch:
      operationId: patch_v1_tenants_ByTenantID_agents_ById
      summary: Update agent
      description: Sets or clears the agent's disabled_at. A disabled agent can no longer mint tokens; existing tokens remain valid until they expire.
      tags:
        - Agents
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - disabled
              properties:
                disabled:
                  type: boolean
      responses:
        '204':
          description: Updated
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/tenants/{tenantID}/agents/{id}/suspend:
    post:
      operationId: post_v1_tenants_ByTenantID_agents_ById_suspend
      summary: Suspend agent
      tags:
        - Agents
      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: Suspended
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/tenants/{tenantID}/agents/kill-all:
    post:
      operationId: post_v1_tenants_ByTenantID_agents_kill_all
      summary: Suspend all agents
      description: Security incident-response control. Sets disabled_at on all of the tenant's agents in a single transaction. Returns the number suspended.
      tags:
        - Agents
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Agents suspended
          content:
            application/json:
              schema:
                type: object
                properties:
                  suspended:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /v1/tenants/{tenantID}/auth-hooks:
    get:
      operationId: get_v1_tenants_ByTenantID_auth_hooks
      summary: List auth hooks
      tags:
        - Auth Hooks
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Configured hooks (secrets are write-only, not returned)
          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_auth_hooks
      summary: Create auth hook
      tags:
        - Auth Hooks
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - url
              properties:
                url:
                  type: string
                  format: uri
                secret:
                  type: string
                fail_open:
                  type: boolean
      responses:
        '201':
          description: Hook created
          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}/auth-hooks/{id}:
    patch:
      operationId: patch_v1_tenants_ByTenantID_auth_hooks_ById
      summary: Update auth hook
      tags:
        - Auth Hooks
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                enabled:
                  type: boolean
                fail_open:
                  type: boolean
      responses:
        '200':
          description: Updated
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: delete_v1_tenants_ByTenantID_auth_hooks_ById
      summary: Delete auth hook
      tags:
        - Auth Hooks
      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: Removed
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/vault/{name}:
    get:
      operationId: get_v1_vault_ByName
      summary: Vault read by name
      description: Returns a secret's value to a caller holding a "vault:<name>" (or "vault:read") scope — e.g. an AI agent fetching a credential at runtime without embedding it. Tenant comes from the caller's token; every access is audited.
      tags:
        - Secrets
      parameters:
        - name: name
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: '{ name, value }'
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                  value:
                    type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/tenants/{tenantID}/secrets:
    get:
      operationId: get_v1_tenants_ByTenantID_secrets
      summary: List secrets
      tags:
        - Secrets
      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_secrets
      summary: Create secret
      tags:
        - Secrets
      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}/secrets/{id}:
    delete:
      operationId: delete_v1_tenants_ByTenantID_secrets_ById
      summary: Delete secret
      tags:
        - Secrets
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
        - 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'
    patch:
      operationId: patch_v1_tenants_ByTenantID_secrets_ById
      summary: Rotate secret
      tags:
        - Secrets
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
        - 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:
        '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}/secrets/{id}/reveal:
    post:
      operationId: post_v1_tenants_ByTenantID_secrets_ById_reveal
      summary: Reveal secret
      tags:
        - Secrets
      parameters:
        - name: tenantID
          in: path
          required: true
          schema:
            type: string
        - 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/tenants/{tenantID}/webhooks:
    get:
      operationId: get_v1_tenants_ByTenantID_webhooks
      summary: List webhooks
      tags:
        - Webhooks
      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/webhooks:
    post:
      operationId: post_v1_webhooks
      summary: Create webhook
      tags:
        - Webhooks
      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/webhooks/{id}:
    get:
      operationId: get_v1_webhooks_ById
      summary: Get webhook
      tags:
        - Webhooks
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Webhook subscription
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: delete_v1_webhooks_ById
      summary: Disable webhook
      tags:
        - Webhooks
      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/webhooks/{id}/deliveries:
    get:
      operationId: get_v1_webhooks_ById_deliveries
      summary: List deliveries
      tags:
        - Webhooks
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Recent deliveries
          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/webhooks/{id}/deliveries/{deliveryID}/retry:
    post:
      operationId: post_v1_webhooks_ById_deliveries_ByDeliveryID_retry
      summary: Retry delivery
      tags:
        - Webhooks
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: deliveryID
          in: path
          required: true
          schema:
            type: string
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        '200':
          description: Delivery re-queued
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/webhooks/{id}/test:
    post:
      operationId: post_v1_webhooks_ById_test
      summary: Test webhook
      tags:
        - Webhooks
      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'
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'
    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
    ApiKey:
      type: object
      properties:
        id:
          type: string
          format: uuid
        tenant_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
          nullable: true
        name:
          type: string
        prefix:
          type: string
        scopes:
          type: array
          items:
            type: string
        expires_at:
          type: string
          format: date-time
          nullable: true
        last_used_at:
          type: string
          format: date-time
          nullable: true
        revoked_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
    ApiKeyCreate:
      type: object
      required:
        - tenant_id
        - name
      properties:
        tenant_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
          nullable: true
        name:
          type: string
          minLength: 1
          maxLength: 200
        scopes:
          type: array
          items:
            type: string
        expires_at:
          type: string
          format: date-time
          nullable: true
