openapi: 3.0.3
info:
  title: VerifyAX Gateway Public API
  version: 1.0.0
  license:
    name: Proprietary
    url: https://conscium.com/terms-of-use
  description: >
    Public HTTP API exposed by the VerifyAX gateway under `/api`.

    **Tenant scoping:** On proxied `POST`, `PUT`, and `PATCH` bodies (except
    `PATCH /v1/scenarios/{scenario_uuid}/artifacts`), the gateway sets
    `organization_uuid`, `workspace_uuid`, and `user_uuid` from the authenticated API key.
    Client-supplied values for those fields are **overwritten** before the request reaches
    verifyax-api. The same tenant fields are also set on query strings for `GET`/`DELETE`.
    API keys must be bound to a user. Example request bodies in this document may still
    show tenant UUIDs for clarity; treat them as illustrative — the gateway replaces them.
    Response bodies may include tenant fields returned by verifyax-api (resource metadata,
    usage events, etc.).

    **Rate limiting:** Public `/api/v1` requests are limited per workspace. Responses include
    `RateLimit-*` headers; exceeding the limit returns **429** with `Retry-After`.
servers:
  - url: /api
    description: Gateway public API base path
tags:
  - name: Auth
    description: One-time login tokens for browser sign-in from API-authenticated context.
  - name: Tickets
    description: Open feedback or help/support tickets for the API key's user.
  - name: Scenarios
    description: Simulation scenarios (generation, copies, artifact reads).
  - name: Jobs
    description: Async Celery-backed jobs (scenario creation, engine runs, evaluations, etc.).
  - name: Agents
    description: Registered agent endpoints (A2A, API, extension) and connectivity tests.
  - name: Engine
    description: Start simulation runs, preview credits, and trigger evaluations.
  - name: Billing
    description: Read-only billing balances for API-key tenants.
  - name: Simulations
    description: List, fetch, cancel, and delete simulation runs (`simulation_uuid` in paths).
  - name: Audit Logs
    description: Read-only organization audit events with filters and pagination.
  - name: Usage
    description: Workspace usage events and per-call LLM usage for spend analysis.
  - name: Skill Tags
    description: >
      Scenario skill tag catalogue for scenario generation (`GET /v1/tags`). Use tag `name`
      values in `POST /v1/scenarios/generate`; filter by `allowed_scenario_types` for your
      `scenario_type` before calling generate.
  - name: Client Tags
    description: Register organization-specific QnA skill tags for interview scenarios.
  - name: Gold Standards
    description: >
      Favorites: a pinned run group that later runs are compared against. `PATCH` moves a Favorite
      to a newer run without resetting `pinned_at`, so the capability baseline survives a repin.
  - name: Devices
    description: Enroll client device signing keys so installers can sign artifact uploads.
security:
  - BearerApiKeyAuth: []
paths:
  /v1/auth/one-time-login-token:
    post:
      tags: [Auth]
      summary: Create one-time login token
      description: >
        Creates a one-time token using API-key authenticated context. Response `example_links`
        put the token in the URL fragment (`#one-time-login-token=...`) so static hosts and
        cross-origin Referer headers do not receive the secret; the user-webapp redeems it via POST.
        The webapp does not read the token from the query string.
      operationId: createOneTimeLoginToken
      responses:
        "200":
          description: Token created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OneTimeLoginTokenResponse"
        "400":
          description: Missing user, organization, or workspace context
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/tickets:
    post:
      tags: [Tickets]
      summary: Open a ticket
      description: >
        Opens a ticket (a feedback or help/support message) on behalf of the API key's user.
        The user identity is taken from the API key and the email is resolved server-side, so
        only `message` (and optional `type`) are sent in the body. `type` defaults to
        `help_support`. Returns **400** if the user account has no email address, and **404**
        if the API key's user account cannot be found.
      operationId: createTicket
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TicketCreate"
      responses:
        "201":
          description: Ticket created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TicketResponse"
        "400":
          description: Empty message, invalid type, or the user account has no email address
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: The user account associated with the API key was not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/devices/enroll:
    post:
      tags: [Devices]
      summary: Enroll a signing device
      description: >
        Registers a client device's public signing key so the device can sign artifact uploads.
        The installer authenticates with its workspace API key and presents a one-time
        `enrollmentCode` minted from a logged-in Workbench session. The gateway verifies the code,
        cross-checks that it belongs to the API key's organization, derives the RFC 7638 JWK
        thumbprint (`jkt`) and signature algorithm, then registers the device. Only OKP (Ed25519,
        `EdDSA`) and EC (P-256, `ES256`) public keys are accepted.
      operationId: enrollDevice
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DeviceEnrollRequest"
      responses:
        "201":
          description: Device enrolled
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeviceEnrollResponse"
        "400":
          description: Missing body fields, invalid or expired enrollment code, or unsupported public key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Enrollment code belongs to a different organization
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "409":
          description: >
            The public key belongs to a revoked device, which is never reactivated. Enroll again
            with a newly generated key; the one-time code is restored and stays usable. Re-sending
            a key that is still active is not a conflict — it returns the existing enrollment.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/billing/balance:
    get:
      tags: [Billing]
      summary: Get billing balance
      description: Returns read-only billing balance fields for the API key tenant.
      operationId: getBillingBalance
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      responses:
        "200":
          description: Billing balance fields
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicBillingBalanceResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/scenarios:
    get:
      tags: [Scenarios]
      summary: List scenarios
      description: >
        Lists every scenario in the workspace, including scenarios created by other members,
        with optional `scenario_type`, `status`, `limit`, and `offset` filters. Default
        `limit` upstream is 100 (max 1000).
      operationId: listScenarios
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/ScenarioType"
        - $ref: "#/components/parameters/Status"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: List scenarios
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ScenarioResponse"
        "400":
          description: Invalid scenario_type or status filter value
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"
    post:
      tags: [Scenarios]
      summary: Create scenario shell
      description: >
        **Step 1 of a 2-step JSON import.** Creates an empty scenario row synchronously (no
        `scenario_creation` job). The scenario is **not runnable** until step 2: uploading
        the definition via `PATCH /v1/scenarios/{uuid}/artifacts`. Starting a run on a shell
        before the artifacts are uploaded returns **409 `scenario_definition_missing`** from
        `/v1/engine/simulate/scenario` and `/v1/engine/workspace-credit-preview`. Use
        `scenario_type: info_exchange` for JSON imports. Sending `corpus_uuid` returns **400**.
        `job_uuid` in the response is null. Prefer `POST /v1/scenarios/generate` if you want
        a fully-formed scenario in one call.

        **Developer-only:** requires the API-key owner's Auth0 user to have the platform
        `developer` role (enforced by the gateway via Auth0 Management API).
      operationId: createScenario
      x-developer-only: true
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ScenarioCreateRequest"
            example:
              name: Imported Scenario
              description: Scenario imported from JSON
              scenario_type: info_exchange
      responses:
        "201":
          description: Created scenario shell
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScenarioCreateResponse"
              example:
                uuid: 00000000-0000-0000-0000-000000000001
                name: Imported Scenario
                description: Scenario imported from JSON
                scenario_type: info_exchange
                job_uuid: null
        "400":
          description: Missing scenario_type, invalid enum, or corpus_uuid sent
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: API-key owner lacks Auth0 developer role
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "409":
          description: Name already exists in workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/scenarios/generate:
    post:
      tags: [Scenarios]
      summary: Generate scenario
      description: >
        Queues LLM scenario_creation work. Returns the scenario row (`uuid`, not
        `scenario_uuid`) plus `job_uuid`. When num_scenarios is greater than 1, creates a batch
        (`batch_uuid`, `batch_scenario_uuids`) and requires `tag_pool`. Runs a billing credit
        preflight before enqueueing (**402** when balance is insufficient, **502** when billing is
        unreachable). Tenant UUIDs are injected by the gateway from the API key. Use the returned
        `uuid` as the `scenario_uuid` path parameter on `/v1/scenarios/{scenario_uuid}/...`.
      operationId: generateScenario
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SimulationScenarioCreateRequest"
            example:
              name: Product Return Flow
              scenario_type: interview
              tags: [anger_deescalation]
              context_prompt: Customer wants to return a damaged item bought 2 weeks ago
      responses:
        "201":
          description: Created scenario with scenario_creation job
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScenarioCreateResponse"
              example:
                uuid: 00000000-0000-0000-0000-000000000001
                name: Product Return Flow
                scenario_type: interview
                status: null
                job_uuid: 11111111-1111-1111-1111-111111111111
                batch_uuid: null
                batch_scenario_uuids: null
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          description: Insufficient workspace credits for estimated generation cost
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "409":
          description: Name already exists in workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "422":
          description: Request body failed Pydantic validation (tag caps, batch rules, etc.)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"
        "502":
          description: Billing preflight could not reach the billing service
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"

  /v1/scenarios/generate-from-qna:
    post:
      tags: [Scenarios]
      summary: Generate interview scenario from Q&A
      description: >
        Creates a fixed-question **interview** scenario from inline Q&A pairs (for example
        `l3_qna_benchmark.json` output). Enqueues `scenario_creation` and returns the scenario
        row plus `job_uuid`. Tenant UUIDs are injected by the gateway from the API key.
      operationId: generateScenarioFromQna
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GenerateScenarioFromQnaRequest"
            example:
              name: Latin QnA interview
              context_prompt: Latin dance history
              questions:
                - question: What is salsa?
                  correct_answer: A partner dance with Afro-Cuban roots.
                  is_hallucination_trap: false
      responses:
        "201":
          description: Created scenario with scenario_creation job
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScenarioCreateResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          description: Request body failed validation (empty questions, too many items, etc.)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/scenarios/tag-recommendation:
    post:
      tags: [Scenarios]
      summary: Recommend scenario skill tags
      description: >
        Suggests skill tags using the same pipeline as the Workbench Scenario Generator: verifyax
        embeddings + LLM recommendation, then gateway merge with lexical tag ranking (cap 20).
        Provide `context_prompt` and/or `agent_uuid` (at least one). The gateway injects tenant
        UUIDs from the API key. Response is a **bare JSON array** of skill tag objects — the same
        shape as `GET /v1/tags`, in recommendation order (not `{ success, data }`).
      operationId: recommendScenarioTags
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TagRecommendationPublicRequest"
            example:
              scenario_type: interview
              context_prompt: Customer wants to return a damaged item bought 2 weeks ago
              agent_uuid: 00000000-0000-0000-0000-000000000002
      responses:
        "200":
          description: Recommended skill tags (Workbench Suggested column parity)
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/SkillTag"
              example:
                - name: human_npc
                  category: structure
                  description: Multi-agent human NPC scenario structure.
                  benchmark_family: null
                  allowed_scenario_types: [info_exchange, interview]
                  custom: false
                - name: skepticism_handling
                  category: social
                  description: Handle skeptical interlocutors.
                  benchmark_family: null
                  allowed_scenario_types: [info_exchange, interview]
                  custom: false
        "400":
          description: Missing context/agent, invalid scenario_type, or other client error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Agent not found or not in workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "422":
          description: Request body failed validation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "503":
          description: Recommender dependency unavailable (for example Redis or embeddings)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "504":
          description: Recommender exceeded server timeout
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/scenarios/tag-search:
    post:
      tags: [Scenarios]
      summary: Search skill tags by embedding similarity
      description: >
        Returns skill tags from the global catalogue ranked by cosine similarity to a natural-language
        query (no LLM). The gateway injects tenant UUIDs from the API key. A dedicated per-user tag
        search rate limit applies in addition to the workspace public API limit.
      operationId: searchScenarioTags
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TagSearchPublicRequest"
            example:
              scenario_type: info_exchange
              query: de-escalate angry customer
              limit: 20
      responses:
        "200":
          description: Ranked skill tag names
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TagSearchPublicResponse"
        "400":
          description: Query too short or invalid scenario_type
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          description: Request body failed validation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "503":
          description: Search dependency unavailable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "504":
          description: Search exceeded server timeout
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/scenarios/{scenario_uuid}:
    get:
      tags: [Scenarios]
      summary: Get scenario by UUID
      description: >
        Returns scenario metadata, definition paths, optional `scenario_creation_job_status`,
        complexity fields, etc.
      operationId: getScenario
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/ScenarioUuid"
      responses:
        "200":
          description: Scenario details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScenarioResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Scenario not in the authenticated workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "404":
          description: Scenario not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"
    patch:
      tags: [Scenarios]
      summary: Update scenario by UUID
      description: Partial metadata update (name and description only).
      operationId: updateScenario
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/ScenarioUuid"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ScenarioUpdateRequest"
            example:
              name: Customer Support L2 - Revised
              description: Updated description for scenario tuning
      responses:
        "200":
          description: Updated scenario
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScenarioResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Scenario not in the authenticated workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "404":
          description: Scenario not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"
    delete:
      tags: [Scenarios]
      summary: Delete scenario by UUID
      description: >
        Deletes the scenario in the current workspace context. Returns **409** when the scenario
        still has any non-deleted simulation run pointing at it.
      operationId: deleteScenario
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/ScenarioUuid"
      responses:
        "204":
          description: Scenario deleted (no response body)
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Scenario not in the authenticated workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "404":
          description: Scenario not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "409":
          description: Scenario still has non-deleted simulation runs
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/scenarios/{scenario_uuid}/generate-copy:
    post:
      tags: [Scenarios]
      summary: Generate new scenario from stored creation parameters
      description: >
        Replays `creation_parameters` from the source scenario through the same LLM generation
        path as `POST /v1/scenarios/generate`. Response shape matches generate (`uuid`, `job_uuid`,
        optional batch fields). Returns **400** when the source has no saved creation snapshot.
        Tenant scope is injected from the API key (query on upstream).
      operationId: generateScenarioCopy
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/ScenarioUuid"
      responses:
        "201":
          description: New scenario with scenario_creation job
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScenarioCreateResponse"
              example:
                uuid: 00000000-0000-0000-0000-000000000002
                name: Product Return Flow (Copy)
                job_uuid: 11111111-1111-1111-1111-111111111111
        "400":
          description: Source has no creation_parameters to replay
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Scenario not in tenant scope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "404":
          description: Scenario not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "409":
          description: Name conflict in workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/scenarios/{scenario_uuid}/copy:
    post:
      tags: [Scenarios]
      summary: Copy scenario
      description: >
        Duplicates storage (`scenario.json` when present) into a new scenario row in the same
        workspace. Optional `new_name` query parameter; default name is `{original} (Copy)` with
        numeric suffixes when needed.
      operationId: copyScenario
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/ScenarioUuid"
        - $ref: "#/components/parameters/NewName"
      responses:
        "201":
          description: Copied scenario
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScenarioResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Scenario not in tenant scope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "404":
          description: Scenario not found, or source scenario.json missing in storage
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "409":
          description: Name conflict in workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/scenarios/{scenario_uuid}/job:
    get:
      tags: [Scenarios]
      summary: List jobs for scenario
      description: >
        Returns all non-deleted jobs for the scenario (newest first), including
        `scenario_creation` and other job types.
      operationId: getScenarioJob
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/ScenarioUuid"
      responses:
        "200":
          description: Scenario jobs
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobsListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Scenario not in the authenticated workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "404":
          description: Scenario not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/scenarios/{scenario_uuid}/artifacts:
    get:
      tags: [Scenarios]
      summary: Get scenario artifacts JSON
      description: >
        Downloads `scenario.json` (or legacy `benchmark.json` for corpus-linked rows) from storage.
        **404** when `definition_bucket_path` is unset or the file is missing.
      operationId: getScenarioArtifacts
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/ScenarioUuid"
      responses:
        "200":
          description: Parsed scenario.json (or legacy benchmark.json) object
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Scenario not in the authenticated workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "404":
          description: definition_bucket_path unset or JSON file missing in storage
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"
    patch:
      tags: [Scenarios]
      summary: Replace scenario artifacts JSON
      description: >
        Replaces the entire `scenario.json` object in storage with the request body (full
        document upload, not a deep merge). Requires `definition_bucket_path` on the scenario.

        **Developer-only:** requires the API-key owner's Auth0 user to have the platform
        `developer` role (enforced by the gateway via Auth0 Management API).
      operationId: patchScenarioArtifacts
      x-developer-only: true
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/ScenarioUuid"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
            example:
              prompt_template: "You are a support assistant. Follow policy strictly."
              metadata:
                owner: qa-team
                version: "1.0.0"
      responses:
        "200":
          description: Echoes the uploaded JSON document after writing to storage
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: API-key owner lacks Auth0 developer role, or scenario not in workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "404":
          description: definition_bucket_path unset on the scenario
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/jobs:
    get:
      tags: [Jobs]
      summary: List jobs
      description: >
        Lists async jobs for the workspace, including jobs created by other members
        (scenario creation, engine simulation runs, evaluations, etc.) with optional
        `current_status` filter and pagination.
      operationId: listJobs
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - $ref: "#/components/parameters/CurrentStatus"
      responses:
        "200":
          description: List jobs
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/JobResponse"
              example:
                - uuid: 11111111-1111-1111-1111-111111111111
                  job_type: simulation
                  user_uuid: 00000000-0000-0000-0000-000000000001
                  workspace_uuid: 00000000-0000-0000-0000-000000000002
                  current_status: PROCESSING
                  current_progress_text: Round 3 of 5
                  progress_percentage: 60
                  error_details: null
                  task_id: celery-task-id
                  created_at: "2026-04-30T10:00:00.000Z"
                  updated_at: "2026-04-30T10:02:30.000Z"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/jobs/{job_uuid}:
    get:
      tags: [Jobs]
      summary: Get job by UUID
      description: Returns one job by UUID — current status, progress, and error details.
      operationId: getJob
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/JobUuid"
      responses:
        "200":
          $ref: "#/components/responses/ProxySuccess"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"
    delete:
      tags: [Jobs]
      summary: Delete job by UUID
      description: Deletes a job record. Typically allowed only for terminal states (`COMPLETED`, `FAILED`, `CANCELLED`).
      operationId: deleteJob
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/JobUuid"
      responses:
        "200":
          $ref: "#/components/responses/ProxySuccess"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/jobs/{job_uuid}/retry:
    post:
      tags: [Jobs]
      summary: Retry job
      description: Submits a retry for a `FAILED` or retriable job in the workspace context.
      operationId: retryJob
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/JobUuid"
      responses:
        "200":
          $ref: "#/components/responses/ProxySuccess"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/jobs/{job_uuid}/cancel:
    post:
      tags: [Jobs]
      summary: Cancel job
      description: >
        Cancels an in-progress job when allowed by policy. Returns an acknowledgement message
        when cancellation succeeds.
      operationId: cancelJob
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/JobUuid"
      responses:
        "200":
          description: Job cancelled
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobCancelResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Job not found in the authenticated workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/agents:
    get:
      tags: [Agents]
      summary: List agents
      description: Lists every agent registered in the workspace, including agents created by other members, with optional type filter and pagination.
      operationId: listAgents
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/AgentType"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: List agents
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/AgentResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"
    post:
      tags: [Agents]
      summary: Create agent
      description: Registers an agent endpoint (A2A, API, Direct Line, etc.) for use in simulations and tests.
      operationId: createAgent
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AgentCreateRequest"
            examples:
              a2a:
                summary: A2A agent
                value:
                  name: Support Agent
                  description: Agent for handling customer support requests
                  agent_url: https://agent.example.com/api
                  agent_type: A2A
                  agent_parameters:
                    auth_method: bearer
                    token: sk-example-token-min-10-chars
                    timeout: 15000
              directline:
                summary: Copilot Studio (Direct Line)
                description: >
                  Set `agent_url` from `directline.region` (see DirectlineAgentParameters).
                  Store the Direct Line secret under `agent_parameters.directline.secret`.
                value:
                  name: Copilot Studio Support Bot
                  description: Microsoft Copilot Studio agent over Direct Line 3.0
                  agent_url: https://directline.botframework.com
                  agent_type: DIRECTLINE
                  agent_parameters:
                    directline:
                      secret: "<direct-line-secret-from-copilot-studio>"
                      region: global
                    include_full_scenario: first_only
                    include_message_history: false
      responses:
        "200":
          description: Agent
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/agents/{agent_uuid}:
    get:
      tags: [Agents]
      summary: Get agent by UUID
      description: Returns a single agent definition including URL and parameters.
      operationId: getAgent
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/AgentUuid"
      responses:
        "200":
          description: Agent
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"
    patch:
      tags: [Agents]
      summary: Update agent by UUID
      description: Partially updates agent settings (name, URL, agent_parameters).
      operationId: updateAgent
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/AgentUuid"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AgentUpdateRequest"
            example:
              name: Support Agent - Updated
              description: Tweaked timeout and endpoint
              agent_url: https://example.com/agent/v2
              agent_parameters:
                timeout_seconds: 30
      responses:
        "200":
          $ref: "#/components/responses/ProxySuccess"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"
    delete:
      tags: [Agents]
      summary: Delete agent by UUID
      description: Removes the agent from the workspace registry.
      operationId: deleteAgent
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/AgentUuid"
      responses:
        "200":
          $ref: "#/components/responses/ProxySuccess"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/agents/{agent_uuid}/nfr/gate-policy:
    get:
      tags: [Agents]
      summary: Get the agent's release policy
      description: >
        Returns what this agent's runs are judged against, and the hash every decision
        made under it records. Comparing that hash against the one on a decision tells a
        pipeline the policy changed after the run was judged, without comparing rules
        field by field.
      operationId: getNfrGatePolicy
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/AgentUuid"
      responses:
        "200":
          description: The stored policy and its hash
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NfrGatePolicyResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: The agent belongs to another workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"
    put:
      tags: [Agents]
      summary: Replace the agent's release policy
      description: >
        A whole replacement rather than a merge, so a repository's policy file is the
        statement of what the team blocks on. A merge would let a rule somebody deleted
        there go on blocking builds here. An unparseable field falls back to its default
        rather than refusing the policy, because a policy that will not load is a gate
        that cannot decide. An unknown rule name is refused with 422, so a typo cannot
        drop a rule.
      operationId: setNfrGatePolicy
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/AgentUuid"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NfrGatePolicyRequest"
            example:
              policy:
                required_dimensions: [security, safety]
                forbidden_kinds: [auth_bypass]
                severity_floor: high
                block_on_regression: true
                inconclusive_blocks: true
      responses:
        "200":
          description: The policy as stored, with its hash
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NfrGatePolicyResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: The agent belongs to another workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "422":
          description: The policy names a rule the gate does not know
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/agents/tests/agent-card:
    post:
      tags: [Agents]
      summary: Test fetch agent card via proxy connector
      description: Validates connectivity by fetching an A2A-style agent card URL through the gateway connector.
      operationId: testAgentCard
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FetchAgentCardRequest"
            example:
              agent_url: https://example.com/.well-known/agent.json
              agent_type: A2A
      responses:
        "200":
          $ref: "#/components/responses/ProxySuccess"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/agents/tests/api-agent-test:
    post:
      tags: [Agents]
      summary: Test REST agent endpoint
      description: Sends a configurable HTTP request to a REST agent and returns the proxied response for debugging.
      operationId: testRestAgent
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TestRestAgentRequest"
            example:
              url: https://example.com/agent/test
              method: POST
              headers:
                Authorization: Bearer replace-with-token
              body:
                message: Hello from API docs test
              timeout: 10.0
      responses:
        "200":
          $ref: "#/components/responses/ProxySuccess"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/agents/tests/api-agent-test-curl:
    post:
      tags: [Agents]
      summary: Test CURL agent command
      description: Executes a curl-style agent definition server-side with a timeout for integration checks.
      operationId: testCurlAgent
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TestCurlAgentRequest"
            example:
              curl_command: "curl -X POST https://example.com/agent/test -H 'Content-Type: application/json' -d '{\"message\":\"hello\"}'"
              timeout: 10.0
      responses:
        "200":
          $ref: "#/components/responses/ProxySuccess"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/agents/tests/a2a-connection:
    post:
      tags: [Agents]
      summary: Test A2A agent connection (card, message, mini simulations)
      description: >
        Full A2A onboarding connectivity test: fetches the agent card, sends a synchronous
        message probe, and optionally starts background mini scenario simulations when
        `agent_uuid` and tenant UUIDs are present (injected from the API key on the public
        surface). Poll the agent record for `testing` status when `testing_started` is true.
      operationId: testA2aConnection
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/A2AConnectionTestRequest"
            example:
              agent_url: https://example.com/.well-known/agent.json
              agent_type: A2A
              message: Hello from VerifyAX connection test
      responses:
        "200":
          description: Connection test result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/A2AConnectionTestResponse"
        "400":
          description: Missing agent_url or invalid request body
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/agents/tests/a2a-message:
    post:
      tags: [Agents]
      summary: Test A2A message probe
      description: >
        Sends a single A2A message probe through the gateway connector. Lighter-weight than
        `POST /v1/agents/tests/a2a-connection` (no mini simulations).
      operationId: testA2aMessage
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TestA2AMessageRequest"
            example:
              agent_url: https://example.com/.well-known/agent.json
              agent_type: A2A
              message: Ping
      responses:
        "200":
          $ref: "#/components/responses/ProxySuccess"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/agents/tests/api-agent-test-directline:
    post:
      tags: [Agents]
      summary: Test Copilot Studio via Direct Line
      description: >
        Probes a Microsoft Copilot Studio agent using a Direct Line secret **before registration**.
        Request body uses top-level `secret` and `region` — not the nested
        `agent_parameters.directline` object used by `POST /v1/agents`. Returns connector
        outcome metadata (`success`, `message`, `outcome`) suitable for onboarding checks.
      operationId: testDirectlineAgent
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TestDirectlineAgentRequest"
            example:
              secret: "<direct-line-secret>"
              region: global
              message: Hello
              timeout: 60.0
      responses:
        "200":
          $ref: "#/components/responses/ProxySuccess"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/agents/tests/api-agent-test-copilot-studio:
    post:
      tags: [Agents]
      summary: Test Copilot Studio agent (all auth modes)
      description: >
        Probes a Copilot Studio agent using the nested `directline` registration shape.
        Supports `auth_mode` `secret`, `microsoft` (Entra OBO), and `manual` (custom OAuth).
        Microsoft and manual modes require `user_token` (end-user Entra access token).
      operationId: testCopilotStudioAgent
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TestCopilotStudioAgentRequest"
      responses:
        "200":
          $ref: "#/components/responses/ProxySuccess"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/agents/tests/mcp-connection:
    post:
      tags: [Agents]
      summary: Test MCP server connection
      description: >
        Discovers tools on a remote MCP server and, when `agent_url` is set, fetches the
        catalogue MCP adapter agent card. Does not send A2A probe messages or run mini
        scenarios. Tenant UUIDs are injected from the API key. Use before or after
        registering an agent with `agent_type: MCP`.
      operationId: testMcpConnection
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TestMcpConnectionRequest"
            example:
              mcp_url: https://mcp.example.com/mcp
              auth_method: bearer
              token: "<pat-or-api-key>"
              agent_url: https://mcp-adapter.example.run.app
      responses:
        "200":
          description: MCP discovery and optional adapter agent-card result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TestMcpConnectionResponse"
        "400":
          description: Missing mcp_url or invalid MCP URL
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/agents/tests/connection-readiness:
    post:
      tags: [Agents]
      summary: Check whether a saved agent can preflight
      description: >
        Looks at the saved agent's last full connection test and newest completed run.
        A pass or completed run inside the last 7 days returns `preflight`. A missing
        result, a failure, or anything older than 7 days returns `full_test_required`.
        A test that is still running returns `in_progress`. Does not contact the agent.
        The gateway overwrites `workspace_uuid` from the API key.
      operationId: connectionReadiness
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ConnectionCheckRequest"
            example:
              agent_uuid: 00000000-0000-4000-8000-000000000001
      responses:
        "200":
          description: Whether a run may preflight or must wait for a full connection test
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConnectionReadinessResponse"
        "400":
          description: Missing or invalid agent_uuid
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Agent does not belong to the API key's workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/agents/tests/connection-preflight:
    post:
      tags: [Agents]
      summary: Check that a saved agent is still reachable
      description: >
        Reachability check against the saved URL and credentials. Does not run practice
        simulations and does not replace the stored connection-test result. Use after
        `connection-readiness` returns `preflight`. For `auth_method=cs` agents, pass a
        fresh Conscium session token in `cs_auth_token`. The gateway overwrites
        `workspace_uuid` from the API key.
      operationId: connectionPreflight
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ConnectionCheckRequest"
            example:
              agent_uuid: 00000000-0000-4000-8000-000000000001
      responses:
        "200":
          description: Reachability result. A failed check is still HTTP 200 with success false.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConnectionPreflightResponse"
        "400":
          description: Missing or invalid agent_uuid
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Agent does not belong to the API key's workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "404":
          description: Agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/engine/workspace-credit-preview:
    post:
      tags: [Engine]
      summary: Preview workspace credits (scenario run or scenario generation)
      description: >
        Unified credit preview: organisation balance, pending ENGINE verification runs and
        scenario_creation jobs (with stored estimates), and either `newRunEstimatedCredits`
        (`mode: scenario_run`) or `newGenerationEstimatedCredits` (`mode: scenario_generation`).
        The estimate field that does not apply is `null`. Tenant UUIDs in the body are
        overwritten by the gateway from the API key (see API overview).
      operationId: previewWorkspaceCredits
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreditPreviewRequest"
            examples:
              scenario_run:
                summary: Scenario run estimate
                description: Tenant UUIDs optional — gateway injects them from the API key.
                value:
                  mode: scenario_run
                  organization_uuid: 00000000-0000-0000-0000-000000000010
                  workspace_uuid: 00000000-0000-0000-0000-000000000020
                  scenario_uuid: 00000000-0000-0000-0000-000000000001
                  num_runs: 2
                  timeout_minutes: 30
              scenario_generation:
                summary: Scenario generation estimate
                description: Tenant UUIDs optional — gateway injects them from the API key.
                value:
                  mode: scenario_generation
                  num_scenarios: 3
      responses:
        "200":
          description: Credit preview
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreditPreviewResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "409":
          description: >
            Scenario row exists but its `scenario.json` artifact has not been uploaded yet
            (see issue #2370). Use `POST /v1/scenarios/generate` to create a fully-formed scenario.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScenarioDefinitionMissingError"
              example:
                detail:
                  error: scenario_definition_missing
                  message: >-
                    Scenario has no definition uploaded. Use POST /v1/scenarios/generate
                    to create a fully-formed scenario.
                  scenario_uuid: 00000000-0000-0000-0000-000000000001
                  scenario_path: orgs/<org>/scenarios/<scenario>/scenario.json
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/engine/simulate/scenario:
    post:
      tags: [Engine]
      summary: Trigger scenario simulation
      description: Starts a scenario simulation; supports evaluate_on_complete and multiple runs.
      operationId: simulateScenario
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EngineInterrogateScenarioRequest"
            example:
              scenario_uuid: 00000000-0000-0000-0000-000000000001
              agent_uuid: 00000000-0000-0000-0000-000000000002
              evaluate_on_complete: true
              num_runs: 1
              timeout_minutes: 30
      responses:
        "200":
          description: Simulation started
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StartSimulationResponse"
              example:
                job_uuid: 11111111-1111-1111-1111-111111111111
                simulation_uuid: 22222222-2222-2222-2222-222222222222
                evaluation_job_uuid: null
                status: dispatched
                message: Scenario simulation task(s) dispatched successfully (1 run(s))
                simulation_uuids:
                  - 22222222-2222-2222-2222-222222222222
                run_group_uuid: null
        "401":
          $ref: "#/components/responses/Unauthorized"
        "409":
          description: >
            Scenario row exists but its `scenario.json` artifact has not been uploaded yet
            (see issue #2370). Use `POST /v1/scenarios/generate` to create a fully-formed scenario.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScenarioDefinitionMissingError"
              example:
                detail:
                  error: scenario_definition_missing
                  message: >-
                    Scenario has no definition uploaded. Use POST /v1/scenarios/generate
                    to create a fully-formed scenario.
                  scenario_uuid: 00000000-0000-0000-0000-000000000001
                  scenario_path: orgs/<org>/scenarios/<scenario>/scenario.json
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/engine/evaluate/trigger:
    post:
      tags: [Engine]
      summary: Trigger evaluation
      description: >
        Queues evaluation for a simulation run in `COMPLETED` status. Skip this if you set
        `evaluate_on_complete` on the trigger call.
      operationId: triggerEvaluation
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EngineEvaluateTriggerRequest"
            example:
              simulation_uuid: 00000000-0000-0000-0000-000000000003
      responses:
        "200":
          description: Evaluation triggered
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TriggerEvaluationResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/engine/verify/nfr:
    post:
      tags: [Engine]
      summary: Start NFR verification
      description: >
        Dispatch an async non-functional verification run against a registered agent URL.
        Poll orchestration status via `GET /v1/jobs/{job_uuid}`; fetch report JSON via
        `GET /v1/engine/verify/nfr/{simulation_uuid}`. The gateway injects
        `organization_uuid`, `workspace_uuid`, and `user_uuid` from the API key.
      operationId: startNfrVerification
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StartNfrVerificationRequest"
            example:
              agent_uuid: 00000000-0000-0000-0000-000000000002
              ownership_attested: true
              nfr_config:
                preset: smoke
                load:
                  users: 1
                  spawn_rate: 1
                  run_time_sec: 60
      responses:
        "200":
          description: NFR verification dispatched
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StartNfrVerificationResponse"
              example:
                job_uuid: 11111111-1111-1111-1111-111111111111
                simulation_uuid: 22222222-2222-2222-2222-222222222222
                status: dispatched
                message: NFR verification task dispatched successfully
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/engine/verify/nfr/domain/start:
    post:
      tags: [Engine]
      summary: Start NFR domain ownership verification
      description: >
        Issue or rotate a DNS-TXT ownership challenge for the host derived from a
        registered agent's `agent_url`. Publish the returned TXT record, then poll
        verification via `POST /v1/engine/verify/nfr/domain/check`. The gateway
        injects `organization_uuid`, `workspace_uuid`, and `user_uuid` from the
        API key.
      operationId: startNfrDomainVerification
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NfrDomainVerifyRequest"
            example:
              agent_uuid: 00000000-0000-0000-0000-000000000002
      responses:
        "200":
          description: DNS-TXT challenge issued
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NfrDomainVerifyResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Agent not found or agent_url host could not be resolved
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/engine/verify/nfr/domain/check:
    post:
      tags: [Engine]
      summary: Check NFR domain ownership verification
      description: >
        Resolve the DNS-TXT challenge for the host derived from a registered agent's
        `agent_url` and mark it verified when the record is present. The gateway
        injects `organization_uuid`, `workspace_uuid`, and `user_uuid` from the
        API key.
      operationId: checkNfrDomainVerification
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NfrDomainVerifyRequest"
            example:
              agent_uuid: 00000000-0000-0000-0000-000000000002
      responses:
        "200":
          description: Challenge status (pending or verified)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NfrDomainVerifyResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: No challenge issued yet or agent not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/engine/verify/nfr/pipeline-status:
    get:
      tags: [Engine]
      summary: Get NFR pipeline health
      description: >
        Returns NFR pipeline health for the run page: infra liveness (workers,
        evaluator), queue depth, dispatch state, and this workspace's in-flight
        runs. `workspace_uuid` is injected from the API key on proxied requests.
      operationId: getNfrPipelineStatus
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      responses:
        "200":
          description: NFR pipeline status snapshot
          content:
            application/json:
              schema:
                type: object
                properties:
                  health:
                    type: string
                  health_reason:
                    type: string
                    nullable: true
                  workers_alive:
                    type: boolean
                  evaluator_alive:
                    type: boolean
                  queue_depth:
                    type: integer
                  dispatch_paused:
                    type: boolean
                  active_runs:
                    type: array
                    items:
                      type: object
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/engine/verify/nfr/{simulation_uuid}:
    get:
      tags: [Engine]
      summary: Get NFR verification report
      description: >
        Returns NFR report JSON for a completed or in-progress verification run.
        `workspace_uuid` is injected from the API key on proxied requests.
      operationId: getNfrVerificationResult
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - name: simulation_uuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: NFR report payload
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NfrVerificationResultResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Simulation or report artifacts not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "409":
          description: NFR verification is still in progress
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/engine/verify/nfr/{simulation_uuid}/activity:
    get:
      tags: [Engine]
      summary: Get live NFR run activity
      description: >
        Returns heartbeat, worker phase, and recent event summaries for an in-flight
        NFR verification run. Use while polling `GET /v1/jobs/{job_uuid}` during
        execution. `workspace_uuid` is injected from the API key on proxied requests.
      operationId: getNfrRunActivity
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - name: simulation_uuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Live run activity snapshot
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NfrRunActivityResponse"
        "400":
          description: Simulation is not an NFR verification run
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Simulation does not belong to the authenticated workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "404":
          description: Simulation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/engine/verify/nfr/{simulation_uuid}/decision:
    get:
      tags: [Engine]
      summary: Get the release gate's decision for a run
      description: >
        What the gate decided about one run and what fired, so a pipeline can block a
        merge and say why. `inconclusive` is its own outcome rather than a pass: a run
        that could not be measured has not certified the change, and `blocks` states
        whether this policy stops the merge on it.
      operationId: getNfrRunDecision
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - name: simulation_uuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: The decision and the reasons behind it
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NfrRunDecisionResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Simulation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/engine/verify/nfr/{simulation_uuid}/artifacts/{filename}:
    get:
      tags: [Engine]
      summary: Download NFR run artifact
      description: >
        Downloads a persisted NFR artifact for a verification run (for example
        `verifyax_report.json`, `verifyax_partial.json`, or `verifyax_events.jsonl`).
        `workspace_uuid` is injected from the API key on proxied requests.
      operationId: downloadNfrArtifact
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - name: simulation_uuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: filename
          in: path
          required: true
          description: NFR artifact basename (no path segments).
          schema:
            type: string
            example: verifyax_report.json
      responses:
        "200":
          description: Artifact file contents
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "400":
          description: Invalid or unsupported artifact filename
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Simulation does not belong to the authenticated workspace
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "404":
          description: Simulation or artifact not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorMessage"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/simulations:
    get:
      tags: [Simulations]
      summary: List simulations
      description: Paginated simulation search across the workspace, including runs started by other members, with rich filters (status, dates, run group, evaluation flags).
      operationId: listSimulations
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - name: scenario_uuid
          in: query
          schema:
            type: string
            format: uuid
        - $ref: "#/components/parameters/AgentUuidQueryOptional"
        - $ref: "#/components/parameters/SimulationStatus"
        - $ref: "#/components/parameters/RunGroupUuid"
        - $ref: "#/components/parameters/DateFrom"
        - $ref: "#/components/parameters/DateTo"
        - $ref: "#/components/parameters/Search"
        - $ref: "#/components/parameters/HasSingleEvaluation"
        - $ref: "#/components/parameters/GroupByRunGroup"
        - $ref: "#/components/parameters/IncludeEvaluationJobs"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Paginated simulations
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedSimulationListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/simulations/scenarios/{scenario_uuid}:
    get:
      tags: [Simulations]
      summary: List simulations by scenario
      description: Returns simulations that ran against a specific scenario, including runs started by other members, with optional agent and status filters.
      operationId: listSimulationsByScenario
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/ScenarioUuid"
        - $ref: "#/components/parameters/AgentUuidQueryOptional"
        - $ref: "#/components/parameters/SimulationStatus"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Scenario simulations
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/SimulationListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/simulations/{id}/evaluation:
    get:
      tags: [Simulations]
      summary: Get full simulation evaluation
      description: Returns the latest evaluation payload for a simulation UUID.
      operationId: getSimulationEvaluationBySimulationId
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Full evaluation payload
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicSimulationEvaluationResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/simulations/{id}/evaluation/scores:
    get:
      tags: [Simulations]
      summary: Get simulation evaluation score breakdown
      description: Returns per-tag and overall score aggregates for the latest evaluation on a simulation.
      operationId: getSimulationEvaluationScores
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Evaluation score breakdown
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicSimulationScoreResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/simulations/scores:
    get:
      tags: [Simulations]
      summary: Get batch simulation score breakdowns
      description: Returns score breakdowns for multiple simulation UUIDs.
      operationId: getBatchSimulationScores
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - name: ids
          in: query
          required: true
          description: >
            Simulation UUIDs. Supports repeated params (`?ids=a&ids=b`) or comma separated values (`?ids=a,b`).
          schema:
            type: array
            items:
              type: string
              format: uuid
          style: form
          explode: true
      responses:
        "200":
          description: Batch score map keyed by simulation UUID
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicBatchSimulationScoresResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/simulations/evaluations/{evaluation_job_uuid}:
    get:
      tags: [Simulations]
      summary: Get simulation evaluation result
      description: Downloads evaluation results for a job UUID including aggregated scores and payloads when ready.
      operationId: getSimulationEvaluation
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - name: evaluation_job_uuid
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: "#/components/parameters/ItemIdentifier"
      responses:
        "200":
          description: Evaluation job and payload
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetEvaluationResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/simulations/{simulation_uuid}:
    get:
      tags: [Simulations]
      summary: Get simulation by UUID
      description: Returns full run detail including nested evaluation job references.
      operationId: getSimulation
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/SimulationUuid"
      responses:
        "200":
          description: Simulation details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SimulationDetailResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
    delete:
      tags: [Simulations]
      summary: Delete simulation
      description: Permanently removes a simulation record when allowed by policy.
      operationId: deleteSimulation
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/SimulationUuid"
      responses:
        "204":
          description: Simulation deleted
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/simulations/{simulation_uuid}/cancel:
    post:
      tags: [Simulations]
      summary: Cancel simulation
      description: Cancels a still-active simulation and returns a cancellation result envelope.
      operationId: cancelSimulation
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/SimulationUuid"
      responses:
        "200":
          description: Cancellation result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CancelResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/simulations/{simulation_uuid}/files:
    get:
      tags: [Simulations]
      summary: Download run artifact file
      description: >
        Downloads a binary artifact persisted for a simulation run (transcripts, evidence files, evaluation artifacts). The `path` query parameter is relative to the run directory and must start with `files/` (for example `files/messages/round_1/1_report.pdf`). Tenant scope is derived from your API key, so `organization_uuid`/`workspace_uuid` are not supplied.
      operationId: downloadSimulationFile
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/SimulationUuid"
        - name: path
          in: query
          required: true
          description: Relative path under the run directory; must start with `files/`.
          schema:
            type: string
      responses:
        "200":
          description: File contents
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "400":
          description: Invalid path (missing, traversal, or not under `files/`).
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Simulation or scenario does not belong to the authenticated workspace.
        "404":
          description: Simulation, scenario, or file not found.
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/simulations/{simulation_uuid}/output:
    get:
      tags: [Simulations]
      summary: Get simulation structured output JSON
      description: >
        Returns the stored simulation output document (`response.json` / ScenarioOutput) for
        a completed run. Tenant scope is derived from your API key — do not send
        `organization_uuid` or `workspace_uuid` query parameters on the public surface.
      operationId: getSimulationOutput
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/SimulationUuid"
      responses:
        "200":
          description: Parsed simulation output JSON
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Simulation or scenario does not belong to the authenticated workspace.
        "404":
          description: Simulation, scenario, or output file not found.
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/logs:
    get:
      tags: [Audit Logs]
      summary: List audit logs
      description: Lists organization audit log entries with optional date range, actor, and action filters.
      operationId: listAuditLogs
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - name: from
          in: query
          required: false
          schema:
            type: string
            format: date-time
          description: Start timestamp (inclusive). Must be provided with `to`.
        - name: to
          in: query
          required: false
          schema:
            type: string
            format: date-time
          description: End timestamp (inclusive). Must be provided with `from`.
        - name: actor
          in: query
          required: false
          schema:
            type: string
          description: Actor identifier (user UUID or API key prefix).
        - name: action
          in: query
          required: false
          schema:
            type: string
          description: Action filter.
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Audit log entries
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicAuditLogsResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/usage/events:
    get:
      tags: [Usage]
      summary: List usage events
      description: Lists usage telemetry events for the workspace, including events from other members (LiteLLM/compute USD actuals), with filters for product area, related resource IDs, and pagination.
      operationId: listUsageEvents
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/ProductArea"
        - $ref: "#/components/parameters/Failed"
        - $ref: "#/components/parameters/EventStartFrom"
        - $ref: "#/components/parameters/EventStartTo"
        - $ref: "#/components/parameters/SimulationUuidQueryOptional"
        - $ref: "#/components/parameters/JobUuidQueryOptional"
        - $ref: "#/components/parameters/ScenarioUuidQueryOptional"
        - $ref: "#/components/parameters/SimulationJobUuid"
        - $ref: "#/components/parameters/EvaluationJobUuid"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Usage events
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/UsageEventResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/usage/events/{event_id}:
    get:
      tags: [Usage]
      summary: Get usage event
      description: Fetches one usage event by its identifier for drill-down or reconciliation.
      operationId: getUsageEvent
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - name: event_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Usage event
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsageEventResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/usage/calls:
    get:
      tags: [Usage]
      summary: List usage calls
      description: Lists model or provider call rows (token usage, latency) scoped by event or model filters.
      operationId: listUsageCalls
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/EventUuid"
        - $ref: "#/components/parameters/ProviderName"
        - $ref: "#/components/parameters/ModelName"
        - $ref: "#/components/parameters/CallStartFrom"
        - $ref: "#/components/parameters/CallStartTo"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Usage calls
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/UsageCallResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/validation/validate:
    post:
      tags: [Scenarios]
      summary: Validate JSON payload against scenario schema
      description: >
        Validates a JSON **string** against the scenario input model (`schema: scenario`, default).
        Always returns **200** with a human-readable `result` field (`VALID` / `INVALID` and error
        lines); malformed JSON or schema violations are expressed in `result`, not via 4xx.
      operationId: validateJsonPayload
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ValidateRequest"
            example:
              json: '{"name":"sample","enabled":true}'
      responses:
        "200":
          description: Validation result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidateResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/validation/schema/scenario:
    get:
      tags: [Scenarios]
      summary: Get scenario JSON schema
      description: Returns the canonical scenario JSON Schema document for client-side or offline validation.
      operationId: getValidationSchemaScenario
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      responses:
        "200":
          $ref: "#/components/responses/ProxySuccess"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/client-tags/register-qna:
    post:
      tags: [Client Tags]
      summary: Register QnA client skill tag
      description: >
        Registers a per-organization QnA skill tag and persists the benchmark payload to
        master-data storage (`tags/org/<orgId>/qna/...` and `client_tags.jsonl`). Used by
        Q&amp;A Studio export and custom benchmark pipelines. Tenant UUIDs are injected from
        the API key. Set `dry_run: true` to validate without writing storage.
      operationId: registerClientQnaTag
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RegisterClientQnaRequest"
            example:
              skill_tag: latin_dance_qna
              description: Latin dance history recall questions extracted from studio QnA.
              testing_method: Ask factual recall questions from the registered QnA benchmark.
              qna:
                questions:
                  - question: What is salsa?
                    correct_answer: A partner dance with Afro-Cuban roots.
                    is_hallucination_trap: false
              dry_run: false
      responses:
        "200":
          description: Tag registered or dry-run preview
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RegisterClientQnaResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "409":
          description: Skill tag name collides with a global catalogue tag
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "422":
          description: Validation error (payload, tag name, or QnA content)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/tags:
    get:
      tags: [Skill Tags]
      summary: List skill tags
      description: >
        Returns the scenario skill tag catalogue used by scenario generation, as a bare JSON array. The organization is derived from your API key, so the response is the global catalogue merged with your organization's custom tag overlay. Custom (org-scoped) tags are flagged with `custom: true`; global tags carry `custom: false`.

        Before `POST /v1/scenarios/generate`, filter for tags whose `allowed_scenario_types` includes your `scenario_type`. Tag existence and compatibility are validated asynchronously by the `scenario_creation` worker, so invalid tags yield **201** then a **FAILED** job, not a synchronous **400**.
      operationId: listSkillTags
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      responses:
        "200":
          description: Tag catalogue (global merged with the API key organization's custom overlay)
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/SkillTag"
              example:
                - name: empathy
                  category: social
                  description: Recognize and respond to emotional cues.
                  benchmark_family: null
                  allowed_scenario_types: [info_exchange, interview]
                  custom: false
                - name: fraud-detection
                  category: safety
                  description: Org-defined fraud probe.
                  benchmark_family: null
                  allowed_scenario_types: [info_exchange]
                  custom: true
        "400":
          description: API key context is missing an organization
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  message:
                    type: string
                  statusCode:
                    type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/gold-standards:
    get:
      tags: [Gold Standards]
      summary: List Favorites
      description: Lists the workspace's pinned baselines, newest first. Filter by agent to find the Favorite a run should be compared against.
      operationId: listGoldStandards
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/AgentUuidQueryOptional"
        - name: include_band
          in: query
          description: Compute each Favorite's capability band. Off by default because it reads one stored evaluation per matching run.
          schema:
            type: boolean
            default: false
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Favorites
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GoldStandardsListResponse"
        "422":
          description: limit or offset outside the allowed range
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"
    post:
      tags: [Gold Standards]
      summary: Pin a run group as a Favorite
      description: Pins a completed run group as the baseline for its agent. The run group must be evaluated; its scenarios and agent are read from its members.
      operationId: createGoldStandard
      parameters:
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GoldStandardCreateRequest"
            examples:
              batch:
                summary: Pin a whole run group
                value:
                  run_group_uuid: 2c74f6f2-db58-4b2c-9ac0-a9c3d4b86e50
                  label: Nightly baseline
      responses:
        "201":
          description: Favorite
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GoldStandardResponse"
        "400":
          description: Run group not found, or not usable as a baseline
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "409":
          description: This run is already pinned as a Favorite
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "422":
          description: Request body failed validation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

  /v1/gold-standards/{gold_uuid}:
    patch:
      tags: [Gold Standards]
      summary: Move a Favorite to a newer run
      description: >
        Repoints an existing Favorite at a newer run group, or renames it. `pinned_at` is not
        updatable: it is the window deciding which later runs belong to the baseline, so replacing
        the pin must not discard the runs collected since the Favorite was saved.
      operationId: updateGoldStandard
      parameters:
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/GoldUuid"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GoldStandardUpdateRequest"
            examples:
              repin:
                summary: Point the Favorite at last night's run
                value:
                  run_group_uuid: b4b58c09-fce5-48f4-9707-5eeccae786d3
      responses:
        "200":
          description: Favorite
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GoldStandardResponse"
        "400":
          description: New run group not found, or it changes the Favorite between a single run and a batch
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "404":
          description: Favorite not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "409":
          description: The target run is already pinned as a Favorite
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "422":
          description: Request body failed validation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/GatewayError"

components:
  securitySchemes:
    BearerApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: "Send `Authorization: Bearer <api_key>`."

  parameters:
    XRequestId:
      name: X-Request-ID
      in: header
      required: false
      schema:
        type: string
      description: Optional trace identifier forwarded to upstream.
    Limit:
      name: limit
      in: query
      description: Maximum number of records to return (pagination page size).
      schema:
        type: integer
        minimum: 1
      example: 20
    Offset:
      name: offset
      in: query
      description: Number of records to skip from the beginning of the sorted result set.
      schema:
        type: integer
        minimum: 0
      example: 0
    AgentUuidQueryOptional:
      name: agent_uuid
      in: query
      required: false
      description: Restrict verification runs or lists to this registered workspace agent UUID.
      schema:
        type: string
        format: uuid
    SimulationUuidQueryOptional:
      name: simulation_uuid
      in: query
      required: false
      description: Filter usage events linked to a specific verification run.
      schema:
        type: string
        format: uuid
    JobUuidQueryOptional:
      name: job_uuid
      in: query
      required: false
      description: Filter usage events to those emitted for this async job UUID.
      schema:
        type: string
        format: uuid
    ScenarioUuidQueryOptional:
      name: scenario_uuid
      in: query
      required: false
      description: Filter usage events or other lists to this scenario UUID.
      schema:
        type: string
        format: uuid
    CreatedAfter:
      name: created_after
      in: query
      description: Return corpora created at or after this instant (RFC 3339 date-time).
      schema:
        type: string
        format: date-time
    CreatedBefore:
      name: created_before
      in: query
      description: Return corpora created at or before this instant (RFC 3339 date-time).
      schema:
        type: string
        format: date-time
    Tags:
      name: tags
      in: query
      required: false
      description: Match corpora having any of these tag strings (OR semantics; repeated query params).
      schema:
        type: array
        items:
          type: string
      style: form
      explode: true
    ScenarioType:
      name: scenario_type
      in: query
      description: Filter by scenario type (`info_exchange` or `interview`).
      schema:
        type: string
        enum: [info_exchange, interview]
    Status:
      name: status
      in: query
      description: >
        Repeatable filter on scenario row `status`. Values: `INIT`, `PROCESSING`, `SUCCESS`,
        `FAILED`, `CANCELLED`. `SUCCESS` means the scenario is materialised and runnable
        (`definition_bucket_path` set and scenario JSON uploaded). Use `scenario_creation_job_status`
        on list/get responses to track in-flight generation jobs.
      schema:
        type: array
        items:
          type: string
          enum: [INIT, PROCESSING, SUCCESS, FAILED, CANCELLED]
      style: form
      explode: true
    CurrentStatus:
      name: current_status
      in: query
      description: >
        Filter jobs whose `current_status` equals this value (exact string match). Values:
        `PENDING`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`.
      schema:
        type: string
        enum: [PENDING, PROCESSING, COMPLETED, FAILED, CANCELLED]
    AgentType:
      name: agent_type
      in: query
      description: Filter by connector type (`A2A`, `API`, `DIRECTLINE`, `EXTENSION`, `MCP`).
      schema:
        type: string
        enum: [A2A, API, DIRECTLINE, EXTENSION, MCP]
    NewName:
      name: new_name
      in: query
      description: Optional new display name when copying a scenario in-place.
      schema:
        type: string
    NumRuns:
      name: num_runs
      in: query
      required: true
      description: >
        Defined for forward compatibility; not referenced by current gateway paths. If wired
        by a proxy extension, expresses a run-count hint as an integer 1–10.
      schema:
        type: integer
        minimum: 1
        maximum: 10
    SimulationStatus:
      name: status
      in: query
      description: >
        Filter verification runs whose `status` equals this value. Values: `CREATED`,
        `IN_PROGRESS`, `COMPLETED`, `FAILED`, `CANCELLED`.
      schema:
        type: string
        enum: [CREATED, IN_PROGRESS, COMPLETED, FAILED, CANCELLED]
    RunGroupUuid:
      name: run_group_uuid
      in: query
      description: Return runs that belong to this batched / multi-scenario run group UUID.
      schema:
        type: string
        format: uuid
    DateFrom:
      name: date_from
      in: query
      description: Lower bound on run `created_at` or comparable timestamp (inclusive).
      schema:
        type: string
        format: date-time
    DateTo:
      name: date_to
      in: query
      description: Upper bound on run `created_at` or comparable timestamp (inclusive).
      schema:
        type: string
        format: date-time
    Search:
      name: search
      in: query
      description: Free-text filter applied to run metadata or identifiers (upstream-defined).
      schema:
        type: string
    HasSingleEvaluation:
      name: has_single_evaluation
      in: query
      description: When true, restrict to runs that have exactly one linked evaluation job.
      schema:
        type: boolean
    GroupByRunGroup:
      name: group_by_run_group
      in: query
      description: When true, collapse list results to one row per `run_group_uuid` (upstream semantics).
      schema:
        type: boolean
    IncludeEvaluationJobs:
      name: include_evaluation_jobs
      in: query
      description: When true, embed evaluation job summaries on each listed run (heavier payload).
      schema:
        type: boolean
    ItemIdentifier:
      name: item_identifier
      in: query
      description: Optional opaque item key when downloading evaluation payloads (upstream-specific).
      schema:
        type: string
    ProductArea:
      name: product_area
      in: query
      description: Filter usage events by billing product area code (ENGINE, SCENARIO_CREATION, etc.).
      schema:
        type: string
    Failed:
      name: failed
      in: query
      description: When set, return only failed (`true`) or only successful (`false`) usage events.
      schema:
        type: boolean
    EventStartFrom:
      name: event_start_from
      in: query
      description: Lower bound on usage event start timestamp (inclusive).
      schema:
        type: string
        format: date-time
    EventStartTo:
      name: event_start_to
      in: query
      description: Upper bound on usage event start timestamp (exclusive — events with `event_start_timestamp` equal to this value are excluded).
      schema:
        type: string
        format: date-time
    SimulationJobUuid:
      name: simulation_job_uuid
      in: query
      description: Filter usage events tied to this scenario-creation or simulation job identifier string.
      schema:
        type: string
    EvaluationJobUuid:
      name: evaluation_job_uuid
      in: query
      description: Filter usage events associated with this evaluation job UUID.
      schema:
        type: string
        format: uuid
    EventUuid:
      name: event_uuid
      in: query
      description: When listing usage calls, restrict to rows for this parent usage event UUID.
      schema:
        type: string
        format: uuid
    ProviderName:
      name: provider_name
      in: query
      description: Filter usage calls by LLM provider name (e.g. OpenAI, Anthropic).
      schema:
        type: string
    ModelName:
      name: model_name
      in: query
      description: Filter usage calls by model id or display name string.
      schema:
        type: string
    CallStartFrom:
      name: call_start_from
      in: query
      description: Lower bound on per-call start timestamp when listing usage calls (inclusive).
      schema:
        type: string
        format: date-time
    CallStartTo:
      name: call_start_to
      in: query
      description: Upper bound on per-call start timestamp when listing usage calls (exclusive — calls with `call_start_timestamp` equal to this value are excluded).
      schema:
        type: string
        format: date-time
    ScenarioUuid:
      name: scenario_uuid
      in: path
      required: true
      description: >
        UUID of a scenario in the authenticated workspace. Use the `uuid` field
        from scenario create, generate, list, or get responses (not a response field named
        `scenario_uuid`).
      schema:
        type: string
        format: uuid
    JobUuid:
      name: job_uuid
      in: path
      required: true
      description: UUID of an async job (creation, engine, evaluation, etc.) in the workspace.
      schema:
        type: string
        format: uuid
    AgentUuid:
      name: agent_uuid
      in: path
      required: true
      description: UUID of a registered workspace agent.
      schema:
        type: string
        format: uuid
    GoldUuid:
      name: gold_uuid
      in: path
      required: true
      description: UUID of a Favorite, as returned by `GET /v1/gold-standards`.
      schema:
        type: string
        format: uuid
    SimulationUuid:
      name: simulation_uuid
      in: path
      required: true
      description: >
        Identifier of a **verification run**. Use the `simulation_uuid` returned when starting
        a run or listed under `GET /v1/simulations`.
      schema:
        type: string
        format: uuid

  responses:
    Unauthorized:
      description: >
        **401** — API key missing, malformed `Authorization` header, or key not recognised.
        Response body uses `ErrorMessage` when JSON is returned.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorMessage"
    GatewayError:
      description: >
        **5xx / proxy error** — the gateway could not obtain a successful response from
        verifyax-api, or an internal gateway fault occurred. See `detail` in `GatewayError`.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/GatewayError"
    TooManyRequests:
      description: >
        **429** — workspace rate limit exceeded. Honor `Retry-After` (seconds) before retrying.
        All public responses include `RateLimit-Limit`, `RateLimit-Remaining`, and `RateLimit-Reset`.
      content:
        application/json:
          schema:
            type: object
            required: [error, message, statusCode]
            properties:
              error:
                type: string
                example: Too Many Requests
              message:
                type: string
              statusCode:
                type: integer
                example: 429
    ProxySuccess:
      description: Upstream verifyax-api JSON response proxied unchanged by the gateway.
      content:
        application/json:
          schema:
            type: object
            description: Arbitrary JSON object returned by verifyax-api for this operation.
            additionalProperties: true

  schemas:
    ErrorMessage:
      type: object
      description: >
        Gateway-generated JSON error. Always includes `message`. Some gateway routes also
        include `error` or `success`. verifyax-api rejections use ApiErrorResponse instead.
      properties:
        message:
          type: string
          description: Human-readable error summary suitable for display or logs.
      additionalProperties: true

    GatewayError:
      type: object
      description: Gateway-side failure when the proxy cannot complete a request to verifyax-api.
      properties:
        detail:
          type: string
          description: Error detail string (may include upstream status or diagnostic text).
      required: [detail]

    ScenarioDefinitionMissingError:
      type: object
      description: >
        Structured 409 returned by `/v1/engine/simulate/scenario` and
        `/v1/engine/workspace-credit-preview` when the scenario row exists but its
        `scenario.json` artifact has not been uploaded yet. The frontend should key off
        `detail.error == "scenario_definition_missing"` to gate run-start affordances.
      properties:
        detail:
          type: object
          required: [error, message, scenario_uuid]
          properties:
            error:
              type: string
              enum: [scenario_definition_missing]
              description: Stable machine-readable error code.
            message:
              type: string
              description: Human-readable hint suggesting POST /v1/scenarios/generate.
            scenario_uuid:
              type: string
              format: uuid
              description: The scenario whose definition is missing.
            scenario_path:
              type: string
              description: Storage path the engine probed (debug aid; subject to change).
      required: [detail]

    OneTimeLoginTokenResponse:
      type: object
      description: One-time browser login hand-off; token must be redeemed quickly and never logged verbatim.
      properties:
        one-time-login-token:
          type: string
          description: Opaque single-use token string; pass only in URL fragment per `example_links`.
        example_links:
          type: object
          description: Example user-webapp URLs with the token in the fragment (after `#`).
          properties:
            home:
              type: string
              format: uri
              description: Example deep link to the app home route carrying the token fragment.
            workbench:
              type: string
              format: uri
              description: Example deep link to the workbench route carrying the token fragment.

    TicketCreate:
      type: object
      description: Body for `POST /v1/tickets`. The user id and email are derived from the API key, not the body.
      required: [message]
      properties:
        message:
          type: string
          minLength: 1
          description: Ticket body text (non-empty).
        type:
          type: string
          enum: [feedback, help_support]
          default: help_support
          description: Ticket category. Defaults to `help_support` when omitted.

    TicketResponse:
      type: object
      description: Envelope returned after a ticket is created.
      properties:
        success:
          type: boolean
          description: True when the ticket was stored.
        message:
          type: string
          description: Human-readable status message.
        data:
          $ref: "#/components/schemas/Ticket"

    Ticket:
      type: object
      description: A stored ticket (backed by a user message row).
      properties:
        id:
          type: string
          format: uuid
          description: Ticket identifier.
        userId:
          type: string
          format: uuid
          description: Id of the user who opened the ticket.
        type:
          type: string
          enum: [feedback, help_support]
          description: Ticket category.
        email:
          type: string
          format: email
          description: Email associated with the user when the ticket was opened.
        message:
          type: string
          description: Ticket body text.
        replied:
          type: boolean
          description: Whether the ticket has been marked as handled.
        createdAt:
          type: string
          format: date-time
          description: Creation timestamp.

    DeviceEnrollRequest:
      type: object
      description: Body for `POST /v1/devices/enroll`. Organization, workspace, and user context are derived from the API key and the enrollment code, not the body.
      required: [publicKey, enrollmentCode]
      properties:
        publicKey:
          description: >
            Device public key as a JWK, supplied as either a JSON object or a JSON string. Must be an OKP Ed25519 key or an EC P-256 key; anything else is rejected. Only the canonical key form and its thumbprint are stored.
          oneOf:
            - type: object
            - type: string
        enrollmentCode:
          type: string
          description: One-time enrollment code minted from a logged-in Workbench session.
        label:
          type: string
          description: Optional human-readable device label.

    DeviceEnrollResponse:
      type: object
      description: Envelope returned after a device signing key is enrolled.
      properties:
        success:
          type: boolean
          description: True when the device was registered.
        device:
          type: object
          properties:
            id:
              type: string
              description: Device identifier.
            jkt:
              type: string
              description: RFC 7638 JWK SHA-256 thumbprint (base64url); the stable device fingerprint.
            alg:
              type: string
              enum: [EdDSA, ES256]
              description: Signature algorithm derived from the key type.
            expiresAt:
              type: string
              format: date-time
              description: ISO 8601 timestamp when the enrollment expires.

    ScenarioCreateRequest:
      type: object
      description: >
        Body for `POST /v1/scenarios` (synchronous shell). `scenario_type` is optional
        (`info_exchange` or `interview`); omit for empty shells before `PATCH .../artifacts`.
        `corpus_uuid`
        and other legacy L1 game fields return **400**. Response `job_uuid` is null. Developer-only.
      required: [name]
      properties:
        name:
          type: string
          description: Display name (unique per workspace after normalisation upstream).
        description:
          type: string
        scenario_type:
          type: string
          enum: [info_exchange, interview]
          description: Dialogue kind stored on the row when known at create time.
        tags:
          type: array
          items:
            type: string
          description: Optional workspace tags stored on the scenario row.
        creation_parameters:
          type: object
          additionalProperties: true
          description: Optional opaque creation metadata persisted on the scenario row.

    GenerateScenarioFromQnaRequest:
      type: object
      required: [name, questions]
      description: >
        Body for `POST /v1/scenarios/generate-from-qna`. Tenant UUIDs are injected by the
        gateway from the API key.
      properties:
        name:
          type: string
          description: Scenario record name.
        description:
          type: string
          nullable: true
          description: Optional scenario description.
        context_prompt:
          type: string
          nullable: true
          description: Optional NPC/interview setting.
        questions:
          type: array
          minItems: 1
          maxItems: 100
          items:
            $ref: "#/components/schemas/QnaQuestionItem"

    QnaQuestionItem:
      type: object
      required: [question, correct_answer]
      properties:
        question:
          type: string
        correct_answer:
          type: string
        is_hallucination_trap:
          type: boolean
          default: false

    TagRecommendationPublicRequest:
      type: object
      required: [scenario_type]
      description: >
        Body for `POST /v1/scenarios/tag-recommendation`. At least one of `context_prompt` or
        `agent_uuid` is required. Tenant UUIDs are injected by the gateway from the API key.
      properties:
        scenario_type:
          type: string
          enum: [info_exchange, interview]
          description: Scenario dialogue kind used to filter eligible skill tags.
        context_prompt:
          type: string
          nullable: true
          description: Optional scenario context (same meaning as scenario `context_prompt`).
        agent_uuid:
          type: string
          format: uuid
          nullable: true
          description: Optional workspace agent UUID; A2A agents may trigger a live agent-card fetch.

    TagRecommendationAnnotation:
      type: object
      required: [reasons, use_count]
      properties:
        reasons:
          type: array
          items:
            type: string
            enum: [underutilized, problematic]
          description: History-based reasons this tag was prioritized.
        use_count:
          type: integer
          description: Prior simulations for this agent that included the tag.
        avg_grade:
          type: number
          nullable: true
          description: Mean of recent evaluation grades (1–5) when available.

    TagRecommendationData:
      type: object
      required:
        - skill_tags
        - recommended_scenario_type
        - scenario_type_reasoning
        - skill_tags_reasoning
      properties:
        skill_tags:
          type: array
          items:
            type: string
          description: Skill tags allowed for the selected scenario_type.
        recommended_scenario_type:
          type: string
          description: Suggested scenario type (may match or refine the request).
        scenario_type_reasoning:
          type: string
        skill_tags_reasoning:
          type: string
        tag_annotations:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/TagRecommendationAnnotation"
          nullable: true
          description: Optional per-tag history annotations for recommended tags.
        warnings:
          type: array
          items:
            type: string
          nullable: true

    TagRecommendationPublicResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
        data:
          $ref: "#/components/schemas/TagRecommendationData"

    TagSearchPublicRequest:
      type: object
      required: [scenario_type, query]
      description: >
        Body for `POST /v1/scenarios/tag-search`. Tenant UUIDs are injected by the gateway from
        the API key.
      properties:
        scenario_type:
          type: string
          enum: [info_exchange, interview]
        query:
          type: string
          minLength: 2
          description: Natural-language search text (trimmed; minimum 2 characters).
        limit:
          type: integer
          minimum: 1
          maximum: 100
          nullable: true
          description: Max tags to return (default 50).

    TagSearchData:
      type: object
      required: [skill_tags]
      properties:
        skill_tags:
          type: array
          items:
            type: string
          description: Skill tags ranked by embedding similarity to the query.

    TagSearchPublicResponse:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
        data:
          $ref: "#/components/schemas/TagSearchData"

    JobCancelResponse:
      type: object
      properties:
        message:
          type: string
          description: Human-readable cancellation acknowledgement.

    SimulationScenarioCreateRequest:
      type: object
      description: >
        Body for POST /v1/scenarios/generate (maps to verifyax-api SimulationScenarioCreate).
        The gateway injects organization_uuid, workspace_uuid, and user_uuid from the API key.
        Tag list size is capped at 5 for info_exchange and 1 for interview.

        **Discover tags:** `GET /v1/tags` (see Skill Tags). Pass each tag's `name` in
        `tags` / `tag_pool`. Filter by `allowed_scenario_types` for your `scenario_type` before
        calling generate — missing, unknown, or incompatible tags are rejected synchronously
        with **400** before a `scenario_creation` job is queued.

        **Single vs batch:** Single mode sends name, scenario_type, tags, and optional
        context_prompt. Batch mode (num_scenarios > 1) adds tag_pool and optional include_tags,
        total_tags, and max_tags_per_npc. Internal engine, model, and DAG knobs are not part of
        the public contract; the gateway strips them before forwarding.
      required: [name, scenario_type]
      anyOf:
        - required: [tags]
          properties:
            num_scenarios:
              maximum: 1
        - required: [num_scenarios, tag_pool]
          properties:
            num_scenarios:
              minimum: 2
      properties:
        name:
          type: string
          description: Scenario name (workspace-unique after normalisation).
        scenario_type:
          type: string
          enum: [info_exchange, interview]
          default: info_exchange
          description: Multi-agent `info_exchange` (default) or 1-on-1 `interview`. Controls tag caps and NPC layout.
        context_prompt:
          type: string
          description: Optional context to guide generation (info_exchange and interview).
        tags:
          type: array
          minItems: 1
          items:
            type: string
          description: >-
            Explicit skill tag `name` values from GET /v1/tags. At most 5
            (info_exchange) or 1 (interview). Each tag's `allowed_scenario_types` must include
            your `scenario_type`. Required for single-scenario generation; batch requests use
            `tag_pool`.
        num_scenarios:
          type: integer
          minimum: 1
          maximum: 50
          default: 1
          description: >-
            Batch size. 1 = single scenario. Greater than 1 requires tag_pool and returns
            uuid, batch_uuid, and batch_scenario_uuids on the response.
        tag_pool:
          type: array
          items:
            type: string
          description: >-
            Required when num_scenarios > 1; universe of tag `name` values to sample from.
            Each entry must allow your `scenario_type` per the tag catalogue.
        include_tags:
          type: array
          items:
            type: string
          description: >-
            Batch only; tags required in every scenario. Must be a subset of tag_pool.
            total_tags must be at least the number of distinct include_tags.
        total_tags:
          type: integer
          minimum: 1
          maximum: 5
          description: >-
            Batch only; tags drawn per scenario from tag_pool (default len(tag_pool)).
            Capped at 5 info_exchange / 1 interview.
        max_tags_per_npc:
          type: integer
          minimum: 1
          maximum: 1
          description: >-
            Batch only; tags per NPC. Must be 1 - every NPC holds exactly one skill tag,
            so NPC count = total_tags. Ignored for interview (single NPC). Default 1 when omitted.
        objective_steps:
          type: array
          minItems: 1
          description: >-
            Advanced; interview scenarios with num_scenarios=1 and explicit tags only.
            Author-supplied NPC objective steps used verbatim and in order instead of the
            generated objective queue, giving deterministic control over the NPC's beat
            ordering. Gated by a server-side feature flag; rejected when the flag is disabled.
          items:
            type: object
            required: [description, context]
            properties:
              description:
                type: string
                minLength: 1
                description: Step objective narrative.
              personality:
                type: string
                description: >-
                  NPC personality for this step; defaults to the generated NPC personality
                  when omitted.
              context:
                type: string
                minLength: 1
                description: >-
                  Additional context for this step. Should instruct the NPC when to emit the
                  completion marker; the step advances on the marker or on
                  max_rounds_per_objective.
              max_rounds_per_objective:
                type: integer
                minimum: 1
                maximum: 50
                description: Max rounds for this step before force-advancing.
              tags:
                type: array
                items:
                  type: string
                description: Tags for this step.

    ScenarioUpdateRequest:
      type: object
      description: >
        Partial update for scenario display metadata. Only `name` and `description` are
        writable via PATCH; skill tags live in `scenario.json` and row `tags` / `valid_versions`
        are set by generation or copy, not this endpoint.
      properties:
        name:
          type: string
          description: New display name (workspace uniqueness enforced upstream).
        description:
          type: string
          description: New scenario description text.

    DirectlineAgentParameters:
      type: object
      description: >
        Copilot Studio configuration nested under `agent_parameters.directline` when
        `agent_type` is `DIRECTLINE`. Set `auth_mode` to match the agent's Security
        settings: `secret` (Direct Line / no authentication), `microsoft` (Entra SSO),
        or `manual` (custom OAuth).
      properties:
        auth_mode:
          type: string
          enum: [secret, microsoft, manual]
          default: secret
          description: Copilot Studio authentication mode.
        secret:
          type: string
          minLength: 1
          description: Direct Line secret (required when auth_mode is `secret`).
        region:
          type: string
          default: global
          enum: [global, europe, india, unitedstates, asia, australia, northamerica]
          description: Direct Line deployment region (secret mode).
        environment_id:
          type: string
          description: Power Platform environment ID (microsoft/manual modes).
        agent_identifier:
          type: string
          description: Copilot Studio agent schema name (microsoft/manual modes).
        tenant_id:
          type: string
          description: Entra tenant ID (microsoft mode, OBO).
        client_id:
          type: string
          description: Entra application client ID (microsoft mode, OBO).
        client_secret:
          type: string
          description: Entra application client secret (microsoft mode, OBO).
        use_obo:
          type: boolean
          default: true
          description: >-
            When true (default for microsoft), exchange the user token on-behalf-of.
            Set false for manual OAuth when the user token is already Copilot-scoped.
        base_url:
          type: string
          format: uri
          nullable: true
          description: Optional Direct Line endpoint override (secret mode).
      required: []

    AgentCreateRequest:
      type: object
      description: >
        Registers a new agent endpoint in the workspace agent registry. For Copilot Studio,
        use `agent_type: DIRECTLINE` and nest credentials in `agent_parameters.directline`.
      required: [name]
      properties:
        name:
          type: string
          description: >-
            Display name for the registered agent. Must be unique within the workspace;
            if the requested name is taken, the server assigns the next available
            suffix (e.g. My Agent (1)).
        description:
          type: string
          description: Optional notes about the agent’s role or integration.
        agent_url:
          type: string
          format: uri
          description: >
            Endpoint URL VerifyAX calls. For A2A/API agents, your deployment URL. For
            `DIRECTLINE`, the regional Direct Line host (see `DirectlineAgentParameters.region`).
        agent_type:
          type: string
          enum: [A2A, API, DIRECTLINE, EXTENSION, MCP]
          default: A2A
          description: >
            Connector type. Use `DIRECTLINE` for Microsoft Copilot Studio over Bot Framework
            Direct Line 3.0. Defaults to `A2A`.
        agent_parameters:
          type: object
          additionalProperties: true
          description: >
            Connector-specific settings. For `DIRECTLINE`, include a `directline` object (see
            `DirectlineAgentParameters`). A2A/API agents use auth, timeout, and card keys
            documented in the Connect Agents guide.

    AgentUpdateRequest:
      type: object
      description: Partial update for an existing agent; only send fields that should change.
      properties:
        name:
          type: string
          description: >-
            New display name for the agent. Must be unique within the workspace;
            if the requested name is taken, the server assigns the next available
            suffix (e.g. My Agent (1)).
        description:
          type: string
          description: Updated agent description text.
        agent_url:
          type: string
          format: uri
          description: Updated endpoint URL when the deployment location changes.
        agent_type:
          type: string
          enum: [A2A, API, DIRECTLINE, EXTENSION, MCP]
          description: Switch connector type when migrating how VerifyAX talks to the agent.
        agent_parameters:
          type: object
          additionalProperties: true
          description: >
            Merged or replaced connector settings (upstream merge rules apply). For Direct Line,
            update `directline.secret` or `directline.region` here.

    GoldStandardResponse:
      type: object
      description: A pinned baseline run group for one agent.
      properties:
        uuid:
          type: string
          format: uuid
          description: Favorite UUID.
        organization_uuid:
          type: string
          format: uuid
        workspace_uuid:
          type: string
          format: uuid
        agent_uuid:
          type: string
          format: uuid
          description: Agent the baseline belongs to, read from the pinned run.
        run_group_uuid:
          type: string
          format: uuid
          description: The run group being used as the baseline.
        label:
          type: string
          nullable: true
          description: Display name.
        pinned_by_user_uuid:
          type: string
          format: uuid
        pinned_at:
          type: string
          format: date-time
          description: When the Favorite was first saved. Not changed by a repin.
        scenario_uuids:
          type: array
          items:
            type: string
            format: uuid
          description: Scenarios covered by the pinned run.
        scenario_batch_uuid:
          type: string
          format: uuid
          nullable: true
        timeout_minutes:
          type: integer
          nullable: true
        stale_scenario_uuids:
          type: array
          items:
            type: string
            format: uuid
          description: Scenarios whose definition changed since the Favorite was pinned.
        capability_band:
          allOf:
            - $ref: "#/components/schemas/CapabilityBandResponse"
          nullable: true
          description: Populated on a single read, and on list only when include_band is set.
    GoldStandardsListResponse:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/GoldStandardResponse"
        total:
          type: integer
    GoldStandardCreateRequest:
      type: object
      required: [run_group_uuid]
      description: Tenancy and the pinning user are taken from the API key.
      properties:
        run_group_uuid:
          type: string
          format: uuid
          description: Run group to pin, or a simulation uuid when solo is true.
        label:
          type: string
          nullable: true
        solo:
          type: boolean
          default: false
          description: Pin only the named simulation rather than expanding the batch it leads.
    GoldStandardUpdateRequest:
      type: object
      description: Every field is optional; omitted fields are left as they are.
      properties:
        run_group_uuid:
          type: string
          format: uuid
          description: New baseline run group, or a simulation uuid when solo is true.
        label:
          type: string
          nullable: true
        solo:
          type: boolean
          default: false
    CapabilityBandResponse:
      type: object
      description: Capability band for a Favorite. band is null while the baseline is Pending.
      properties:
        band:
          type: integer
          nullable: true
          description: Band 1-5, null until confidence is met.
        band_name:
          type: string
          description: Robust, Advanced, Functional, Basic, Limited, or Pending.
        observation_count:
          type: integer
        sigma:
          type: number
          nullable: true
        performance:
          type: number
          nullable: true
          description: Mean of the aggregate per-tag means.
        consistency_across_skills:
          type: number
          nullable: true
          description: Weakest aggregate per-tag mean.
        tag_aggregates:
          type: object
          additionalProperties:
            type: number
          description: Per-tag mean across the baseline's observations.
        is_confident:
          type: boolean
          description: Whether the confidence gate passed.
        evaluation_tags:
          type: array
          items:
            type: string
          description: Tag set this baseline matches on.
    ApiErrorResponse:
      type: object
      description: Error envelope returned by verifyax-api for a rejected request. Gateway-generated errors such as 401 use ErrorMessage instead and carry only `message`.
      required: [error, message, request_id]
      properties:
        error:
          type: string
          description: Machine-readable code, such as bad_request, not_found, conflict or validation_error.
        message:
          type: string
          description: Human-readable summary.
        request_id:
          type: string
          format: uuid
          description: Identifier for this request, useful when reporting the failure.
        details:
          type: object
          additionalProperties: true
          description: Present on validation_error, carrying an `errors` array of per-field failures.
    AgentResponse:
      type: object
      description: Registered agent row returned from create/get/list endpoints.
      properties:
        uuid:
          type: string
          format: uuid
          description: Agent registry UUID.
        name:
          type: string
          description: Display name.
        description:
          type: string
          nullable: true
          description: Optional notes stored with the agent.
        user_uuid:
          type: string
          format: uuid
          description: Creating user UUID (tenant context).
        workspace_uuid:
          type: string
          format: uuid
          description: Owning workspace UUID.
        agent_url:
          type: string
          format: uri
          nullable: true
          description: Endpoint URL used by the connector for runs.
        agent_type:
          type: string
          description: Connector type (`A2A`, `API`, `DIRECTLINE`, `EXTENSION`, or `MCP`).
        agent_parameters:
          type: object
          nullable: true
          additionalProperties: true
          description: >
            Connector-specific configuration blob. Direct Line agents store credentials under
            `directline`.
        created_at:
          type: string
          format: date-time
          description: Registration timestamp (RFC 3339).
        updated_at:
          type: string
          format: date-time
          description: Last modification timestamp (RFC 3339).

    EngineInterrogateScenarioRequest:
      type: object
      description: >
        Provide **exactly one** of `scenario_uuid` or `scenario_uuids`
        (batch of up to 50). When batching, runs share a **run group** for correlation upstream.
        For a single scenario, set `scenario_uuid` to the `uuid` returned by create or generate.
      properties:
        scenario_uuid:
          type: string
          format: uuid
          description: >
            Single scenario to run against — the scenario row’s `uuid` from create or generate
            responses.
        scenario_uuids:
          type: array
          maxItems: 50
          items:
            type: string
            format: uuid
          description: >
            Ordered list of scenario UUIDs to run in one request. Mutually exclusive with
            `scenario_uuid`; must contain at least one UUID when used.
        agent_uuid:
          type: string
          format: uuid
          description: >
            Registered workspace agent to exercise. The gateway resolves URL, connector
            strategy, and auth from this agent before starting the simulation.
        evaluate_on_complete:
          type: boolean
          default: true
          description: When true, queue evaluation automatically after the run completes successfully.
        num_runs:
          type: integer
          minimum: 1
          maximum: 10
          default: 1
          description: Number of parallel runs for robustness. Defaults to 1.
        timeout_minutes:
          type: integer
          minimum: 1
          maximum: 240
          description: >-
            Optional wall-clock budget in minutes for this verification run. Overrides
            simulation_config.timeout_minutes in the scenario definition for billing and
            playground execution. Workbench UI presets 5–60 min.

    EngineEvaluateTriggerRequest:
      type: object
      description: >
        Queues a standalone evaluation for a finished verification run. Tenant UUIDs are
        injected by the gateway on POST like other engine bodies.
      required: [simulation_uuid]
      properties:
        simulation_uuid:
          type: string
          format: uuid
          description: Verification run to evaluate (`simulation_uuid` from the run record).

    ScenarioResponse:
      type: object
      description: Scenario record including generation and complexity metadata.
      properties:
        uuid:
          type: string
          format: uuid
          description: >
            Scenario identifier. Returned as `uuid` on create and generate responses;
            supply this value for the `scenario_uuid` path parameter on `/v1/scenarios/{scenario_uuid}/...`
            and as `scenario_uuid` in `POST /v1/engine/simulate/scenario` request bodies.
        name:
          type: string
          description: Display name in the workspace.
        description:
          type: string
          nullable: true
          description: Optional description shown in UI and APIs.
        user_uuid:
          type: string
          format: uuid
          description: Creating user UUID (tenant context).
        workspace_uuid:
          type: string
          format: uuid
          description: Owning workspace UUID.
        corpus_uuid:
          type: string
          format: uuid
          nullable: true
          description: Legacy link to a corpus for corpus-linked scenarios, if any.
        game_version:
          type: integer
          nullable: true
          description: Legacy game benchmark version field when applicable.
        status:
          type: string
          nullable: true
          description: Scenario lifecycle status (`INIT`, `PROCESSING`, `SUCCESS`, `FAILED`, `CANCELLED`).
          enum: [INIT, PROCESSING, SUCCESS, FAILED, CANCELLED]
        scenario_creation_job_status:
          type: string
          nullable: true
          description: >
            Latest `scenario_creation` job `current_status` while materialisation is in progress.
            Values: `PENDING`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`.
          enum: [PENDING, PROCESSING, COMPLETED, FAILED, CANCELLED]
        creation_parameters:
          type: object
          nullable: true
          additionalProperties: true
          description: Snapshot of scenario generation inputs used to create or regenerate this scenario.
        num_questions:
          type: integer
          nullable: true
          description: Legacy question count for benchmark-style scenarios.
        scenario_type:
          type: string
          nullable: true
          description: >
            Dialogue kind (`info_exchange` or `interview`). Also mirrored in
            `creation_parameters.scenario_type` for generated scenarios when set.
        valid_versions:
          type: array
          items: { type: integer }
          description: Scenario JSON schema versions accepted for runs against this scenario.
        tags:
          type: array
          items: { type: string }
          description: Workspace tags for organisation and filtering.
        definition_bucket_path:
          type: string
          nullable: true
          description: Storage root where `scenario.json` and artifacts live once materialised.
        complexity_score:
          type: number
          nullable: true
          description: Numeric difficulty score derived from historical runs when computed.
        complexity_label:
          type: string
          nullable: true
          description: Human-readable complexity band (e.g. easy, medium, hard) when set.
        complexity_updated_at:
          type: string
          format: date-time
          nullable: true
          description: When complexity labels were last recomputed.
        run_metadata:
          type: object
          nullable: true
          additionalProperties: true
          description: Opaque per-scenario statistics or UI hints from upstream.
        created_at:
          type: string
          format: date-time
          description: Creation timestamp (RFC 3339).
        updated_at:
          type: string
          format: date-time
          description: Last update timestamp (RFC 3339).

    ScenarioCreateResponse:
      allOf:
        - $ref: "#/components/schemas/ScenarioResponse"
        - type: object
          description: >
            Environment row plus async job linkage after create or generate. The scenario id is
            `uuid` (inherited from ScenarioResponse), not `scenario_uuid`. After
            `POST /v1/scenarios/generate` (and generate-copy), `creation_parameters` is null in the
            response even though inputs are stored server-side.
          properties:
            job_uuid:
              type: string
              format: uuid
              nullable: true
              description: scenario_creation job UUID to poll via GET /v1/jobs/{job_uuid}; null for empty shells.
            batch_uuid:
              type: string
              format: uuid
              nullable: true
              description: Present when num_scenarios > 1; shared Celery task id for the batch.
            batch_scenario_uuids:
              type: array
              items:
                type: string
                format: uuid
              nullable: true
              description: All scenario UUIDs in a batch create, same order as jobs.

    JobResponse:
      type: object
      description: Single async job row (Celery-backed work unit) in the workspace.
      properties:
        uuid:
          type: string
          format: uuid
          description: Job UUID for polling and cancel/retry/delete APIs.
        job_type:
          type: string
          description: >
            Job classifier (e.g. scenario_creation, ENGINE, evaluation, simulation).
        user_uuid:
          type: string
          format: uuid
          description: User that triggered or owns the job record.
        workspace_uuid:
          type: string
          format: uuid
          description: Workspace scope for the job.
        current_status:
          type: string
          description: >
            Job lifecycle status. Poll until `COMPLETED`, `FAILED`, or `CANCELLED`. Values:
            `PENDING`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`.
          enum: [PENDING, PROCESSING, COMPLETED, FAILED, CANCELLED]
        current_progress_text:
          type: string
          nullable: true
          description: Short human-readable progress line suitable for UI.
        progress_percentage:
          type: number
          description: Approximate completion fraction between 0 and 100.
        error_details:
          type: string
          nullable: true
          description: Error message or stack snippet when the job failed.
        task_id:
          type: string
          nullable: true
          description: Celery task id when the worker has claimed the job.
        created_at:
          type: string
          format: date-time
          description: When the job row was created (RFC 3339).
        updated_at:
          type: string
          format: date-time
          description: Last status or progress update (RFC 3339).

    JobsListResponse:
      type: object
      description: Wrapper listing zero or more jobs (e.g. all jobs tied to one scenario).
      properties:
        jobs:
          type: array
          description: Ordered collection of job rows (typically newest-first from upstream).
          items:
            $ref: "#/components/schemas/JobResponse"

    EvaluationJobInfo:
      type: object
      description: Lightweight evaluation job summary embedded on verification run list rows.
      properties:
        uuid:
          type: string
          format: uuid
          description: Evaluation job UUID (use with evaluation download endpoints when complete).
        job_type:
          type: string
          description: Upstream evaluation job type discriminator.
        user_uuid:
          type: string
          format: uuid
          description: User associated with the evaluation job record.
        current_status:
          type: string
          description: >
            Evaluation job lifecycle status (`PENDING`, `PROCESSING`, `COMPLETED`, `FAILED`,
            `CANCELLED`).
          enum: [PENDING, PROCESSING, COMPLETED, FAILED, CANCELLED]
        current_progress_text:
          type: string
          nullable: true
          description: Short progress description for the evaluation worker.
        progress_percentage:
          type: number
          description: Approximate evaluation completion percentage.
        error_details:
          type: string
          nullable: true
          description: Populated when evaluation fails or is cancelled with a reason.
        created_at:
          type: string
          format: date-time
          description: When the evaluation job was enqueued (RFC 3339).
        updated_at:
          type: string
          format: date-time
          description: Last evaluation status change (RFC 3339).

    SimulationListResponse:
      type: object
      description: >
        Summary of one **verification run**: counters, linkage to scenario
        and agent, evaluation flags, and optional nested evaluation jobs.
      properties:
        uuid:
          type: string
          format: uuid
          description: Verification run UUID (`simulation_uuid` in engine responses).
        user_uuid:
          type: string
          format: uuid
          description: User that owns or initiated the run.
        workspace_uuid:
          type: string
          format: uuid
          description: Workspace scope for the run.
        scenario_uuid:
          type: string
          format: uuid
          nullable: true
          description: >
            Linked scenario UUID on the run record (same value as the scenario row’s
            `uuid` field, not a separate response key on create/generate).
        agent_uuid:
          type: string
          format: uuid
          nullable: true
          description: Registered agent UUID under test when not using a pure override flow.
        agent_identifier:
          type: string
          nullable: true
          description: Stable agent key or catalogue identifier from upstream when set.
        agent_url:
          type: string
          nullable: true
          description: Effective agent URL used for this run (may reflect overrides).
        status:
          type: string
          description: >
            Run lifecycle status. Poll until `COMPLETED`, `FAILED`, or `CANCELLED`. Evaluation
            is a separate async job — not a run status value.
          enum: [CREATED, IN_PROGRESS, COMPLETED, FAILED, CANCELLED]
        total_items:
          type: integer
          description: Total work units or steps tracked for progress (engine-specific).
        completed_items:
          type: integer
          description: Work units successfully completed so far.
        failed_items:
          type: integer
          description: Work units that ended in a hard failure.
        started_at:
          type: string
          format: date-time
          nullable: true
          description: When execution began (RFC 3339).
        completed_at:
          type: string
          format: date-time
          nullable: true
          description: When execution finished or was terminal (RFC 3339).
        execution_mode:
          type: string
          nullable: true
          description: Engine execution mode discriminator when present.
        simulation_type:
          type: string
          nullable: true
          description: Scenario vs game / benchmark type string from upstream.
        scenario_version:
          type: integer
          nullable: true
          description: Scenario JSON schema version used for this run when recorded.
        run_group_uuid:
          type: string
          format: uuid
          nullable: true
          description: Batched runs share one run group UUID for correlation.
        run_index:
          type: integer
          nullable: true
          description: Zero-based index within a multi-run or batch group when applicable.
        created_at:
          type: string
          format: date-time
          nullable: true
          description: Row creation time (RFC 3339).
        updated_at:
          type: string
          format: date-time
          nullable: true
          description: Last mutation time (RFC 3339).
        run_metadata:
          type: object
          nullable: true
          additionalProperties: true
          description: Opaque JSON bag with UI hints, cost hints, or engine diagnostics.
        evaluation_jobs:
          type: array
          description: Nested evaluation jobs spawned for this run (may be empty).
          items:
            $ref: "#/components/schemas/EvaluationJobInfo"
        has_single_evaluation:
          type: boolean
          nullable: true
          description: Denormalised flag — true when exactly one evaluation job exists.
        has_aggregated_evaluation:
          type: boolean
          nullable: true
          description: Denormalised flag — true when an aggregated evaluation result exists.

    SimulationDetailResponse:
      allOf:
        - $ref: "#/components/schemas/SimulationListResponse"
        - type: object
          description: Full verification run row including engine `run_parameters` payload.
          properties:
            run_parameters:
              type: object
              nullable: true
              additionalProperties: true
              description: Engine-supplied parameters snapshot (timeouts, overrides, flags, …).

    PaginatedSimulationListResponse:
      type: object
      description: Page of verification runs plus total count for infinite-scroll style UIs.
      properties:
        items:
          type: array
          description: Runs for the requested page (size `limit`, skip `offset`).
          items:
            $ref: "#/components/schemas/SimulationListResponse"
        total:
          type: integer
          description: Total matching runs across all pages for the current filter set.
        limit:
          type: integer
          description: Page size used for this response (echo of request `limit`).
        offset:
          type: integer
          description: Skip value used for this response (echo of request `offset`).

    GetEvaluationResponse:
      type: object
      description: Evaluation job envelope including structured scores when the job has finished.
      properties:
        uuid:
          type: string
          format: uuid
          description: Evaluation job UUID (same as path `evaluation_job_uuid` when fetched directly).
        simulation_uuid:
          type: string
          format: uuid
          description: Parent verification run UUID this evaluation scores.
        user_uuid:
          type: string
          format: uuid
          description: User that owns the evaluation job record.
        current_status:
          type: string
          description: Evaluation worker status string.
        current_progress_text:
          type: string
          nullable: true
          description: Short human-readable evaluation progress line.
        progress_percentage:
          type: number
          description: Approximate evaluation completion percentage.
        error_details:
          type: string
          nullable: true
          description: Failure detail when evaluation ends in error.
        created_at:
          type: string
          format: date-time
          description: When the evaluation job was created (RFC 3339).
        updated_at:
          type: string
          format: date-time
          description: Last evaluation status change (RFC 3339).
        evaluation:
          type: object
          nullable: true
          additionalProperties: true
          description: >
            Structured evaluation payload (scores, rationales, per-criterion breakdown) when
            `current_status` indicates success; null while pending or on hard failure.

    PublicSimulationEvaluationResponse:
      type: object
      description: Gateway evaluation response for a simulation UUID.
      properties:
        success:
          type: boolean
        data:
          $ref: "#/components/schemas/GetEvaluationResponse"
      required: [success]

    PublicSimulationScoreSummary:
      type: object
      description: Score summary derived from the latest evaluation payload for a simulation.
      properties:
        overall_score:
          type: number
          nullable: true
          description: Mean of per-tag average grades.
        per_tag_scores:
          type: object
          additionalProperties:
            type: number
          description: Map of tag name to average grade.
        evaluator_model:
          type: string
          nullable: true
          description: Evaluator model when available in payload metadata.
        timestamp:
          type: string
          format: date-time
          nullable: true
          description: Best available evaluation timestamp.
      required: [overall_score, per_tag_scores, evaluator_model, timestamp]

    PublicSimulationScoreResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            simulation_uuid:
              type: string
              format: uuid
            overall_score:
              type: number
              nullable: true
            per_tag_scores:
              type: object
              additionalProperties:
                type: number
            evaluator_model:
              type: string
              nullable: true
            timestamp:
              type: string
              format: date-time
              nullable: true
          required:
            - simulation_uuid
            - overall_score
            - per_tag_scores
            - evaluator_model
            - timestamp
      required: [success, data]

    PublicBatchSimulationScoresResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            scores:
              type: object
              additionalProperties:
                type: object
                allOf:
                  - $ref: "#/components/schemas/PublicSimulationScoreSummary"
                nullable: true
          required: [scores]
      required: [success, data]

    PublicBillingBalanceResponse:
      type: object
      description: Billing balance response for the authenticated organization.
      properties:
        credits_remaining:
          type: number
        credits_used:
          type: number
          nullable: true
        plan:
          type: string
          nullable: true
        billing_period_end:
          type: string
          format: date-time
          nullable: true
      required: [credits_remaining, credits_used, plan, billing_period_end]

    PublicAuditLogEntry:
      type: object
      properties:
        id:
          type: string
          format: uuid
        traceId:
          type: string
        userId:
          type: string
          format: uuid
          nullable: true
        userName:
          type: string
          nullable: true
        email:
          type: string
          nullable: true
        key:
          type: string
          nullable: true
        action:
          type: string
        route:
          type: string
        organizationId:
          type: string
          format: uuid
        createdAt:
          type: string
          format: date-time
        method:
          type: string
          nullable: true

    PublicAuditLogsPagination:
      type: object
      properties:
        page:
          type: integer
        offset:
          type: integer
        limit:
          type: integer
        total:
          type: integer
        totalPages:
          type: integer
      required: [page, offset, limit, total, totalPages]

    PublicAuditLogsResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: array
          items:
            $ref: "#/components/schemas/PublicAuditLogEntry"
        pagination:
          $ref: "#/components/schemas/PublicAuditLogsPagination"
      required: [success, data, pagination]

    CancelResponse:
      type: object
      description: Acknowledgement and side-effect summary when cancelling a run or related job.
      properties:
        message:
          type: string
          description: Human-readable outcome message from upstream.
        job_uuid:
          type: string
          format: uuid
          nullable: true
          description: Related orchestration job UUID that was signalled to stop, if any.
        simulation_uuid:
          type: string
          format: uuid
          nullable: true
          description: Verification run UUID that was targeted for cancellation.
        warnings:
          type: array
          items: { type: string }
          description: Non-fatal notices (e.g. already terminal, partial cleanup).

    StartSimulationResponse:
      type: object
      description: Acknowledgement from engine start endpoints — async work continues under `job_uuid`.
      properties:
        job_uuid:
          type: string
          format: uuid
          description: Orchestration job to poll via `GET /v1/jobs/{job_uuid}`.
        simulation_uuid:
          type: string
          format: uuid
          description: Primary verification run id (single-scenario start).
        evaluation_job_uuid:
          type: string
          format: uuid
          nullable: true
          description: Present when evaluation was scheduled immediately (`evaluate_on_complete`).
        status:
          type: string
          description: Engine acknowledgement status (e.g. `dispatched`).
          example: dispatched
        message:
          type: string
          description: Human-readable detail from the engine.
          example: Scenario simulation task(s) dispatched successfully (1 run(s))
        simulation_uuids:
          type: array
          items: { type: string, format: uuid }
          description: Populated for multi-scenario / multi-run responses instead of a single `simulation_uuid`.
        run_group_uuid:
          type: string
          format: uuid
          nullable: true
          description: Correlates batched or multi-run executions in the UI and APIs.

    StartNfrVerificationRequest:
      type: object
      description: >
        Start an async NFR verification run. The gateway injects `organization_uuid`,
        `workspace_uuid`, and `user_uuid` on proxied POST bodies, so clients may omit
        tenant fields.
      required: [agent_uuid]
      properties:
        agent_uuid:
          type: string
          format: uuid
          description: Registered agent whose `agent_url` is the load-test target.
        nfr_config:
          type: object
          description: NFR preset, probes, overlays, and load profile passed to the engine worker.
          additionalProperties: true
        auth_override:
          type: object
          nullable: true
          description: Ad-hoc target auth when not stored on the agent record.
          additionalProperties: true
        ownership_attested:
          type: boolean
          default: false
          description: >
            Caller asserts authorization to load-test the target. Required for non-smoke
            presets and load-bearing segment runs.

    StartNfrVerificationResponse:
      type: object
      description: Acknowledgement from the NFR start endpoint — async work continues under `job_uuid`.
      properties:
        job_uuid:
          type: string
          format: uuid
          description: Orchestration job to poll via `GET /v1/jobs/{job_uuid}`.
        simulation_uuid:
          type: string
          format: uuid
          description: NFR verification run id.
        status:
          type: string
          description: Engine acknowledgement status (e.g. `dispatched`).
          example: dispatched
        message:
          type: string
          description: Human-readable detail from the engine.

    NfrDomainVerifyRequest:
      type: object
      description: >
        Start or check DNS-TXT domain ownership for an agent load-test target. The
        gateway injects `organization_uuid`, `workspace_uuid`, and `user_uuid` on
        proxied POST bodies, so clients may omit tenant fields.
      required: [agent_uuid]
      properties:
        organization_uuid:
          type: string
          format: uuid
          description: Injected by the gateway on proxied requests.
        workspace_uuid:
          type: string
          format: uuid
          description: Injected by the gateway on proxied requests.
        user_uuid:
          type: string
          format: uuid
          description: Injected by the gateway on proxied requests.
        agent_uuid:
          type: string
          format: uuid
          description: Agent whose `agent_url` host is being verified.

    NfrDomainVerifyResponse:
      type: object
      description: DNS-TXT challenge details and verification status for an agent host.
      properties:
        hostname:
          type: string
          description: Host derived from the agent URL (without scheme or path).
        txt_record_name:
          type: string
          description: DNS TXT record name to publish (typically `_vax-verify.<hostname>`).
        txt_record_value:
          type: string
          description: Challenge token value to publish in the TXT record.
        status:
          type: string
          description: "`pending` until the TXT record resolves, then `verified`."
          example: pending
        verified_at:
          type: string
          format: date-time
          nullable: true
        instructions:
          type: string
          description: Operator-facing steps to publish the TXT record and re-check.

    NfrGatePolicy:
      type: object
      description: >
        What a team blocks on. Every rule is optional; a policy with none set blocks
        nothing, which is why `inconclusive_blocks` defaults to true.
      properties:
        required_dimensions:
          type: array
          description: >
            Dimensions that must have been measured. Measured, not merely in scope: a
            dimension the run did not exercise makes the outcome inconclusive rather
            than a pass.
          items:
            type: string
        forbidden_kinds:
          type: array
          description: Finding kinds that must never be open on a judged run.
          items:
            type: string
        severity_floor:
          type: string
          nullable: true
          description: Block on any open finding at or above this severity.
          enum: [critical, high, medium, low]
        block_on_regression:
          type: boolean
          description: >
            Block when a dimension's band drops against the agent's latest run on the
            default branch. With no such run, this rule does not evaluate.
        inconclusive_blocks:
          type: boolean
          description: >
            Block when the run could not certify the change. Defaults to true: a run
            that could not be measured has not said the change is safe, only that it
            could not tell.
    NfrGatePolicyRequest:
      type: object
      required: [policy]
      properties:
        policy:
          $ref: "#/components/schemas/NfrGatePolicy"
    NfrGatePolicyResponse:
      type: object
      properties:
        agent_uuid:
          type: string
          format: uuid
        policy:
          $ref: "#/components/schemas/NfrGatePolicy"
        policy_hash:
          type: string
          description: The hash every decision judged under this policy records.
    NfrGateDecisionReason:
      type: object
      description: One rule that fired, naming what it fired on.
      properties:
        rule_id:
          type: string
          description: Which rule fired, for example `policy.severity_floor`.
        summary:
          type: string
        findings:
          type: array
          description: >
            The findings behind this reason, named by identity rather than by score,
            because an engineer cannot act on a number.
          items:
            type: object
            properties:
              dimension:
                type: string
              kind:
                type: string
        dimensions:
          type: array
          items:
            type: string
    NfrGateDecision:
      type: object
      properties:
        outcome:
          type: string
          enum: [pass, fail, inconclusive]
        blocks:
          type: boolean
          description: Whether this policy stops the merge on this outcome.
        run_uuid:
          type: string
          format: uuid
        baseline_run_uuid:
          type: string
          format: uuid
          nullable: true
          description: The default-branch run the regression rule compared against, if any.
        policy_hash:
          type: string
        decided_at:
          type: string
          format: date-time
        inconclusive_blocks:
          type: boolean
        reasons:
          type: array
          items:
            $ref: "#/components/schemas/NfrGateDecisionReason"
    NfrRunDecisionResponse:
      type: object
      properties:
        agent_uuid:
          type: string
          format: uuid
        decision:
          $ref: "#/components/schemas/NfrGateDecision"
    NfrReport:
      type: object
      additionalProperties: true
      description: >
        The reader-facing report payload, carried on `report`. Documented one level deep, because these are the
        keys a consumer branches on. The structures beneath them are not specified: they follow
        the engine and change with it, so `additionalProperties` stays true rather than pinning a
        shape this contract would then have to keep true.


        The harness's own economics are not here and never reach a reader. Spend, credits, token
        counts and the judge models we chose are split into a separate billing artefact that is
        not served on this path.
      properties:
        schema_version:
          type: string
          description: Version of this report's own shape. Read it before relying on any field below.
        report_type:
          type: string
          description: Which report shape this is, which is what decides the keys a consumer can expect.
        overall_score:
          type: number
          nullable: true
          description: The run's headline score. Null when nothing was measured.
        overall_band:
          type: string
          nullable: true
          description: The headline band, pass / weak / fail, derived from overall_score. It is not_measured when the run is void or nothing was measured.
        dimension_scores:
          type: array
          description: One entry per dimension, each with its score, band, findings and the prose the renderers display. This is the body of the report.
        fail_banded_dimensions:
          type: array
          description: Names of the dimensions that banded fail, so a reader does not have to scan dimension_scores to find them.
        caveats:
          type: array
          description: What this run does not claim. Every caveat here qualifies the verdict and is rendered on the web and in the Markdown.
        methodology:
          type: object
          additionalProperties: true
          description: How the run was made and what it could not establish. Carries provenance, judge configuration, measurement limitations and caveat groupings.
        run_validity:
          type: object
          additionalProperties: true
          description: Whether the run is sound enough to read at all. A run voided here has scores that must not be taken at face value.
        operational_readiness:
          type: array
          description: Whether each skill reached a working state before it was measured. A skill that never worked was not tested.
        warm_up_summary:
          type: object
          additionalProperties: true
          description: What happened during warm-up, before measurement began.
        warm_up_downgraded_dimensions:
          type: array
          description: Dimensions whose band was lowered because warm-up did not complete for them.
        deployment_fit:
          type: array
          description: How the measured behaviour fits named deployment profiles.
        regulatory_evidence:
          type: object
          additionalProperties: true
          description: Framework-by-framework evidence drawn from this run, for a reader assembling a compliance case.
        scope_classifier:
          type: object
          additionalProperties: true
          description: What the run decided was in scope to measure, and why.
        active_overlays:
          type: array
          description: Overlays applied to this run, which change what is probed and how.
        agent_card:
          type: object
          additionalProperties: true
          description: The target agent as it described itself, including the skills the run drew its tasks from.
        transport_metadata:
          type: object
          additionalProperties: true
          description: How the harness reached the agent, and what the transport reported back.
        locust_stats:
          type: object
          additionalProperties: true
          description: Raw load-generator statistics for the run.
        baseline_diff:
          type: object
          nullable: true
          additionalProperties: true
          description: What changed against the baseline run, when one resolved. Null when there was nothing to compare against, which is not the same as nothing having changed.
        eval_awareness_paired_enabled:
          type: boolean
          description: Whether the paired evaluation-awareness probe ran.
        eval_awareness_paired_summary:
          type: object
          additionalProperties: true
          description: What the paired probe found about the agent behaving differently when it believed it was being tested.
        eval_awareness_reasoning_detected:
          type: boolean
          description: Whether the agent's own reasoning showed it had inferred it was under evaluation.
        eval_awareness_reasoning_summary:
          type: object
          additionalProperties: true
          description: The evidence behind eval_awareness_reasoning_detected.
        judge_cascade_universal_default:
          type: boolean
          description: Whether the judge cascade ran on its default settings for this run.
        llm_used:
          type: boolean
          description: Whether any judge call was made. False means every verdict here is deterministic.
        redaction_mode:
          type: string
          description: Whether secret redaction was on while this run's artefacts were written.
        profile_name:
          type: string
          description: The scoring profile this run was graded against.
        profile_version:
          type: string
          description: Version of that scoring profile, so a score is compared only with scores graded the same way.
        generated_at:
          type: string
          description: When this report was written.
        run_started_at_iso:
          type: string
          description: When the run itself began, which is earlier than generated_at by the run's duration.
        vax_seed:
          type: integer
          description: The seed the run was given, so a run can be repeated.
        vax_seed_source:
          type: string
          description: Where that seed came from, supplied by the caller or chosen by the harness.
        warm_up_gate_failed:
          type: boolean
          description: Whether the warm-up applicability check raised instead of running. True means the run could not establish which dimensions warm-up had driven into a working state, which is not the same as having checked and found nothing to downgrade.
        judge_execution_mode:
          type: string
          description: How the judge calls were run for this report. Present only when a judge ran.
        judge_batch_enabled:
          type: boolean
          description: Present and true when judging ran in batch. Absent otherwise, so its absence is not a claim that batching was off for a run that used no judge.
        eval_awareness_behavioral_delta_detected:
          type: boolean
          description: Present and true when the paired probe measured the agent behaving differently once it believed it was being evaluated.
        per_skill:
          type: array
          description: Configured SLO and scope rows per skill. Stamped on a report written for a run that aborted, so a reader of a partial report can still see what was configured.
        vax_deterministic_load:
          type: boolean
          description: Present and true when the run derived its load identities deterministically. Absent on a run that did not, so a reader can tell a deterministic load from one that merely pinned a seed.
        injection_sample_seed_contract:
          type: object
          additionalProperties: true
          description: How the injection corpus sample was drawn, when the run was given a seed contract rather than a bare seed. Absent otherwise, and a run carrying one does not also carry vax_seed, because two answers to what produced the draws is worse than one.
        verdict:
          type: object
          additionalProperties: true
          description: The scoring object the engine produced, carried alongside the rendered report.

    NfrVerificationResultResponse:
      type: object
      description: >
        NFR report payload returned when artifacts are available. Which of `report`,
        `smoke_report` and `capacity_report` is populated depends on `preset`, so read
        that field before branching on the report keys.
      properties:
        simulation_uuid:
          type: string
          format: uuid
        agent_uuid:
          type: string
          format: uuid
          nullable: true
          description: >
            The agent this run tested, so a consumer can join a report to the agent's
            cross-run views. The report artefact itself carries no platform UUID.
        status:
          type: string
          description: Simulation row status (e.g. `COMPLETED`, `IN_PROGRESS`, `FAILED`).
        preset:
          type: string
          nullable: true
          description: >
            Which preset produced this run, and therefore which report key is populated:
            `smoke` fills `smoke_report`, `capacity` fills `capacity_report`, and every
            other preset fills `report`. A run of any preset that stopped early can also,
            or instead, fill `partial_report`. Not a closed set: new presets are added
            without a change to this schema.
        nfr_config:
          type: object
          nullable: true
          additionalProperties: true
          description: NFR preset, probes, overlays, and load profile this run was dispatched with.
        simulation_outcome:
          type: string
          nullable: true
        simulation_reason:
          type: string
          nullable: true
          description: >
            Why the run ended as it did. Usually null on a run that completed normally.
            Otherwise either a short token such as `budget_exceeded` or `spend_cap`, or a
            one-line diagnostic such as `Run aborted before final report`. Not a closed
            set, and the diagnostic wording can change between releases.
        started_at:
          type: string
          format: date-time
          nullable: true
          description: >
            When the run began executing. Can be null even on a finished run, when its
            start was never recorded, for example because the run finished before its
            start callback arrived.
        created_at:
          type: string
          format: date-time
          nullable: true
          description: When the run was requested, which is earlier than `started_at` by the queue wait.
        report:
          allOf:
            - $ref: "#/components/schemas/NfrReport"
          nullable: true
          description: Primary `verifyax_report.json` payload, present for every preset except `smoke` and `capacity`.
        partial_report:
          type: object
          nullable: true
          additionalProperties: true
        smoke_report:
          type: object
          nullable: true
          additionalProperties: true
          description: The `verifyax_smoke.json` payload for a `smoke` preset run. It has its own shape, not `NfrReport`.
        capacity_report:
          type: object
          nullable: true
          additionalProperties: true
          description: The `verifyax_capacity_report.json` payload for a `capacity` preset run. It has its own shape, not `NfrReport`.
        artifact_paths:
          type: object
          nullable: true
          additionalProperties:
            type: string

    NfrRunActivityResponse:
      type: object
      description: Live activity snapshot for an in-flight NFR verification run.
      properties:
        simulation_uuid:
          type: string
          format: uuid
        status:
          type: string
          description: Simulation row status (e.g. `IN_PROGRESS`, `COMPLETED`).
        queue_position:
          type: integer
          nullable: true
        job_uuid:
          type: string
          format: uuid
          nullable: true
        celery_task_id:
          type: string
          nullable: true
        celery_state:
          type: string
          nullable: true
        phase:
          type: string
          nullable: true
          description: Current engine phase when the worker is active.
        heartbeat_mtime:
          type: number
          nullable: true
        phase_age_s:
          type: number
          nullable: true
        heartbeat_alive:
          type: boolean
        heartbeat_last_seen:
          type: integer
          nullable: true
        heartbeat_last_seen_iso:
          type: string
          format: date-time
          nullable: true
        recent_events:
          type: array
          items:
            type: object
            additionalProperties: true
        simulation_outcome:
          type: string
          nullable: true

    CreditPreviewRequest:
      type: object
      description: >
        Workspace credit preview. The gateway injects `organization_uuid`, `workspace_uuid`,
        and `user_uuid` on proxied POST bodies, so clients may omit tenant fields entirely.
        Upstream validation: `scenario_run` requires `scenario_uuid` and `num_runs`;
        `scenario_generation` requires `num_scenarios`.
      required: [mode, organization_uuid, workspace_uuid]
      properties:
        mode:
          type: string
          enum: [scenario_run, scenario_generation]
          description: Select run-cost estimate vs scenario batch-generation estimate.
        organization_uuid:
          type: string
          format: uuid
          description: Organisation scope (overwritten from API key by the gateway).
        workspace_uuid:
          type: string
          format: uuid
          description: Workspace scope (overwritten from API key by the gateway).
        scenario_uuid:
          type: string
          format: uuid
          description: >
            Required when `mode` is `scenario_run` — target scenario (the scenario row’s
            `uuid` from create or generate, passed here as `scenario_uuid`).
        num_runs:
          type: integer
          minimum: 1
          maximum: 10
          description: Required when `mode` is `scenario_run` — number of planned runs to price.
        agent_uuid:
          type: string
          format: uuid
          nullable: true
          description: Optional agent hint for run-cost estimates (registry agent UUID).
        timeout_minutes:
          type: integer
          minimum: 1
          maximum: 240
          description: >-
            scenario_run: overrides simulation_config.timeout_minutes for the credit estimate.
            scenario_generation: generation time budget (mirrors scenario create). Omit for defaults.
        num_scenarios:
          type: integer
          minimum: 1
          maximum: 50
          description: Required when `mode` is `scenario_generation` — number of scenario creates to price.

    CreditPreviewRunItem:
      type: object
      description: One in-flight or pending ENGINE verification run contributing to committed credits.
      required: [simulationUuid, estimatedCredits]
      properties:
        simulationUuid:
          type: string
          format: uuid
          description: Verification run UUID with a stored credit estimate.
        estimatedCredits:
          type: number
          description: Credits reserved or estimated for that run.
        status:
          type: string
          nullable: true
          description: Run or job status when provided (`CREATED`, `IN_PROGRESS`, `COMPLETED`, `FAILED`, `CANCELLED`, or job `PENDING`, `PROCESSING`, …).

    CreditPreviewGenerationItem:
      type: object
      description: One pending scenario_creation job contributing to generation commitments.
      required: [jobUuid, estimatedCredits]
      properties:
        jobUuid:
          type: string
          format: uuid
          description: scenario_creation job UUID.
        estimatedCredits:
          type: number
          description: Credits estimated for that generation job.
        status:
          type: string
          nullable: true
          description: Job status when available (`PENDING`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`).

    CreditPreviewResponse:
      type: object
      description: >
        Workspace credit snapshot: current balance, pending commitments, existing in-flight
        work, and optional estimate for a **new** run or generation request.
      required:
        - balance
        - existingRuns
        - existingRunsEstimatedTotal
        - existingGenerations
        - pendingGenerationsEstimatedTotal
        - pendingCommittedTotal
      properties:
        balance:
          type: number
          description: Organisation credit balance visible to billing after recent updates.
        newRunEstimatedCredits:
          type: number
          nullable: true
          description: Set when `mode` was `scenario_run` — estimated incremental cost for the proposed run(s).
        newRunEstimateMetadata:
          type: object
          additionalProperties: true
          nullable: true
          description: >
            Full billing estimate metadata for the new run preview, including nested
            `estimate_breakdown` (NPC/evaluator/external/compute lines), `effective_rounds`,
            `scenario_type_used`, `global_config_version`, etc.
        newGenerationEstimatedCredits:
          type: number
          nullable: true
          description: Set when `mode` was `scenario_generation` — estimated cost for N new scenarios.
        existingRuns:
          type: array
          description: Pending ENGINE runs with stored per-run estimates.
          items:
            $ref: "#/components/schemas/CreditPreviewRunItem"
        existingRunsEstimatedTotal:
          type: number
          description: Sum of `estimatedCredits` across `existingRuns`.
        existingGenerations:
          type: array
          description: Pending scenario_creation jobs with stored estimates.
          items:
            $ref: "#/components/schemas/CreditPreviewGenerationItem"
        pendingGenerationsEstimatedTotal:
          type: number
          description: Sum of generation estimates for jobs in `existingGenerations`.
        pendingCommittedTotal:
          type: number
          description: >
            Credits already soft-committed by in-flight ENGINE runs and scenario_creation jobs
            (used with balance to compute headroom).

    TriggerEvaluationResponse:
      type: object
      description: Acknowledgement after manually triggering evaluation for a simulation run in `COMPLETED` status.
      properties:
        evaluation_job_uuid:
          type: string
          format: uuid
          description: UUID of the newly created evaluation job to poll or download.
        simulation_uuid:
          type: string
          format: uuid
          description: Verification run being evaluated.
        status:
          type: string
          description: Engine acknowledgement status for the trigger request.
        message:
          type: string
          description: Human-readable detail from the evaluation trigger path.

    FetchAgentCardRequest:
      type: object
      description: >
        Connectivity check: fetches an A2A agent card (or equivalent) through the gateway
        connector — useful before registering an agent URL.
      properties:
        agent_url:
          type: string
          nullable: true
          description: Absolute URL of the `.well-known` agent card or root to fetch.
        agent_type:
          type: string
          default: A2A
          description: Connector hint; defaults to A2A card discovery semantics.
        agent_parameters:
          type: object
          nullable: true
          additionalProperties: true
          description: Optional extra parameters passed to the connector (timeouts, headers, etc.).

    TestRestAgentRequest:
      type: object
      description: >
        Sends a single HTTP request to a REST-style agent through the gateway for debugging;
        not a full verification run.
      required: [url]
      properties:
        url:
          type: string
          description: Fully qualified URL to invoke (method applied as given).
        method:
          type: string
          default: GET
          description: HTTP verb (GET, POST, PUT, PATCH, DELETE, …).
        headers:
          type: object
          additionalProperties:
            type: string
          description: Outbound request headers (e.g. Authorization, Content-Type).
        body:
          type: object
          description: Optional JSON object body for non-GET calls; arbitrary keys are forwarded upstream.
          additionalProperties: true
        timeout:
          type: number
          default: 10.0
          description: Per-request timeout in seconds enforced server-side.

    TestCurlAgentRequest:
      type: object
      description: >
        Executes a curl-style command template server-side with sandboxed outbound access;
        intended for integration smoke tests, not arbitrary shell.
      required: [curl_command]
      properties:
        curl_command:
          type: string
          description: Full curl command line as a single string (quoting rules apply server-side).
        timeout:
          type: number
          default: 10.0
          description: Maximum seconds to wait for the proxied curl execution.

    A2AConnectionTestRequest:
      type: object
      description: >
        Body for `POST /v1/agents/tests/a2a-connection`. Tenant UUIDs and optional
        `agent_uuid` are injected or supplied from the API key context when testing a saved
        registry agent.
      required: [agent_url]
      properties:
        agent_url:
          type: string
          description: Absolute URL of the agent or agent-card entrypoint.
        agent_type:
          type: string
          default: A2A
        agent_parameters:
          type: object
          nullable: true
          additionalProperties: true
        agent_uuid:
          type: string
          format: uuid
          nullable: true
          description: When set with tenant context, mini-sim results are persisted on the agent.
        message:
          type: string
          nullable: true
          description: Optional probe message for the synchronous A2A message step.
        cs_auth_token:
          type: string
          nullable: true
          description: Fresh Conscium session token for `auth_method=cs` agents.

    ConnectionCheckRequest:
      type: object
      description: >
        Body for `POST /v1/agents/tests/connection-readiness` and
        `POST /v1/agents/tests/connection-preflight`. The gateway sets `workspace_uuid`
        from the API key.
      required: [agent_uuid]
      properties:
        agent_uuid:
          type: string
          format: uuid
          description: Saved agent to check. It must belong to the API key's workspace.
        cs_auth_token:
          type: string
          nullable: true
          description: >
            Fresh Conscium session token. Used by connection-preflight when the saved
            agent has `auth_method=cs`. Ignored by connection-readiness.

    ConnectionReadinessResponse:
      type: object
      required: [action, message]
      properties:
        action:
          type: string
          enum:
            - preflight
            - full_test_required
            - in_progress
          description: >
            `preflight` when a pass or completed run is inside the last 7 days.
            `full_test_required` when that evidence is missing, failed, or older.
            `in_progress` when a connection test is still running.
        message:
          type: string
        last_full_test_at:
          type: string
          format: date-time
          nullable: true
          description: When the stored full connection test completed, if a time was recorded.
        last_run_at:
          type: string
          format: date-time
          nullable: true
          description: When the newest completed workbench run for this agent finished.

    ConnectionPreflightResponse:
      type: object
      required: [success, message]
      properties:
        success:
          type: boolean
          description: True when the saved URL and credentials are reachable.
        message:
          type: string

    TestA2AMessageRequest:
      type: object
      description: Body for `POST /v1/agents/tests/a2a-message`.
      properties:
        agent_url:
          type: string
          nullable: true
        agent_type:
          type: string
          default: A2A
        agent_parameters:
          type: object
          nullable: true
          additionalProperties: true
        message:
          type: string
          nullable: true
          description: Optional probe message for the A2A message test.

    TestDirectlineAgentRequest:
      type: object
      description: >
        Body for `POST /v1/agents/tests/api-agent-test-directline` (pre-registration probe).
        Uses top-level `secret` and `region` — not `agent_parameters.directline` on
        `POST /v1/agents`.
      required: [secret]
      properties:
        secret:
          type: string
          minLength: 1
          description: Direct Line secret from Copilot Studio.
        region:
          type: string
          default: global
          description: Direct Line region code or `global`.
        message:
          type: string
          default: Hello
          description: Probe message sent to the Copilot Studio agent.
        timeout:
          type: number
          default: 60.0
          description: Poll timeout in seconds.

    TestCopilotStudioAgentRequest:
      type: object
      description: >
        Body for `POST /v1/agents/tests/api-agent-test-copilot-studio`. Uses the nested
        `directline` registration object (including `auth_mode`).
      required: [directline]
      properties:
        directline:
          $ref: "#/components/schemas/DirectlineAgentParameters"
        user_token:
          type: string
          nullable: true
          description: End-user Entra token (required for microsoft/manual modes).
        message:
          type: string
          default: Hello
        timeout:
          type: number
          default: 60.0

    McpToolInfo:
      type: object
      required: [name]
      properties:
        name:
          type: string
        description:
          type: string
          default: ""
        input_schema:
          type: object
          additionalProperties: true
          default: {}

    TestMcpConnectionRequest:
      type: object
      description: >
        Body for `POST /v1/agents/tests/mcp-connection`. Tenant UUIDs are injected from the
        API key. When `agent_uuid` is set, stored MCP credentials may be merged from the registry.
      required: [mcp_url]
      properties:
        mcp_url:
          type: string
          minLength: 1
          description: Target remote MCP server URL (must be a public HTTPS endpoint).
        auth_method:
          type: string
          default: bearer
          enum: [bearer, none]
          description: Authentication mode for the MCP server.
        token:
          type: string
          nullable: true
          description: Bearer token or PAT when auth_method is bearer.
        transport:
          type: string
          nullable: true
          enum: [streamable-http, sse, auto]
          description: MCP transport; auto-detect when omitted.
        agent_url:
          type: string
          format: uri
          nullable: true
          description: Catalogue MCP adapter A2A URL for an agent-card probe after discovery.
        agent_uuid:
          type: string
          format: uuid
          nullable: true
          description: Persisted registry agent UUID; loads redacted MCP credentials when set.
        agent_parameters:
          type: object
          nullable: true
          additionalProperties: true
          description: Registry agent parameters merged with request fields (includes nested `mcp`).
        message:
          type: string
          nullable: true
          description: Ignored. MCP connection tests do not send A2A probe messages.

    TestMcpConnectionResponse:
      type: object
      required: [success]
      properties:
        success:
          type: boolean
        message:
          type: string
          nullable: true
        transport:
          type: string
          nullable: true
        server_info:
          type: object
          additionalProperties: true
        tools:
          type: array
          items:
            $ref: "#/components/schemas/McpToolInfo"
        auth_required:
          type: boolean
        auth_type:
          type: string
          nullable: true
        provider_hint:
          type: string
          nullable: true
        outcome:
          type: string
          nullable: true
          enum: [success, failure, partial, pending]
        connection:
          $ref: "#/components/schemas/ConnectionTestStepStatus"
        agent_card:
          $ref: "#/components/schemas/ConnectionTestStepStatus"
        agent_communication:
          $ref: "#/components/schemas/ConnectionTestStepStatus"
        testing_started:
          type: boolean
          default: false

    ConnectionTestStepStatus:
      type: object
      required: [status, message]
      properties:
        status:
          type: string
          enum: [success, failure, skipped, pending]
        message:
          type: string
        agent_card_url:
          type: string
          nullable: true

    A2AConnectionTestResponse:
      type: object
      required: [success, message, outcome, connection, agent_card, agent_communication]
      properties:
        success:
          type: boolean
          description: True when connection, card, and communication steps all pass.
        message:
          type: string
        outcome:
          type: string
          enum:
            - testing
            - unreachable
            - profile_not_found
            - wont_reply
            - save_and_retest
            - connected
            - connected_interview_only
            - connected_info_exchange_only
            - practice_run_failed
            - server_error
            - unexpected_error
            - lifecycle_blocked
        connection:
          $ref: "#/components/schemas/ConnectionTestStepStatus"
        agent_card:
          $ref: "#/components/schemas/ConnectionTestStepStatus"
        agent_communication:
          $ref: "#/components/schemas/ConnectionTestStepStatus"
        interview:
          type: object
          allOf:
            - $ref: "#/components/schemas/ConnectionTestStepStatus"
          nullable: true
        info_exchange:
          type: object
          allOf:
            - $ref: "#/components/schemas/ConnectionTestStepStatus"
          nullable: true
        testing_started:
          type: boolean
          default: false
          description: True when background mini-simulations were started.

    RegisterClientQnaRequest:
      type: object
      required: [skill_tag, description, qna]
      properties:
        skill_tag:
          type: string
          description: Canonical tag name (must not collide with a global catalogue tag).
        description:
          type: string
          minLength: 10
        testing_method:
          type: string
          minLength: 10
          default: Ask factual recall questions from the registered QnA benchmark.
        category:
          type: string
          default: qna_custom
        qna_file:
          type: string
          nullable: true
          description: Optional filename hint for stored QnA JSON.
        comments:
          type: string
          nullable: true
        qna:
          type: object
          required: [questions]
          properties:
            questions:
              type: array
              minItems: 1
              items:
                $ref: "#/components/schemas/QnaQuestionItem"
        qna_user_edited:
          type: boolean
          default: false
        dry_run:
          type: boolean
          default: false
          description: When true, validate only — no writes to master-data storage.

    RegisterClientQnaResponse:
      type: object
      required: [action, skill_tag, qna_storage_path, client_tags_storage_path, question_count]
      properties:
        action:
          type: string
          enum: [appended, updated, dry_run]
        skill_tag:
          type: string
        qna_storage_path:
          type: string
        client_tags_storage_path:
          type: string
        question_count:
          type: integer
        row:
          type: object
          nullable: true
          additionalProperties: true
          description: Client tag row written or previewed in `client_tags.jsonl`.

    UsageEventResponse:
      type: object
      description: >
        One **usage event** (billable execution telemetry row) from `/v1/usage/events`. Costs are
        LiteLLM USD actuals and optional compute; they are **not** customer billing credits
        (see `GET /v1/billing/balance` for credit balance). Additional fields may appear beyond
        those listed (`additionalProperties: true`).
      additionalProperties: true
      properties:
        uuid:
          type: string
          format: uuid
          description: Usage event UUID.
        organization_uuid:
          type: string
          format: uuid
          description: Organisation that incurred the spend.
        user_uuid:
          type: string
          format: uuid
          description: User attributed on the event (often the API key user).
        workspace_uuid:
          type: string
          format: uuid
          description: Workspace attributed on the event.
        session_id:
          type: string
          nullable: true
          description: >
            Correlation id for the billable session (for example `sim-{run_uuid}` or
            `eval-{run_uuid}`).
        event_start_timestamp:
          type: string
          format: date-time
          description: When the billable work started (RFC 3339).
        event_end_timestamp:
          type: string
          format: date-time
          nullable: true
          description: When the billable work finished (null while in progress).
        event_duration_seconds:
          type: number
          nullable: true
          description: Wall-clock duration from start to end in seconds.
        product_area:
          type: string
          description: High-level product or pipeline area code (ENGINE, SCENARIO_CREATION, …).
        compute_runtime_seconds:
          type: number
          nullable: true
          description: Compute runtime attributed to the event in seconds.
        compute_machine_class:
          type: string
          nullable: true
          description: Machine class used for compute cost attribution.
        memory_tier:
          type: string
          nullable: true
          description: Memory tier used for compute cost attribution.
        input_tokens:
          type: integer
          nullable: true
          description: Sum of input tokens across child usage calls.
        output_tokens:
          type: integer
          nullable: true
          description: Sum of output tokens across child usage calls.
        cached_tokens:
          type: integer
          nullable: true
          description: Sum of cached tokens across child usage calls.
        cache_read_input_tokens:
          type: integer
          nullable: true
          description: Sum of cache-read input tokens across child usage calls.
        cache_creation_input_tokens:
          type: integer
          nullable: true
          description: Sum of cache-creation input tokens across child usage calls.
        total_calls:
          type: integer
          nullable: true
          description: Number of child usage calls rolled up into this event.
        actual_total_llm_cost:
          type: number
          nullable: true
          description: >
            Sum of LiteLLM USD actual LLM cost across child calls (`actual_total_llm_cost`). Use
            this (or `actual_total_event_cost`) to aggregate platform spend — not billing
            credits.
        actual_input_token_cost:
          type: number
          nullable: true
          description: Sum of USD input-token cost across child calls.
        actual_output_token_cost:
          type: number
          nullable: true
          description: Sum of USD output-token cost across child calls.
        actual_compute_cost:
          type: number
          nullable: true
          description: USD compute cost attributed to the event.
        actual_total_event_cost:
          type: number
          nullable: true
          description: >
            Total USD event cost (LLM + compute). Preferred field when summing per-event platform
            spend across `/v1/usage/events`.
        event_metadata:
          type: object
          nullable: true
          additionalProperties: true
          description: >
            Product-specific metadata (for example `simulation_uuid`, `job_uuid`,
            `evaluation_job_uuid`, `estimated_credits` on some ENGINE runs). Billing debits are
            written separately to the ledger; they are not returned as a top-level `credits`
            field on this object.
        failed:
          type: boolean
          nullable: true
          description: Whether the underlying work ultimately failed (null until terminal).
        created_at:
          type: string
          format: date-time
          description: Event creation timestamp (RFC 3339).

    UsageCallResponse:
      type: object
      description: >
        One LLM **provider call** row from `/v1/usage/calls`; may include extra metering fields
        from upstream (`additionalProperties: true`).
      additionalProperties: true
      properties:
        uuid:
          type: string
          format: uuid
          description: Call row UUID.
        event_uuid:
          type: string
          format: uuid
          description: Parent usage event UUID this call rolled up into.
        request_id:
          type: string
          nullable: true
          description: LiteLLM request or call id for UI correlation.
        provider_name:
          type: string
          description: LLM vendor identifier string.
        model_name:
          type: string
          description: Model id or display name for this call.
        model_version:
          type: string
          nullable: true
          description: Provider-reported model version when available.
        input_tokens:
          type: integer
          description: Billable input tokens attributed to the call.
        output_tokens:
          type: integer
          description: Billable output tokens attributed to the call.
        cached_tokens:
          type: integer
          nullable: true
          description: Cached tokens attributed to the call.
        cache_read_input_tokens:
          type: integer
          nullable: true
          description: Cache-read input tokens attributed to the call.
        cache_creation_input_tokens:
          type: integer
          nullable: true
          description: Cache-creation input tokens attributed to the call.
        actual_total_llm_cost:
          type: number
          nullable: true
          description: LiteLLM USD actual total LLM cost for this call.
        actual_input_token_cost:
          type: number
          nullable: true
          description: USD input-token cost for this call.
        actual_output_token_cost:
          type: number
          nullable: true
          description: USD output-token cost for this call.
        tool_usage_units:
          type: number
          nullable: true
          description: Non-LLM tool usage quantity (for example Tavily search credits).
        tool_usage_unit_of_measure:
          type: string
          nullable: true
          description: Unit for `tool_usage_units` (for example `credit`, `second`).
        call_start_timestamp:
          type: string
          format: date-time
          description: When the provider call began (RFC 3339).
        call_end_timestamp:
          type: string
          format: date-time
          nullable: true
          description: When the provider call finished (RFC 3339).
        created_at:
          type: string
          format: date-time
          description: Row creation timestamp (RFC 3339).

    ValidateRequest:
      type: object
      description: >
        Validates a JSON **string** against a named in-product schema before you submit
        generation payloads.
      required: [json]
      properties:
        json:
          type: string
          description: JSON string to validate.
        schema:
          type: string
          enum: [scenario]
          default: scenario
          description: >
            Validator id. Use `scenario` (validates scenario input via Pydantic; same model as
            GET /v1/validation/schema/scenario).

    ValidateResponse:
      type: object
      description: Outcome of JSON schema validation against the selected validator (`scenario`, etc.).
      properties:
        result:
          type: string
          description: >
            Human-readable report starting with `Validation Status:` and either `VALID` or
            `INVALID` plus numbered errors when invalid.
        service:
          type: string
          description: Internal validator service name that processed the request.
        request_id:
          type: string
          description: Correlation id echoed from the validator for support tickets.
        processing_time:
          type: number
          description: Server-side validation duration in seconds (floating point).

    SkillTag:
      type: object
      description: >
        One scenario skill tag. Use `name` in scenario generation requests. Filter by
        `allowed_scenario_types` before POST /v1/scenarios/generate.
      required: [name]
      properties:
        name:
          type: string
          description: Canonical tag id — pass this string in `tags` / `tag_pool`.
        category:
          type: string
          description: Grouping label.
        description:
          type: string
          description: Capability being measured.
        benchmark_family:
          oneOf:
            - type: string
            - type: array
              items:
                type: string
          description: >
            Benchmark family when set (e.g. agentharm, gaia, qna). Benchmark tags are generally
            info_exchange only; qna tags are interview only and must be the sole tag.
        allowed_scenario_types:
          type: array
          items:
            type: string
            enum: [info_exchange, interview]
          description: >
            Scenario types that may use this tag. Empty array means not selectable. When omitted,
            treat as both info_exchange and interview (UI backward compat).
        custom:
          type: boolean
          description: True when the tag is an organization custom tag (from the org overlay); false for global catalogue tags.
