openapi: 3.1.0
info:
  title: ReisMaker Agent téléphonique API
  version: 1.0.0
  description: >-
    API REST pour connecter le serveur d'une entreprise à son agent téléphonique
    ReisMaker. Créez d'abord une clé dans Agent téléphonique > API d'entreprise.
    Chaque clé est limitée à son compte ou à son organisation. Les appels réels,
    la publication d'agent et les écritures CRM réelles restent désactivés par défaut.
    La configuration SIP est disponible uniquement pour les organisations en Production
    et ne déverrouille pas les appels réels.
    Les organisations peuvent aussi recevoir des événements webhook signés. La livraison réelle
    reste désactivée jusqu'à l'activation explicite du worker côté serveur.
servers:
  - url: https://reismaker.ca/api/v1/telephone
    description: API ReisMaker
security:
  - bearerAuth: []
  - reismakerApiKey: []
tags:
  - name: Capabilities
  - name: Agent
  - name: Contacts
  - name: Calls
  - name: Events
  - name: Integrations
  - name: Webhooks
  - name: Transport
paths:
  /events:
    get:
      tags: [Events]
      operationId: listTelephoneEvents
      summary: Parcourir le flux d'événements d'une organisation
      description: >-
        Lit le journal transactionnel, même lorsqu'aucun webhook n'est configuré.
        Le curseur est opaque et lié à l'organisation et à l'environnement de la clé.
        Les résultats sont ordonnés de façon stable, du plus ancien au plus récent.
      x-required-scope: telephone:events:read
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: after
          in: query
          description: Curseur opaque retourné par une page précédente.
          schema:
            type: string
            maxLength: 512
      responses:
        "200":
          description: Page d'événements; réutilisez next_cursor comme after.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [events, next_cursor, has_more]
                properties:
                  events:
                    type: array
                    items:
                      $ref: "#/components/schemas/TelephoneEvent"
                  next_cursor:
                    type: [string, "null"]
                  has_more:
                    type: boolean
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Permission de lecture absente ou clé sans organisation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
  /capabilities:
    get:
      tags: [Capabilities]
      operationId: getTelephoneCapabilities
      summary: Lire les fonctions et permissions disponibles
      x-required-scope: telephone:catalog:read
      responses:
        "200":
          description: Capacités de l'API et état actuel des fonctions.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CapabilitiesResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "429":
          $ref: "#/components/responses/RateLimited"
  /agent:
    get:
      tags: [Agent]
      operationId: getTelephoneAgent
      summary: Lire le brouillon d'agent associé à la clé
      x-required-scope: telephone:agents:read
      responses:
        "200":
          description: Configuration dans l'espace associé à la clé.
          content:
            application/json:
              schema:
                type: object
                required: [agent]
                properties:
                  agent:
                    $ref: "#/components/schemas/TelephoneAgent"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "429":
          $ref: "#/components/responses/RateLimited"
    put:
      tags: [Agent]
      operationId: updateTelephoneAgent
      summary: Enregistrer le brouillon d'agent
      description: >-
        Les faits de l'offre doivent inclure une source fournie par l'entreprise.
        La divulgation doit identifier clairement l'agent comme une IA.
        Cette route ne sauvegarde qu'un brouillon versionné et ne change jamais le pointeur publié.
        Ne fournissez pas le champ published; les booléens explicites sont refusés avec 409.
        Utilisez les routes publish et unpublish avec la révision et le SHA-256 attendus.
      x-required-scope: telephone:agents:write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TelephoneAgentInput"
      responses:
        "200":
          description: Brouillon enregistré.
          content:
            application/json:
              schema:
                type: object
                required: [agent]
                properties:
                  agent:
                    $ref: "#/components/schemas/TelephoneAgent"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
  /agent/versions:
    get:
      tags: [Agent]
      operationId: listTelephoneAgentVersions
      summary: Lister les versions du profil d'une organisation
      description: >-
        Réservé à une clé d'organisation et à un membre seller, manager ou owner.
        Retourne les métadonnées des 100 versions les plus récentes, sans le profil complet.
      x-required-scope: telephone:agents:read
      x-required-role: organization-member
      responses:
        "200":
          description: Versions du profil et indicateurs brouillon/publiée.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TelephoneAgentVersionsResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
  /agent/publish:
    post:
      tags: [Agent]
      operationId: publishTelephoneAgent
      summary: Publier le brouillon versionné d'une organisation
      description: >-
        Exige une clé Production d'organisation, le rôle owner/manager, la divulgation IA
        affirmative et le commutateur serveur de publication. La révision et le SHA-256 doivent
        encore correspondre au brouillon courant. La publication ne lance aucun appel.
        Les appels déjà démarrés conservent leur version épinglée.
      x-required-scope: telephone:agents:write
      x-required-role: organization-owner-or-manager
      x-required-environment: live
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TelephoneAgentVersionMutation"
      responses:
        "200":
          description: Le brouillon courant est devenu la version publiée.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TelephoneAgentResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/FeatureDisabled"
  /agent/unpublish:
    post:
      tags: [Agent]
      operationId: unpublishTelephoneAgent
      summary: Désactiver la version publiée d'une organisation
      description: >-
        Exige une clé Production d'organisation et le rôle owner/manager. La révision et le
        SHA-256 doivent correspondre à la version actuellement publiée. Les appels déjà démarrés
        conservent leur version épinglée; les nouveaux appels ne peuvent plus utiliser l'agent.
      x-required-scope: telephone:agents:write
      x-required-role: organization-owner-or-manager
      x-required-environment: live
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TelephoneAgentVersionMutation"
      responses:
        "200":
          description: Le pointeur publié a été retiré.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TelephoneAgentResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
  /contacts/do-not-call:
    post:
      tags: [Contacts]
      operationId: suppressTelephoneContact
      summary: Ajouter un numéro à la liste interne « ne pas appeler »
      description: >-
        Clé Production d'organisation, rôle owner ou manager et permission
        telephone:contacts:write requis. Le numéro est indexé par HMAC côté
        serveur; le numéro en clair n'est pas conservé ni retourné. La V1 ne
        fournit pas de route pour lever une suppression. Cette liste ne remplace
        pas la preuve de consentement ni la vérification de la liste nationale.
        Ne journalisez pas le corps de la requête.
      x-required-scope: telephone:contacts:write
      x-required-role: organization-owner-or-manager
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [phone_number, reason]
              properties:
                phone_number:
                  type: string
                  pattern: '^\+[1-9][0-9]{7,14}$'
                  description: Numéro E.164; il n'est ni retourné ni stocké en clair.
                reason:
                  type: string
                  enum: [caller_request, business_policy]
      responses:
        "200":
          description: Suppression permanente enregistrée de façon idempotente.
          content:
            application/json:
              schema:
                type: object
                required: [suppression]
                properties:
                  suppression:
                    type: object
                    required: [suppressed, created]
                    properties:
                      suppressed:
                        type: boolean
                        const: true
                      created:
                        type: boolean
                        description: false si le numéro était déjà supprimé dans cette organisation.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          description: Clé serveur absente ou changement de clé nécessitant une migration.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
  /contacts/consents:
    post:
      tags: [Contacts]
      operationId: recordTelephoneContactConsentEvidence
      summary: Enregistrer des métadonnées de consentement déclarées par le client
      description: >-
        Clé Production d'organisation, rôle owner ou manager et permission
        telephone:contacts:write requis. Le client conserve la preuve originale;
        ReisMaker conserve ses empreintes SHA-256 et une empreinte HMAC du numéro,
        jamais le numéro en clair ni la preuve. Les quatre attestations exigent
        un consentement précis au numéro, à l'annonceur, aux appels de vente et à
        une voix IA. Cette donnée n'est ni une validation juridique ni une
        autorisation d'appel. La liste « ne pas appeler » prévaut toujours et
        les appels réels restent désactivés.
      x-required-scope: telephone:contacts:write
      x-required-role: organization-owner-or-manager
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - source_event_id
                - phone_number
                - jurisdiction
                - contact_type
                - phone_source
                - phone_collected_at
                - phone_source_evidence_sha256
                - consent_method
                - consented_at
                - consent_scope
                - consent_evidence_sha256
                - consent_text_sha256
                - attestations
              properties:
                source_event_id:
                  type: string
                  format: uuid
                  description: Identifiant idempotent généré par l'entreprise; unique par organisation.
                phone_number:
                  type: string
                  pattern: '^\+[1-9][0-9]{7,14}$'
                  description: Numéro E.164 utilisé uniquement pour produire une empreinte HMAC.
                jurisdiction:
                  type: string
                  enum: [CA-AB, CA-BC, CA-MB, CA-NB, CA-NL, CA-NS, CA-NT, CA-NU, CA-ON, CA-PE, CA-QC, CA-SK, CA-YT]
                contact_type:
                  type: string
                  enum: [consumer, business]
                phone_source:
                  type: string
                  enum: [customer_provided, existing_customer, inbound_call, public_directory, third_party_list, other]
                  description: Provenance déclarée par l'entreprise; elle ne prouve pas la légalité de la collecte.
                phone_collected_at:
                  type: string
                  format: date-time
                  description: Date de collecte du numéro, ISO 8601 avec fuseau horaire; une date future est refusée.
                phone_source_evidence_sha256:
                  type: string
                  pattern: '^[0-9a-fA-F]{64}$'
                  description: SHA-256 d'une preuve de provenance gardée par l'entreprise; la preuve n'est pas transmise.
                consent_method:
                  type: string
                  enum: [web_form, inbound_call, written]
                consented_at:
                  type: string
                  format: date-time
                  description: Date ISO 8601 avec fuseau horaire; une date future est refusée.
                consent_scope:
                  type: string
                  const: ai_sales_call
                consent_evidence_sha256:
                  type: string
                  pattern: '^[0-9a-fA-F]{64}$'
                  description: SHA-256 d'une preuve conservée par l'entreprise; la preuve n'est pas transmise.
                consent_text_sha256:
                  type: string
                  pattern: '^[0-9a-fA-F]{64}$'
                  description: SHA-256 du texte exact présenté au moment du consentement.
                attestations:
                  type: object
                  additionalProperties: false
                  required: [number_specific, named_advertiser, sales_calls, ai_voice, phone_source_documented]
                  properties:
                    number_specific:
                      type: boolean
                      const: true
                    named_advertiser:
                      type: boolean
                      const: true
                    sales_calls:
                      type: boolean
                      const: true
                    ai_voice:
                      type: boolean
                      const: true
                    phone_source_documented:
                      type: boolean
                      const: true
      responses:
        "200":
          description: Métadonnées enregistrées; une répétition identique est idempotente.
          content:
            application/json:
              schema:
                type: object
                required: [consent]
                properties:
                  consent:
                    type: object
                    required: [recorded, created]
                    properties:
                      recorded:
                        type: boolean
                        const: true
                      created:
                        type: boolean
                        description: false lorsque le même événement avait déjà été enregistré.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          description: L'identifiant d'événement a déjà été utilisé avec un autre contenu.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          description: Clé HMAC indisponible ou migration requise après rotation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
  /contacts/preflight:
    post:
      tags: [Contacts]
      operationId: preflightTelephoneSalesContact
      summary: Vérifier les signaux internes sans autoriser un appel
      description: >-
        Clé Production d'organisation, permission telephone:contacts:read et rôle
        owner/manager requis. Le numéro sert uniquement à calculer une empreinte
        HMAC et n'est ni retourné ni conservé. La réponse vérifie l'exclusion
        interne et les métadonnées d'attestation qui correspondent au numéro,
        au marché et au type de contact. recipient_timezone est fourni par le
        client; la route vérifie la plage fédérale CRTC de base à l'heure
        courante dans ce fuseau. Elle ne valide pas ce fuseau, les règles
        régionales plus strictes, la Liste nationale, l'identification de
        l'annonceur ou la validité des preuves. Les champs optionnels campaign_sector
        et call_reason ajoutent un signal limité; les autres règles sectorielles
        restent à vérifier. call_authorized vaut toujours false; cette réponse
        ne permet pas de composer.
      x-required-scope: telephone:contacts:read
      x-required-role: organization-owner-or-manager
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [phone_number, jurisdiction, contact_type, recipient_timezone]
              properties:
                phone_number:
                  type: string
                  pattern: '^\+[1-9][0-9]{7,14}$'
                  description: Numéro E.164; utilisé seulement pour calculer une empreinte HMAC.
                jurisdiction:
                  type: string
                  enum: [CA-AB, CA-BC, CA-MB, CA-NB, CA-NL, CA-NS, CA-NT, CA-NU, CA-ON, CA-PE, CA-QC, CA-SK, CA-YT]
                contact_type:
                  type: string
                  enum: [consumer, business]
                recipient_timezone:
                  type: string
                  minLength: 1
                  maxLength: 128
                  description: Identifiant IANA fourni par le client, par exemple America/Toronto; non vérifié.
                campaign_sector:
                  type: string
                  minLength: 1
                  maxLength: 80
                  pattern: '^[a-z][a-z0-9_-]{0,79}$'
                  description: Code de secteur déclaré par l’entreprise; les secteurs non cartographiés restent non vérifiés.
                call_reason:
                  type: string
                  enum: [inbound, customer_requested_follow_up, unsolicited_outbound]
                  description: Motif déclaré; une demande client reste à vérifier sur sa preuve originale.
      responses:
        "200":
          description: Contrôles internes observés; un appel n'est jamais autorisé par cette route.
          content:
            application/json:
              schema:
                type: object
                required: [preflight]
                properties:
                  preflight:
                    type: object
                    required: [decision, call_authorized, checks]
                    properties:
                      decision:
                        type: string
                        enum: [blocked, incomplete]
                      call_authorized:
                        type: boolean
                        const: false
                      checks:
                        type: object
                        required:
                          - internal_suppression
                          - recorded_consent_metadata
                          - national_dncl
                          - calling_window
                          - regional_calling_rules
                          - sector_specific_rules
                          - advertiser_identification
                          - live_calling
                        properties:
                          internal_suppression:
                            type: string
                            enum: [blocked, clear]
                          recorded_consent_metadata:
                            type: string
                            enum: [present, missing]
                          national_dncl:
                            type: string
                            const: not_checked
                          calling_window:
                            type: string
                            enum: [within_federal_baseline, outside_federal_baseline]
                            description: Plage fédérale de base selon le fuseau fourni; ne couvre pas les règles régionales plus strictes.
                          regional_calling_rules:
                            type: string
                            const: not_checked
                          sector_specific_rules:
                            type: string
                            enum:
                              - blocked_no_customer_request
                              - customer_request_unverified
                              - not_applicable_inbound
                              - not_checked
                          advertiser_identification:
                            type: string
                            const: not_checked
                          live_calling:
                            type: string
                            const: disabled
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          description: Clé HMAC absente ou rotation exigeant une migration.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
  /calls:
    get:
      tags: [Calls]
      operationId: listTelephoneCalls
      summary: Parcourir les appels de l'environnement de la clé
      x-required-scope: telephone:calls:read
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: before
          in: query
          description: Curseur retourné par une page précédente.
          schema:
            type: string
      responses:
        "200":
          description: Page d'appels; next_cursor peut être réutilisé comme before.
          content:
            application/json:
              schema:
                type: object
                required: [calls, next_cursor]
                properties:
                  calls:
                    type: array
                    items:
                      $ref: "#/components/schemas/TelephoneCall"
                  next_cursor:
                    type: [string, "null"]
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
    post:
      tags: [Calls]
      operationId: createTelephoneCall
      summary: Créer une demande d'appel simulée
      description: >-
        Disponible uniquement avec une clé Test. Ne compose aucun numéro et ne
        contacte aucun service tiers. Préférez Idempotency-Key; une répétition
        identique retourne l'appel existant et un contenu différent avec la même
        clé retourne 409. Une clé Production reçoit 503 tant que les appels réels
        ne sont pas ouverts.
      x-required-scope: telephone:calls:write
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TestCallInput"
      responses:
        "200":
          description: Demande idempotente déjà créée.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallWriteResponse"
        "201":
          description: Demande simulée créée.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallWriteResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/FeatureDisabled"
  /calls/{call_id}:
    get:
      tags: [Calls]
      operationId: getTelephoneCall
      summary: Lire un appel associé à la clé
      x-required-scope: telephone:calls:read
      parameters:
        - $ref: "#/components/parameters/CallId"
      responses:
        "200":
          description: Appel dans l'espace et l'environnement de la clé.
          content:
            application/json:
              schema:
                type: object
                required: [call]
                properties:
                  call:
                    $ref: "#/components/schemas/TelephoneCall"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /calls/{call_id}/reconcile:
    post:
      tags: [Calls]
      operationId: reconcileTelephoneCallUsage
      summary: Autoriser un plafond plus élevé pour un reçu mesuré
      description: >-
        Réservé à une clé Production d'organisation créée par un owner ou
        manager. Ne crée ni ne relance un appel. Le débit demeure limité au coût
        fournisseur mesuré et au multiplicateur du produit.
      x-required-scope: telephone:calls:write
      x-required-role: organization-owner-or-manager
      parameters:
        - $ref: "#/components/parameters/CallId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [maximum_customer_charge_cents]
              properties:
                maximum_customer_charge_cents:
                  type: integer
                  minimum: 1
                  description: Plafond autorisé en cents CAD; il doit couvrir la part de l'appel dans le cumul facturé.
      responses:
        "200":
          description: Reçu rapproché sous le plafond autorisé.
          content:
            application/json:
              schema:
                type: object
                required: [call, replayed, charge_created]
                properties:
                  call:
                    $ref: "#/components/schemas/TelephoneCall"
                  replayed:
                    type: boolean
                  charge_created:
                    type: boolean
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "503":
          $ref: "#/components/responses/FeatureDisabled"
  /transport/sip-trunk:
    get:
      tags: [Transport]
      operationId: getTelephoneSipTrunk
      summary: Lire les métadonnées du trunk SIP de l'organisation
      description: Les identifiants SIP ne sont jamais retournés. Une clé Production d'organisation et un rôle owner ou manager sont requis.
      x-required-scope: telephone:transport:read
      x-required-role: organization-owner-or-manager
      responses:
        "200":
          description: Configuration publique du trunk, ou null si elle n'existe pas.
          content:
            application/json:
              schema:
                type: object
                required: [sip_trunk]
                properties:
                  sip_trunk:
                    oneOf:
                      - $ref: "#/components/schemas/TelephoneSipTrunk"
                      - type: "null"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
    put:
      tags: [Transport]
      operationId: setTelephoneSipTrunk
      summary: Enregistrer ou remplacer le trunk SIP de l'organisation
      description: >-
        Clé Production d'organisation et rôle owner ou manager requis. Les
        identifiants sont chiffrés côté serveur et ne sont jamais retournés.
        Cette route ne lance et n'active aucun appel réel. Utilisez-la depuis
        un serveur de confiance, jamais depuis le navigateur.
      x-required-scope: telephone:transport:write
      x-required-role: organization-owner-or-manager
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TelephoneSipTrunkInput"
      responses:
        "200":
          description: Trunk enregistré sans retourner ses identifiants.
          content:
            application/json:
              schema:
                type: object
                required: [sip_trunk]
                properties:
                  sip_trunk:
                    $ref: "#/components/schemas/TelephoneSipTrunk"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          description: Clé de chiffrement serveur absente ou invalide.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
    delete:
      tags: [Transport]
      operationId: revokeTelephoneSipTrunk
      summary: Révoquer le trunk et effacer les identifiants chiffrés
      x-required-scope: telephone:transport:write
      x-required-role: organization-owner-or-manager
      responses:
        "200":
          description: Configuration révoquée et identifiants effacés.
          content:
            application/json:
              schema:
                type: object
                required: [revoked]
                properties:
                  revoked:
                    const: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
  /transport/inbound-routes:
    get:
      tags: [Transport]
      operationId: listTelephoneInboundRoutes
      summary: Lister les routes SIP entrantes de l'organisation
      description: Les secrets de routage ne sont jamais retournés après leur création.
      x-required-scope: telephone:transport:read
      x-required-role: organization-owner-or-manager
      responses:
        "200":
          description: Routes de l'organisation de la clé Production.
          content:
            application/json:
              schema:
                type: object
                required: [routes]
                properties:
                  routes:
                    type: array
                    items:
                      $ref: "#/components/schemas/TelephoneInboundRoute"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
    post:
      tags: [Transport]
      operationId: createTelephoneInboundRoute
      summary: Créer un secret de routage SIP entrant
      description: >-
        Le secret est révélé une seule fois. Configurez le PBX de confiance pour
        remplacer tout en-tête fourni par l'appelant et ajouter
        X-ReisMaker-Inbound-Route à l'INVITE SIP. Cette opération ne lance et
        n'accepte aucun appel.
      x-required-scope: telephone:transport:write
      x-required-role: organization-owner-or-manager
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TelephoneInboundRouteInput"
      responses:
        "200":
          description: Route et secret à conserver dans le coffre du serveur ou du PBX.
          content:
            application/json:
              schema:
                type: object
                required: [route, secret]
                properties:
                  route:
                    $ref: "#/components/schemas/TelephoneInboundRoute"
                  secret:
                    type: string
                    pattern: "^inr_[A-Za-z0-9_-]{43}$"
                    description: Secret à usage de routage, visible uniquement lors de la création.
                    x-secret-one-time: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
  /transport/inbound-routes/{route_id}:
    delete:
      tags: [Transport]
      operationId: revokeTelephoneInboundRoute
      summary: Révoquer un secret de routage SIP
      x-required-scope: telephone:transport:write
      x-required-role: organization-owner-or-manager
      parameters:
        - name: route_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Route révoquée.
          content:
            application/json:
              schema:
                type: object
                required: [revoked]
                properties:
                  revoked:
                    const: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
  /integrations:
    get:
      tags: [Integrations]
      operationId: listTelephoneIntegrations
      summary: Lister les connecteurs de la clé
      x-required-scope: telephone:integrations:read
      responses:
        "200":
          description: Connecteurs de l'espace et de l'environnement de la clé.
          content:
            application/json:
              schema:
                type: object
                required: [integrations]
                properties:
                  integrations:
                    type: array
                    items:
                      $ref: "#/components/schemas/TelephoneIntegration"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "429":
          $ref: "#/components/responses/RateLimited"
    post:
      tags: [Integrations]
      operationId: createTelephoneIntegration
      summary: Déclarer un connecteur HTTPS et ses opérations fixes
      description: >-
        Une clé Test valide la configuration sans connexion réseau et doit
        omettre les secrets. Une clé Production peut enregistrer un secret
        chiffré côté serveur; son exécution demeure désactivée par défaut.
        L'agent ne choisit jamais d'URL ni de chemin libre à l'exécution.
      x-required-scope: telephone:integrations:write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TelephoneIntegrationInput"
      responses:
        "200":
          description: Connecteur enregistré; le secret n'est jamais retourné.
          content:
            application/json:
              schema:
                type: object
                required: [integration]
                properties:
                  integration:
                    $ref: "#/components/schemas/TelephoneIntegration"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/FeatureDisabled"
  /integrations/{integration_id}:
    delete:
      tags: [Integrations]
      operationId: revokeTelephoneIntegration
      summary: Révoquer un connecteur et effacer son secret conservé
      x-required-scope: telephone:integrations:write
      parameters:
        - $ref: "#/components/parameters/IntegrationId"
      responses:
        "200":
          description: Connecteur révoqué.
          content:
            application/json:
              schema:
                type: object
                required: [revoked]
                properties:
                  revoked:
                    type: boolean
                    const: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /integrations/{integration_id}/operations/{operation_id}:
    post:
      tags: [Integrations]
      operationId: executeTelephoneIntegrationOperation
      summary: Exécuter une opération déclarée
      description: >-
        Le corps doit respecter le input_schema configuré. Les clés Test
        retournent un résultat simulé sans appeler le CRM. Les écritures
        Production sont fermées par défaut. Une issue réseau incertaine donne
        409; consultez l'état avant toute autre action.
      x-required-scope: telephone:integrations:execute
      x-operation-required-business-scopes: true
      parameters:
        - $ref: "#/components/parameters/IntegrationId"
        - name: operation_id
          in: path
          required: true
          schema:
            type: string
            pattern: "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
        - $ref: "#/components/parameters/IdempotencyKeyRequired"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
              description: Doit respecter le input_schema de l'opération déclarée.
      responses:
        "200":
          description: Résultat simulé ou résultat CRM validé selon output_schema.
          content:
            application/json:
              schema:
                type: object
                required: [execution]
                properties:
                  execution:
                    $ref: "#/components/schemas/IntegrationExecutionResult"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
        "502":
          description: Échec du service externe; contenu brut masqué.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "503":
          $ref: "#/components/responses/FeatureDisabled"
  /integrations/{integration_id}/executions/{execution_id}:
    get:
      tags: [Integrations]
      operationId: getTelephoneIntegrationExecution
      summary: Lire l'état d'une exécution
      x-required-scope: telephone:integrations:read
      parameters:
        - $ref: "#/components/parameters/IntegrationId"
        - name: execution_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: État limité à l'espace et l'environnement de la clé.
          content:
            application/json:
              schema:
                type: object
                required: [execution]
                properties:
                  execution:
                    $ref: "#/components/schemas/IntegrationExecutionStatus"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /webhooks:
    get:
      tags: [Webhooks]
      operationId: listTelephoneWebhooks
      summary: Lister les destinations webhook de l'organisation
      x-required-scope: telephone:webhooks:read
      responses:
        "200":
          description: Destinations de l'environnement associé à la clé.
          content:
            application/json:
              schema:
                type: object
                required: [webhooks]
                properties:
                  webhooks:
                    type: array
                    items:
                      $ref: "#/components/schemas/TelephoneWebhookEndpoint"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "429":
          $ref: "#/components/responses/RateLimited"
    post:
      tags: [Webhooks]
      operationId: createTelephoneWebhook
      summary: Enregistrer une destination de réception HTTPS
      description: >-
        Réservé aux owners et managers d'une organisation. Le secret de signature
        est retourné une seule fois. La livraison reste en file si le worker n'est
        pas activé par l'opérateur ReisMaker.
      x-required-scope: telephone:webhooks:write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TelephoneWebhookInput"
      responses:
        "201":
          description: Destination créée; secret de vérification affiché une seule fois.
          content:
            application/json:
              schema:
                type: object
                required: [webhook]
                properties:
                  webhook:
                    $ref: "#/components/schemas/TelephoneWebhookCreated"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/FeatureDisabled"
  /webhooks/{endpoint_id}:
    delete:
      tags: [Webhooks]
      operationId: revokeTelephoneWebhook
      summary: Révoquer une destination webhook
      x-required-scope: telephone:webhooks:write
      parameters:
        - name: endpoint_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Destination révoquée et livraisons en attente annulées.
          content:
            application/json:
              schema:
                type: object
                required: [revoked]
                properties:
                  revoked:
                    const: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /webhooks/{endpoint_id}/deliveries:
    get:
      tags: [Webhooks]
      operationId: listTelephoneWebhookDeliveries
      summary: Consulter l'état des livraisons d'une destination
      x-required-scope: telephone:webhooks:read
      parameters:
        - name: endpoint_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
      responses:
        "200":
          description: Historique sans corps brut ni donnée personnelle de l'événement.
          content:
            application/json:
              schema:
                type: object
                required: [deliveries]
                properties:
                  deliveries:
                    type: array
                    items:
                      $ref: "#/components/schemas/TelephoneWebhookDelivery"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/ScopeRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/InvalidRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Clé créée dans le portail Agent téléphonique.
    reismakerApiKey:
      type: apiKey
      in: header
      name: X-ReisMaker-API-Key
      description: Autre en-tête accepté pour une clé créée dans le portail.
  parameters:
    CallId:
      name: call_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    IntegrationId:
      name: integration_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: Obligatoire pour créer une demande d'appel; maximum 64 caractères.
      schema:
        type: string
        maxLength: 64
        pattern: "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
    IdempotencyKeyRequired:
      name: Idempotency-Key
      in: header
      required: true
      description: Clé unique par intention; ne rejouez pas un résultat incertain.
      schema:
        type: string
        minLength: 1
        maxLength: 128
        pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"
  schemas:
    CapabilitiesResponse:
      type: object
      required: [product, api_version, supported_languages, supported_voices, scopes, calls, connectors, pricing]
      properties:
        product:
          const: telephone-agent
        api_version:
          const: v1
        supported_languages:
          type: array
          items:
            type: string
            enum: [fr-CA, en-CA]
        supported_voices:
          type: array
          items:
            type: string
        scopes:
          type: array
          items:
            type: string
        calls:
          type: object
          required: [test, live]
          properties:
            test:
              const: simulated
            live:
              const: disabled
        connectors:
          type: object
          required: [test, live]
          properties:
            test:
              const: simulated_without_network
            live:
              const: disabled_by_default
        pricing:
          type: object
          required: [billing_basis, markup_multiplier]
          properties:
            billing_basis:
              const: measured_provider_cost
            markup_multiplier:
              type: number
              const: 4
    TelephoneAgentInput:
      type: object
      additionalProperties: false
      required: [name, language, voice]
      properties:
        name:
          type: string
          maxLength: 80
        business_name:
          type: string
          maxLength: 120
        language:
          type: string
          enum: [fr-CA, en-CA]
        voice:
          type: string
          enum: [marin, cedar, alloy, verse]
        offer_summary:
          type: string
          maxLength: 4000
        offer_facts:
          type: array
          maxItems: 20
          items:
            type: object
            additionalProperties: false
            required: [claim, source]
            properties:
              claim:
                type: string
                minLength: 1
                maxLength: 600
              source:
                type: string
                minLength: 1
                maxLength: 160
        human_contact:
          $ref: "#/components/schemas/TelephoneHumanContact"
        opening_line:
          type: string
          maxLength: 600
        disclosure:
          type: string
          minLength: 1
          maxLength: 350
          description: Doit identifier l'agent comme une IA.
    TelephoneAgent:
      type: object
      additionalProperties: false
      required:
        [name, business_name, language, voice, offer_summary, offer_facts, opening_line, disclosure,
         published, updated_at, human_contact, profile_version_id, profile_revision,
         profile_content_sha256, published_profile_version_id, published_profile_revision,
         published_content_sha256]
      properties:
        name:
          type: string
          maxLength: 80
        business_name:
          type: string
          maxLength: 120
        language:
          type: string
          enum: [fr-CA, en-CA]
        voice:
          type: string
          enum: [marin, cedar, alloy, verse]
        offer_summary:
          type: string
          maxLength: 4000
        offer_facts:
          type: array
          maxItems: 20
          items:
            type: object
            additionalProperties: false
            required: [claim, source]
            properties:
              claim:
                type: string
                minLength: 1
                maxLength: 600
              source:
                type: string
                minLength: 1
                maxLength: 160
        human_contact:
          $ref: "#/components/schemas/TelephoneHumanContact"
        opening_line:
          type: string
          maxLength: 600
        disclosure:
          type: string
          minLength: 1
          maxLength: 350
          description: Doit identifier l'agent comme une IA.
        published:
          type: boolean
          description: Indique qu'une version publiée existe; le profil retourné est le brouillon courant.
        updated_at:
          type: [string, "null"]
          format: date-time
        profile_version_id:
          type: [string, "null"]
          format: uuid
        profile_revision:
          type: [integer, "null"]
          minimum: 1
        profile_content_sha256:
          type: [string, "null"]
          pattern: "^[a-f0-9]{64}$"
        published_profile_version_id:
          type: [string, "null"]
          format: uuid
        published_profile_revision:
          type: [integer, "null"]
          minimum: 1
        published_content_sha256:
          type: [string, "null"]
          pattern: "^[a-f0-9]{64}$"
    TelephoneAgentResponse:
      type: object
      additionalProperties: false
      required: [agent]
      properties:
        agent:
          $ref: "#/components/schemas/TelephoneAgent"
    TelephoneAgentVersionMutation:
      type: object
      additionalProperties: false
      required: [revision, content_sha256]
      properties:
        revision:
          type: integer
          minimum: 1
        content_sha256:
          type: string
          pattern: "^[a-f0-9]{64}$"
    TelephoneAgentVersionsResponse:
      type: object
      additionalProperties: false
      required: [versions]
      properties:
        versions:
          type: array
          maxItems: 100
          items:
            type: object
            additionalProperties: false
            required:
              [profile_version_id, revision, content_sha256, created_by_user_id, created_at,
               is_draft, is_published]
            properties:
              profile_version_id:
                type: string
                format: uuid
              revision:
                type: integer
                minimum: 1
              content_sha256:
                type: string
                pattern: "^[a-f0-9]{64}$"
              created_by_user_id:
                type: string
              created_at:
                type: string
                format: date-time
              is_draft:
                type: boolean
              is_published:
                type: boolean
    TelephoneHumanContact:
      type: object
      additionalProperties: false
      description: Coordonnées fournies par l'entreprise. Tout profil renseigné exige un nom, un téléphone E.164 et un courriel ou une adresse postale. Le transfert exige aussi l'activation de la fonction côté serveur et une demande explicite du correspondant.
      properties:
        name:
          type: string
          maxLength: 120
        phone:
          type: string
          maxLength: 32
          pattern: '^(?:|\+[1-9][0-9]{7,14})$'
        email:
          type: string
          maxLength: 254
          pattern: '^(?:|[^@\s]{1,64}@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+)$'
        postal_address:
          type: string
          maxLength: 350
        handoff_enabled:
          type: boolean
          default: false
          description: Autorise l'agent à tenter le transfert vers ce numéro seulement si le correspondant demande explicitement à parler à une personne. Un transfert accepté par le fournisseur ne confirme pas que le représentant a répondu.
      anyOf:
        - properties:
            name:
              const: ""
            phone:
              const: ""
            email:
              const: ""
            postal_address:
              const: ""
            handoff_enabled:
              const: false
        - required: [name, phone]
          properties:
            name:
              minLength: 1
            phone:
              pattern: '^\+[1-9][0-9]{7,14}$'
          anyOf:
            - required: [email]
              properties:
                email:
                  minLength: 1
            - required: [postal_address]
              properties:
                postal_address:
                  minLength: 1
    TelephoneSipTrunkInput:
      type: object
      additionalProperties: false
      required: [provider_url, username, password, caller_number]
      properties:
        provider_url:
          type: string
          pattern: "^sips:"
          maxLength: 320
          description: Adresse du trunk SIP sécurisée du fournisseur.
        username:
          type: string
          minLength: 1
          maxLength: 256
        password:
          type: string
          writeOnly: true
          minLength: 1
          maxLength: 4096
          description: Secret SIP; chiffré côté serveur et jamais retourné.
        caller_number:
          type: string
          pattern: "^\\+[1-9][0-9]{1,14}$"
          description: Numéro E.164 fourni ou autorisé par l'entreprise.
    TelephoneSipTrunk:
      type: object
      required: [id, organization_id, provider_url, caller_number, status, credentials_configured, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        organization_id:
          type: string
        provider_url:
          type: string
        caller_number:
          type: string
        status:
          type: string
          enum: [active, revoked]
        credentials_configured:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    TelephoneInboundRouteInput:
      type: object
      additionalProperties: false
      required: [label]
      properties:
        label:
          type: string
          minLength: 1
          maxLength: 80
    TelephoneInboundRoute:
      type: object
      additionalProperties: false
      required: [id, organization_id, label, status, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        organization_id:
          type: string
        label:
          type: string
          maxLength: 80
        status:
          type: string
          enum: [active, revoked]
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    TestCallInput:
      type: object
      additionalProperties: false
      required: [direction]
      properties:
        direction:
          type: string
          enum: [inbound, outbound]
        scenario:
          type: string
          maxLength: 1000
          description: Scénario synthétique; son texte brut n'est pas conservé.
        idempotency_key:
          type: string
          maxLength: 64
          pattern: "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
          description: Alternative à l'en-tête Idempotency-Key.
    TelephoneEvent:
      type: object
      additionalProperties: false
      description: Événement métier réduit; exclut numéros, transcriptions, secrets et réponses CRM brutes.
      required: [id, type, created_at, data]
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          enum:
            - telephone.call.created
            - telephone.call.status_changed
            - telephone.call.usage_settled
            - telephone.integration.execution.completed
        created_at:
          type: string
          format: date-time
        data:
          oneOf:
            - $ref: "#/components/schemas/TelephoneCallCreatedEventData"
            - $ref: "#/components/schemas/TelephoneCallStatusChangedEventData"
            - $ref: "#/components/schemas/TelephoneCallUsageSettledEventData"
            - $ref: "#/components/schemas/TelephoneIntegrationExecutionEventData"
    TelephoneCallCreatedEventData:
      type: object
      additionalProperties: false
      required: [call_id, direction, environment, status]
      properties:
        call_id:
          type: string
          maxLength: 160
        direction:
          type: string
          enum: [inbound, outbound]
        environment:
          type: string
          enum: [test, live]
        status:
          type: string
          enum: [queued, simulated]
    TelephoneCallStatusChangedEventData:
      type: object
      additionalProperties: false
      description: État d'appel modifié; transfer_status est présent lorsqu'un transfert vers un représentant change.
      required: [call_id, direction, environment, status, billing_status]
      properties:
        call_id:
          type: string
          maxLength: 160
        direction:
          type: string
          enum: [inbound, outbound]
        environment:
          type: string
          enum: [test, live]
        status:
          type: string
          enum: [queued, active, completed, failed, unknown]
        billing_status:
          type: string
          pattern: "^[a-z][a-z0-9_]{1,63}$"
        transfer_status:
          type: string
          enum: [not_requested, pending, unknown, connected, failed]
    TelephoneCallUsageSettledEventData:
      type: object
      additionalProperties: false
      required: [call_id, environment, customer_charge_cents, billing_currency]
      properties:
        call_id:
          type: string
          maxLength: 160
        environment:
          type: string
          enum: [test, live]
        customer_charge_cents:
          type: integer
          minimum: 0
        billing_currency:
          type: string
          const: CAD
    TelephoneIntegrationExecutionEventData:
      type: object
      additionalProperties: false
      required: [integration_id, execution_id, operation_id, environment, status]
      properties:
        integration_id:
          type: string
          maxLength: 160
        execution_id:
          type: string
          maxLength: 160
        operation_id:
          type: string
          maxLength: 160
        environment:
          type: string
          enum: [test, live]
        status:
          type: string
          enum: [succeeded, failed, unknown, simulated]
        status_code:
          type: [integer, "null"]
          minimum: 100
          maximum: 599
        error_code:
          type: [string, "null"]
          pattern: "^[a-z][a-z0-9_]{1,63}$"
    TelephoneCall:
      type: object
      required: [id, environment, direction, status, transfer_status, scenario, billing_status, customer_price_usd, customer_charge_cents, billing_currency, usage, created_at, updated_at, simulated]
      properties:
        id:
          type: string
          format: uuid
        environment:
          type: string
          enum: [test, live]
        direction:
          type: string
          enum: [inbound, outbound]
        status:
          type: string
        transfer_status:
          type: string
          enum: [not_requested, pending, unknown, connected, failed]
        scenario:
          type: string
          description: Vide pour les demandes de test; le scénario brut n'est pas conservé.
        billing_status:
          type: string
        customer_price_usd:
          type: [number, "null"]
        customer_charge_cents:
          type: [integer, "null"]
          description: Part de cet appel dans le cumul facturé, arrondi au cent CAD inférieur; peut valoir 0.
        billing_currency:
          type: [string, "null"]
          enum: [CAD, null]
        usage:
          type: [object, "null"]
          description: Mesures vocales et tokens vérifiés; null si le reçu fournisseur est incomplet ou absent.
          required: [voice_seconds, reasoning]
          additionalProperties: false
          properties:
            voice_seconds:
              type: number
              minimum: 0
              description: Durée vocale finale mesurée à la seconde.
            reasoning:
              type: object
              required: [model, response_count, input_tokens, cached_input_tokens, cache_write_tokens, output_tokens]
              additionalProperties: false
              properties:
                model:
                  type: string
                  const: gpt-6-luna
                response_count:
                  type: integer
                  minimum: 0
                input_tokens:
                  type: integer
                  minimum: 0
                  description: Total des tokens d’entrée, incluant les tokens mis en cache et en écriture de cache.
                cached_input_tokens:
                  type: integer
                  minimum: 0
                cache_write_tokens:
                  type: integer
                  minimum: 0
                output_tokens:
                  type: integer
                  minimum: 0
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        simulated:
          type: boolean
    CallWriteResponse:
      type: object
      required: [call, replayed]
      properties:
        call:
          $ref: "#/components/schemas/TelephoneCall"
        replayed:
          type: boolean
    TelephoneIntegrationInput:
      type: object
      additionalProperties: false
      required: [name, base_url, operations]
      properties:
        name:
          type: string
          maxLength: 80
        base_url:
          type: string
          format: uri
          description: Origine HTTPS sur le port 443; URL privée ou locale refusée.
        auth_mode:
          type: string
          enum: [bearer, api_key]
          default: bearer
        api_key_header:
          type: string
          pattern: "^X-[A-Za-z0-9-]{1,60}$"
          default: X-API-Key
        credential:
          type: string
          writeOnly: true
          description: Secret à omettre en Test; chiffré côté serveur en Production.
        operations:
          type: array
          minItems: 1
          maxItems: 24
          items:
            $ref: "#/components/schemas/TelephoneIntegrationOperation"
    TelephoneIntegrationOperation:
      type: object
      additionalProperties: false
      required: [operation_id, endpoint, method, input_schema, output_schema, required_scopes]
      properties:
        operation_id:
          type: string
          pattern: "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
        endpoint:
          type: string
          pattern: "^/"
          description: Chemin relatif fixe, sans URL absolue.
        method:
          type: string
          enum: [GET, POST, PUT, PATCH, DELETE]
        input_schema:
          type: object
          description: Schéma JSON borné de l'entrée de l'opération.
        output_schema:
          type: object
          description: Schéma JSON borné de la réponse attendue.
        required_scopes:
          type: array
          minItems: 1
          items:
            type: string
            enum: [telephone:contacts:read, telephone:contacts:write, telephone:appointments:read, telephone:appointments:write, telephone:orders:read, telephone:orders:write]
        voice_enabled:
          type: boolean
          default: false
          description: >-
            Expose cette opération à l'agent vocal uniquement pour un connecteur Production,
            si method est GET et si toutes les permissions sont en lecture seule.
        query_parameters:
          type: array
          items:
            type: string
          description: Paramètres autorisés pour une opération GET.
    TelephoneIntegration:
      type: object
      required: [id, environment, name, status, credential_configured, operation_ids, voice_enabled_operation_ids, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        environment:
          type: string
          enum: [test, live]
        name:
          type: string
        status:
          type: string
          enum: [active, revoked]
        credential_configured:
          type: boolean
        operation_ids:
          type: array
          items:
            type: string
        voice_enabled_operation_ids:
          type: array
          description: Opérations GET en lecture seule activées pour les consultations vocales.
          items:
            type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    IntegrationExecutionResult:
      type: object
      required: [outcome, status_code, error_code, data, replayed, execution_id]
      properties:
        outcome:
          type: string
          enum: [simulated, succeeded]
        status_code:
          type: [integer, "null"]
        error_code:
          type: [string, "null"]
        data:
          description: Résultat simulé ou réponse validée selon output_schema.
        replayed:
          type: boolean
        execution_id:
          type: string
          format: uuid
    IntegrationExecutionStatus:
      type: object
      required: [id, environment, operation_id, status, status_code, error_code, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        environment:
          type: string
          enum: [test, live]
        operation_id:
          type: string
        status:
          type: string
          enum: [simulated, succeeded, failed, unknown]
        status_code:
          type: [integer, "null"]
        error_code:
          type: [string, "null"]
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    TelephoneWebhookInput:
      type: object
      additionalProperties: false
      required: [target_url, events]
      properties:
        target_url:
          type: string
          format: uri
          maxLength: 2048
          pattern: "^https://"
          description: Destination HTTPS sur le port 443; aucun identifiant, paramètre de requête ou fragment.
        events:
          type: array
          minItems: 1
          maxItems: 8
          uniqueItems: true
          items:
            type: string
            enum:
              - telephone.call.created
              - telephone.call.status_changed
              - telephone.call.usage_settled
              - telephone.integration.execution.completed
    TelephoneWebhookEndpoint:
      type: object
      required: [id, organization_id, environment, target_url, events, status, created_at, updated_at, revoked_at]
      properties:
        id:
          type: string
          format: uuid
        organization_id:
          type: string
        environment:
          type: string
          enum: [test, live]
        target_url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
            enum:
              - telephone.call.created
              - telephone.call.status_changed
              - telephone.call.usage_settled
              - telephone.integration.execution.completed
        status:
          type: string
          enum: [active, revoked]
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        revoked_at:
          type: [string, "null"]
          format: date-time
    TelephoneWebhookCreated:
      allOf:
        - $ref: "#/components/schemas/TelephoneWebhookEndpoint"
        - type: object
          required: [secret]
          properties:
            secret:
              type: string
              writeOnly: true
              pattern: "^rmkwhsec_"
              description: Secret retourné uniquement lors de la création; conserver dans le coffre de secrets de l'entreprise.
    TelephoneWebhookDelivery:
      type: object
      required: [id, endpoint_id, event_id, event_type, status, attempt_count, last_http_status, last_error_code, created_at, updated_at, delivered_at]
      properties:
        id:
          type: string
          format: uuid
        endpoint_id:
          type: string
          format: uuid
        event_id:
          type: string
          format: uuid
        event_type:
          type: string
          enum:
            - telephone.call.created
            - telephone.call.status_changed
            - telephone.call.usage_settled
            - telephone.integration.execution.completed
        status:
          type: string
          enum: [queued, leased, succeeded, failed, cancelled]
        attempt_count:
          type: integer
          minimum: 0
        last_http_status:
          type: [integer, "null"]
        last_error_code:
          type: [string, "null"]
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        delivered_at:
          type: [string, "null"]
          format: date-time
    ApiError:
      type: object
      required: [detail]
      properties:
        detail:
          type: object
          required: [code]
          properties:
            code:
              type: string
            execution_id:
              type: string
              format: uuid
  responses:
    Unauthorized:
      description: Clé absente, invalide, expirée ou révoquée.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
    ScopeRequired:
      description: La clé ne possède pas toutes les permissions requises.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
    NotFound:
      description: Ressource absente ou hors de l'espace associé à la clé.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
    Conflict:
      description: Conflit d'idempotence, état incertain ou règle métier non satisfaite.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
    InvalidRequest:
      description: Corps, paramètres ou configuration refusés.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
    RateLimited:
      description: Limite atteinte; respecter l'en-tête Retry-After.
      headers:
        Retry-After:
          schema:
            type: integer
          description: Secondes à attendre avant une nouvelle tentative.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
    FeatureDisabled:
      description: Fonction désactivée ou prérequis serveur indisponible.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
    PaymentRequired:
      description: Solde disponible insuffisant pour la réserve ou le règlement.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
