> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zelinqa.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Consulter le journal des changements

> Renvoie les actions de configuration du NBQ, de la plus récente à la
plus ancienne. Cette lecture est réservée au scope
`configuration:publish` : le journal contient l'identité des opérateurs
et n'est pas une simple lecture du corpus.

Les différences sont volontairement structurelles. Elles indiquent la
ressource et les champs modifiés, mais ne recopient jamais le texte des
questions, les libellés, les prompts, les réponses d'un utilisateur ni
une clé API en clair. L'identifiant d'une clé actrice est son empreinte
SHA-256 déjà stockée par l'authorizer.

La pagination utilise un curseur opaque. Le journal des événements de
session est séparé et n'est jamais renvoyé par cette route.




## OpenAPI

````yaml /openapi.fr.yaml get /v1/configuration/audit
openapi: 3.1.0
info:
  title: NBQ Engine — API V1
  version: 1.0.0
  summary: Contrat public du runtime NBQ et de la configuration des bases de questions.
  description: >
    Contrat public de NBQ Engine V1, servi sur `https://api.zelinqa.ai`.


    Ce document est la **source de vérité** pour `nbq-sdk` (Python et
    TypeScript) et

    pour `nbq-mcp`. Les SDK et le serveur MCP sont générés ou alignés depuis ce

    contrat, jamais l'inverse.


    ## Pourquoi V1 succède à 0.9


    La version publique déployée aujourd'hui est la bêta `0.9`, déjà servie sous
    le

    préfixe `/v1`. La V1 **ajoute** ses routes sous le même préfixe sans en
    modifier

    aucune : `POST /v1/next-questions` et `POST
    /v1/sessions/{session_id}/conversion`

    restent fonctionnelles et sont marquées dépréciées. La correspondance
    complète

    est décrite dans `docs/api-compat-0.9-to-v1.md`.


    ## Principes contractuels


    - **Le tenant n'est jamais fourni par le client.** L'authorizer de l'API
    Gateway
      publique résout la clé, puis injecte le tenant, le NBQ et les scopes. Tout
      header `x-tenant-id`, `x-nbq-id` ou `x-scopes` présent dans la requête entrante
      est écrasé.
    - **Toute mutation exige `Idempotency-Key`.** Même clé et même corps
    renvoient la
      réponse d'origine ; même clé et corps différent produisent
      `idempotency_key_reused`.
    - **Toute mutation d'une session existante exige `state_version`**, en
    contrôle
      optimiste.
    - **Les identifiants du tour précédent sont optionnels.** `decision_id`,
      `question_id` et `outcome` aident NBQ ; leur absence n'est pas une erreur, le
      moteur les résout depuis la décision en attente et les messages observés. Une
      valeur explicite du client reste prioritaire sur l'inférence.
    - **Le verbatim est optionnel.** `user_text` peut être omis quand une
    réponse
      structurée ou des mises à jour client suffisent : l'extraction sémantique est
      alors dégradée, signalée par `degraded_reasons`, mais le moteur reste
      fonctionnel.
    - **Aucun interne n'est exposé.** Ni score de sélection, ni preuve
    sémantique
      détaillée, ni embedding, ni prompt, ni prototype de réponse, ni matrice
      compilée n'apparaissent dans une réponse publique.
    - **Le client ne choisit pas la version de configuration.** La session
    épingle
      en interne la version publiée active au moment de sa création.

    ## Arrêts souples


    NBQ ne décide pas à la place de l'appelant. Lorsque `max_turns` est atteint,
    que

    l'objectif est déjà atteint ou que l'éligibilité normale est vide, `/next`

    renvoie **encore la meilleure question disponible** et signale la situation
    dans

    `warnings`. Un `action: stop` n'est produit que lorsqu'aucune question

    identifiable n'existe réellement. Les exclusions dures — question inactive,

    sous-objectif exclu par le client, contrainte stricte de l'appel — ne sont
    jamais

    violées, et toute question retournée porte un `question_id` valide.


    ## Scopes de clé API


    Une clé porte un ou plusieurs scopes. Studio recommande des clés séparées
    pour

    le runtime et pour la gestion, selon le principe du moindre privilège.


    | Scope | Autorise |

    |---|---|

    | `runtime` | les cinq routes de session |

    | `configuration:read` | `GET /v1/configuration` (publiée) et `GET
    /v1/configuration/questions` |

    | `configuration:write` | `POST /v1/configuration/changes` et la lecture du
    brouillon |


    Deux niveaux de contrôle se complètent. L'authorizer décide
    **statiquement**,

    à partir de la méthode et du chemin : c'est lui qui refuse une clé `runtime`

    sur `/v1/configuration/publish`. Le service ajoute un contrôle **dynamique**

    là où le scope dépend du contenu de la requête, que l'authorizer ne voit pas
    :

    aujourd'hui le seul cas est `?state=draft`, qui exige `configuration:write`

    sur une route dont l'authorizer n'exige que `configuration:read`.

    | `configuration:publish` | `POST /v1/configuration/publish` |


    Chaque opération déclare le scope exigé dans `x-required-scopes`. Un scope

    manquant produit `403 insufficient_scope`.


    ## Frontière de confiance


    L'API Configuration est implémentée par le service `bo-api` mais atteignable
    par

    deux portes qui n'ont pas la même confiance :


    - via l'**API Gateway publique** (`6nw77hkcib`, `api.zelinqa.ai`),
    l'authorizer
      Lambda authentifie la clé et **écrase** `x-tenant-id` et `x-scopes` ; ces
      headers font alors autorité ;
    - via l'**API Gateway bo-api** (`jdkpi8qlv5`, `bo-api.zelinqa.ai`),
    l'authentification
      vient des guards NestJS et du JWT Cognito ; **tous** les headers `x-tenant-id`
      et `x-scopes` fournis par l'appelant sont ignorés.

    Le service distingue obligatoirement l'origine via `requestContext.apiId`
    avant

    de construire son contexte d'autorisation. Un JWT valide sur la porte bo-api
    ne

    peut jamais obtenir un scope de publication en forgeant un header `x-*`.

    Ce document ne décrit que la surface publique `api.zelinqa.ai`.
  contact:
    name: Zelinqa
    url: https://docs.zelinqa.ai
  license:
    name: Proprietary — Zelinqa SAS
    identifier: LicenseRef-Zelinqa-Proprietary
  x-contract-status: frozen
  x-jira: NBQ-304
  x-supersedes: openapi/nbq-runtime-v2.openapi.yaml
  x-compatibility-note: docs/api-compat-0.9-to-v1.md
servers:
  - url: https://api.zelinqa.ai
    description: API publique NBQ
security:
  - ApiKeyAuth: []
tags:
  - name: sessions
    description: |
      Cycle de vie d'une conversation NBQ : création, sélection de la question
      suivante, mises à jour hors tour, reprise et retour de résultat.
  - name: configuration
    description: |
      Lecture et modification de la configuration éditoriale d'un NBQ, puis
      publication asynchrone de l'artefact moteur.
paths:
  /v1/configuration/audit:
    get:
      tags:
        - configuration
      summary: Consulter le journal des changements
      description: |
        Renvoie les actions de configuration du NBQ, de la plus récente à la
        plus ancienne. Cette lecture est réservée au scope
        `configuration:publish` : le journal contient l'identité des opérateurs
        et n'est pas une simple lecture du corpus.

        Les différences sont volontairement structurelles. Elles indiquent la
        ressource et les champs modifiés, mais ne recopient jamais le texte des
        questions, les libellés, les prompts, les réponses d'un utilisateur ni
        une clé API en clair. L'identifiant d'une clé actrice est son empreinte
        SHA-256 déjà stockée par l'authorizer.

        La pagination utilise un curseur opaque. Le journal des événements de
        session est séparé et n'est jamais renvoyé par cette route.
      operationId: listConfigurationAudit
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: cursor
          in: query
          required: false
          schema:
            type: string
            minLength: 1
            maxLength: 512
          description: Curseur opaque renvoyé par la page précédente.
        - name: action
          in: query
          required: false
          schema:
            type: string
            minLength: 1
            maxLength: 160
        - name: resource_type
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/ConfigurationAuditResourceType'
      responses:
        '200':
          description: Page du journal d'audit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConfigurationAuditPage'
              example:
                request_id: req_8aa1
                events:
                  - id: 109a34df-1b50-4ac3-9045-01934a2fe3d8
                    occurred_at: '2026-09-02T12:44:10Z'
                    action: configuration.question.deactivated
                    origin: studio_jwt
                    actor:
                      type: cognito_user
                      id: 714c7cd0-1096-47e4-9f6b-e73d907f281f
                    scopes:
                      - configuration:read
                      - configuration:write
                      - configuration:publish
                    request_id: req_71aa
                    resource:
                      type: question
                      id: q_style
                    diff:
                      before:
                        active: true
                      after:
                        active: false
                    details:
                      config_version_id: 8e9f2cf0-f25d-4900-a066-e87fe6424d0b
                      draft_revision: 43
                next_cursor: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '404':
          $ref: '#/components/responses/UnknownConfiguration'
        '422':
          $ref: '#/components/responses/ConfigurationValidationFailed'
components:
  schemas:
    ConfigurationAuditResourceType:
      type: string
      enum:
        - objective
        - sub_objective
        - success_information
        - question
        - configuration
        - api_key
        - nbq
    ConfigurationAuditPage:
      type: object
      additionalProperties: false
      required:
        - request_id
        - events
        - next_cursor
      properties:
        request_id:
          type: string
        events:
          type: array
          items:
            $ref: '#/components/schemas/ConfigurationAuditEvent'
        next_cursor:
          type:
            - string
            - 'null'
          description: Curseur de la page suivante, `null` sur la dernière page.
    ConfigurationAuditEvent:
      type: object
      additionalProperties: false
      required:
        - id
        - occurred_at
        - action
        - origin
        - actor
        - scopes
        - request_id
        - resource
        - diff
        - details
      properties:
        id:
          type: string
          format: uuid
        occurred_at:
          type: string
          format: date-time
        action:
          type: string
          minLength: 1
          maxLength: 160
        origin:
          type: string
          enum:
            - public_api
            - studio_jwt
        actor:
          $ref: '#/components/schemas/ConfigurationAuditActor'
        scopes:
          type: array
          uniqueItems: true
          items:
            type: string
        request_id:
          type: string
          minLength: 1
          maxLength: 200
        resource:
          $ref: '#/components/schemas/ConfigurationAuditResource'
        diff:
          $ref: '#/components/schemas/ConfigurationAuditDiff'
        details:
          type: object
          additionalProperties: true
          description: >-
            Identifiants et compteurs techniques bornés, sans contenu
            conversationnel ou secret.
    ErrorEnvelope:
      type: object
      additionalProperties: false
      required:
        - code
        - message
        - request_id
        - details
      description: Enveloppe uniforme de toutes les erreurs métier.
      properties:
        code:
          $ref: '#/components/schemas/ErrorCode'
        message:
          type: string
          minLength: 1
        request_id:
          type: string
          minLength: 1
        details:
          type: object
          additionalProperties: true
    ConfigurationAuditActor:
      type: object
      additionalProperties: false
      required:
        - type
        - id
      properties:
        type:
          type: string
          enum:
            - api_key
            - cognito_user
        id:
          type: string
          minLength: 1
          maxLength: 200
          description: >-
            Identifiant utilisateur interne, ou empreinte SHA-256 de la clé API
            ; jamais la clé en clair.
    ConfigurationAuditResource:
      type: object
      additionalProperties: false
      required:
        - type
        - id
      properties:
        type:
          $ref: '#/components/schemas/ConfigurationAuditResourceType'
        id:
          type: string
          minLength: 1
          maxLength: 200
    ConfigurationAuditDiff:
      type: object
      additionalProperties: false
      required:
        - before
        - after
      description: >
        Vue structurelle assainie. Les valeurs éditoriales libres sont
        remplacées

        par une indication de changement ; seules les valeurs moteur non
        sensibles

        comme `active`, `order_position` ou `completion_role` sont conservées.
      properties:
        before:
          type:
            - object
            - 'null'
          additionalProperties: true
        after:
          type:
            - object
            - 'null'
          additionalProperties: true
    ErrorCode:
      type: string
      description: Catalogue fermé des erreurs métier V1.
      enum:
        - unauthorized
        - insufficient_scope
        - idempotency_contention
        - state_version_conflict
        - idempotency_key_reused
        - unknown_session
        - invalid_previous_turn
        - constraint_no_match
        - invalid_choice
        - compiled_artifact_unavailable
        - configuration_validation_failed
        - compilation_in_progress
        - unknown_configuration
        - unknown_compilation
  responses:
    Unauthorized:
      description: >
        Clé absente, malformée, inconnue, révoquée, expirée, ou rattachée à un
        NBQ

        suspendu. Le refus vient de l'authorizer de la passerelle, avant que la

        requête n'atteigne le service : le corps est donc produit par la
        passerelle

        et peut ne pas suivre l'enveloppe d'erreur métier.


        La distinction avec `403` est nette : `401` signifie « je ne sais pas
        qui

        vous êtes », `403` signifie « je sais qui vous êtes, mais cette clé ne
        porte

        pas le scope requis ».
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            code: unauthorized
            message: Clé d'intégration absente ou invalide.
            request_id: req_9000
            details: {}
    InsufficientScope:
      description: >
        La clé est valide mais ne porte pas le scope exigé par l'opération. Une
        clé

        absente ou invalide produit `401` au niveau de la passerelle, avant
        d'atteindre

        le service.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            code: insufficient_scope
            message: Cette clé ne permet pas de publier une configuration.
            request_id: req_9001
            details:
              required_scopes:
                - configuration:publish
              granted_scopes:
                - configuration:read
                - configuration:write
    UnknownConfiguration:
      description: >
        Aucune configuration ne correspond à l'état demandé — par exemple un
        brouillon

        qui n'a jamais été créé.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            code: unknown_configuration
            message: Aucun brouillon n'existe pour cette configuration.
            request_id: req_9008
            details:
              state: draft
    ConfigurationValidationFailed:
      description: |
        La validation du brouillon a échoué. Toutes les anomalies sont renvoyées
        ensemble, avec la position de l'opération fautive lorsqu'elle vient d'un
        changement, afin que Studio puisse les afficher d'un seul coup.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          examples:
            validation_publication:
              summary: Le brouillon n'est pas publiable
              value:
                code: configuration_validation_failed
                message: La configuration comporte 2 anomalies bloquantes.
                request_id: req_9015
                details:
                  issues:
                    - code: success_information_without_active_question
                      message: >-
                        L'information « Fenêtre de livraison souhaitée » n'a pas
                        de question active.
                      entity: success_information
                      entity_id: delivery_window
                    - code: success_information_only_in_optional_sub_objective
                      message: >-
                        L'information « Budget annuel » ne dépend que d'un
                        sous-objectif optionnel.
                      entity: success_information
                      entity_id: annual_budget
            revision_perimee:
              summary: Le brouillon a changé depuis la lecture du client
              value:
                code: configuration_validation_failed
                message: Le brouillon a été modifié depuis votre dernière lecture.
                request_id: req_9016
                details:
                  issues:
                    - code: draft_revision_mismatch
                      message: Révision attendue 42, révision courante 44.
                      entity: objective
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: NBQ API key
      description: >
        Clé d'intégration préfixée `nbq_live_`, envoyée dans

        `Authorization: Bearer nbq_live_…` et résolue par l'authorizer Lambda de

        l'API Gateway publique. L'authorizer valide la clé, puis **injecte** en
        amont

        du service les headers de contexte `x-tenant-id`, `x-nbq-id` et
        `x-scopes` —

        `x-scopes` étant la liste des scopes de la clé séparés par des
        **virgules**,

        par exemple `runtime,configuration:read`.


        Ces headers ne sont jamais acceptés depuis le client : toute valeur
        entrante

        est écrasée. Une clé absente ou invalide produit `401` au niveau de la

        passerelle ; une clé valide sans le scope requis produit `403` avec le
        code

        `insufficient_scope`.

````