{
  "components": {
    "schemas": {
      "ConversationIn": {
        "properties": {
          "avatar_id": {
            "maxLength": 64,
            "type": [
              "string",
              "null"
            ],
            "description": "Avatar du déploiement (facultatif : celui du déploiement par défaut).",
            "title": "Avatar Id"
          },
          "lang": {
            "maxLength": 8,
            "type": [
              "string",
              "null"
            ],
            "description": "Langue souhaitée, parmi celles du déploiement.",
            "title": "Lang"
          },
          "resume": {
            "maxLength": 64,
            "type": [
              "string",
              "null"
            ],
            "description": "Identifiant d'une conversation à reprendre (moins de 30 min d'inactivité).",
            "title": "Resume"
          }
        },
        "title": "ConversationIn",
        "type": "object"
      },
      "MessageIn": {
        "properties": {
          "lang": {
            "maxLength": 8,
            "type": [
              "string",
              "null"
            ],
            "description": "Accepté, sans effet en v1 : l'avatar répond dans la langue de la question, détectée à chaque message.",
            "title": "Lang"
          },
          "message": {
            "description": "Question du visiteur (tapée ou transcrite).",
            "maxLength": 1000,
            "minLength": 1,
            "title": "Message",
            "type": "string"
          },
          "mode": {
            "default": "text",
            "description": "« voice » : réponse rédigée pour être dite à voix haute.",
            "enum": [
              "text",
              "voice"
            ],
            "title": "Mode",
            "type": "string"
          },
          "page": {
            "maxLength": 500,
            "type": [
              "string",
              "null"
            ],
            "description": "Page consultée par le visiteur (accueil et réponses selon la page).",
            "title": "Page"
          }
        },
        "required": [
          "message"
        ],
        "title": "MessageIn",
        "type": "object"
      },
      "SpeechIn": {
        "properties": {
          "lang": {
            "maxLength": 8,
            "type": [
              "string",
              "null"
            ],
            "description": "Langue du texte : choisit la voix déclarée par le client pour cette langue, si l'avatar du déploiement est celui du client ; sinon, la voix de l'avatar.",
            "title": "Lang"
          },
          "text": {
            "description": "Texte à dire (2 000 caractères au plus ; liens et Markdown retirés).",
            "maxLength": 2000,
            "minLength": 1,
            "title": "Text",
            "type": "string"
          },
          "turn": {
            "minimum": 0.0,
            "type": [
              "integer",
              "null"
            ],
            "description": "Numéro du tour (champ `turn` de l'événement `done` de /messages) : tous les morceaux d'une même réponse portent le même numéro et ne comptent qu'une fois. Absent : le dernier tour de la conversation (0 avant la première question, ex. l'accueil). Un tour reçoit au plus 1 500 caractères de voix, tous morceaux compris.",
            "title": "Turn"
          }
        },
        "required": [
          "text"
        ],
        "title": "SpeechIn",
        "type": "object"
      },
      "Erreur": {
        "title": "Erreur",
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "request_id"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Code stable, à utiliser dans votre code (le message peut changer).",
                "examples": [
                  "origin_not_allowed"
                ]
              },
              "message": {
                "type": "string",
                "description": "Explication lisible, en français."
              },
              "request_id": {
                "type": "string",
                "description": "Aussi dans l'en-tête `X-Request-Id`. À donner au support (contact@twimi.ai)."
              }
            }
          }
        }
      },
      "QuestionOrale": {
        "properties": {
          "audio": {
            "contentMediaType": "application/octet-stream",
            "description": "Son de la question : webm, ogg, mp4, m4a, wav ou mp3 ; 5 Mo au plus",
            "title": "Audio",
            "type": "string",
            "format": "binary"
          },
          "lang": {
            "type": [
              "string",
              "null"
            ],
            "description": "Langue parlée (indice pour la transcription)",
            "title": "Lang"
          }
        },
        "required": [
          "audio"
        ],
        "title": "QuestionOrale",
        "type": "object"
      }
    },
    "securitySchemes": {
      "clePublique": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "pk_test_… / pk_live_…",
        "description": "Clé publique du déploiement, visible dans la page web. Acceptée seulement depuis les pages déclarées : le nom d'hôte de l'en-tête `Origin` doit être exactement l'un des noms du déploiement, ou correspondre à un joker déclaré (`*.concession.fr`)."
      },
      "cleSecrete": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "sk_test_… / sk_live_…",
        "description": "Clé secrète, **jamais dans un navigateur** : sur votre serveur, une application ou une borne."
      },
      "jetonConversation": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Jeton de conversation reçu de `POST /v1/conversations`, valable 30 minutes (à renouveler avec `/token`). Sert aux questions, à la voix et à la transcription."
      }
    },
    "headers": {
      "X-Request-Id": {
        "description": "Identifiant de la requête, à donner au support.",
        "schema": {
          "type": "string",
          "examples": [
            "req_7c1e…"
          ]
        }
      },
      "Retry-After": {
        "description": "Secondes à attendre avant de réessayer.",
        "schema": {
          "type": "integer",
          "examples": [
            12
          ]
        }
      },
      "X-Twimi-Voice-Reason": {
        "description": "Pourquoi la voix est refusée : `forfait`, `conversation`, `fusible` ou `reponse`.",
        "schema": {
          "type": "string",
          "enum": [
            "forfait",
            "conversation",
            "fusible",
            "reponse"
          ]
        }
      }
    }
  },
  "info": {
    "description": "API des partenaires (éditeurs, agences, intégrateurs) et des clients directs qui intègrent un avatar Twimi dans leur propre interface ou application.",
    "title": "API Twimi v1",
    "version": "1.0.0-preview.12",
    "contact": {
      "name": "Support Twimi",
      "email": "contact@twimi.ai"
    }
  },
  "openapi": "3.1.0",
  "paths": {
    "/v1/health": {
      "get": {
        "operationId": "health_v1_health_get",
        "responses": {
          "200": {
            "description": "Service en marche",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "status": "ok",
                  "api": "1",
                  "version": "1.0.0-preview.12"
                }
              }
            }
          }
        },
        "summary": "État du service",
        "tags": [
          "service"
        ],
        "security": [],
        "description": "Sans clé. `api` est la version de l'adresse (`/v1`), `version` celle de la documentation."
      }
    },
    "/v1/me": {
      "get": {
        "description": "Sert à vérifier une clé. Clé publique : depuis une page déclarée. Clé secrète : côté serveur.",
        "operationId": "me_v1_me_get",
        "responses": {
          "200": {
            "description": "Le déploiement de la clé ; `partner` vaut `null` pour un client direct de Twimi.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "deployment": {
                    "id": "dep_…",
                    "client": "concession",
                    "avatar": "arno-pinson",
                    "formula": "agent",
                    "env": "test",
                    "status": "active",
                    "langs": [
                      "fr"
                    ]
                  },
                  "partner": {
                    "id": "par_…",
                    "name": "Agence Exemple"
                  },
                  "key": {
                    "kind": "sk"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentification refusée : `invalid_key`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "invalid_key": {
                    "summary": "invalid_key",
                    "description": "Clé absente, inconnue, révoquée, ou d'un autre environnement.",
                    "value": {
                      "error": {
                        "code": "invalid_key",
                        "message": "Clé absente, inconnue, révoquée, ou d'un autre environnement.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Accès refusé : `origin_not_allowed`, `deployment_suspended`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "origin_not_allowed": {
                    "summary": "origin_not_allowed",
                    "description": "Page non déclarée pour cette clé publique.",
                    "value": {
                      "error": {
                        "code": "origin_not_allowed",
                        "message": "Page non déclarée pour cette clé publique.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  },
                  "deployment_suspended": {
                    "summary": "deployment_suspended",
                    "description": "Déploiement suspendu.",
                    "value": {
                      "error": {
                        "code": "deployment_suspended",
                        "message": "Déploiement suspendu.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "summary": "Le déploiement auquel la clé donne accès",
        "tags": [
          "service"
        ],
        "security": [
          {
            "cleSecrete": []
          },
          {
            "clePublique": []
          }
        ]
      }
    },
    "/v1/usage": {
      "get": {
        "description": "Clé secrète seulement. `opened` : conversations ouvertes ; `counted` : conversations décomptées\ndu forfait (une reprise sous 30 min n'est pas décomptée).",
        "operationId": "usage_v1_usage_get",
        "parameters": [
          {
            "description": "Mois « AAAA-MM » (défaut : le mois en cours)",
            "in": "query",
            "name": "period",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "description": "Mois « AAAA-MM » (défaut : le mois en cours)",
              "title": "Period"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Consommation du mois. `counted` ≥ `opened` : chaque tranche de 5 réponses parlées compte en plus.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "period": "2026-10",
                  "deployment": {
                    "id": "dep_…",
                    "client": "concession",
                    "formula": "agent"
                  },
                  "conversations": {
                    "opened": 1102,
                    "counted": 1288
                  },
                  "limit": 2000
                }
              }
            }
          },
          "400": {
            "description": "Requête invalide : `invalid_request`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "invalid_request": {
                    "summary": "invalid_request",
                    "description": "Requête mal formée ; le message nomme les champs.",
                    "value": {
                      "error": {
                        "code": "invalid_request",
                        "message": "Requête mal formée ; le message nomme les champs.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentification refusée : `invalid_key`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "invalid_key": {
                    "summary": "invalid_key",
                    "description": "Clé absente, inconnue, révoquée, ou d'un autre environnement.",
                    "value": {
                      "error": {
                        "code": "invalid_key",
                        "message": "Clé absente, inconnue, révoquée, ou d'un autre environnement.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Accès refusé : `secret_key_required`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "secret_key_required": {
                    "summary": "secret_key_required",
                    "description": "Route réservée à la clé secrète.",
                    "value": {
                      "error": {
                        "code": "secret_key_required",
                        "message": "Route réservée à la clé secrète.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "summary": "Consommation du mois (clé secrète)",
        "tags": [
          "service"
        ],
        "security": [
          {
            "cleSecrete": []
          }
        ]
      }
    },
    "/v1/assets/{avatar_id}/{rel_path}": {
      "get": {
        "description": "Adresse fournie par la fiche de l'avatar ; ne se fabrique pas. Une fois l'adresse publique en\nplace, ces fichiers sont servis directement par le serveur web, avec le même contrôle.",
        "operationId": "asset_v1_assets__avatar_id___rel_path__get",
        "parameters": [
          {
            "in": "path",
            "name": "avatar_id",
            "required": true,
            "schema": {
              "title": "Avatar Id",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "rel_path",
            "required": true,
            "schema": {
              "title": "Rel Path",
              "type": "string"
            }
          },
          {
            "description": "Expiration (secondes depuis 1970)",
            "in": "query",
            "name": "e",
            "required": false,
            "schema": {
              "default": "",
              "description": "Expiration (secondes depuis 1970)",
              "title": "E",
              "type": "string"
            }
          },
          {
            "description": "Signature",
            "in": "query",
            "name": "s",
            "required": false,
            "schema": {
              "default": "",
              "description": "Signature",
              "title": "S",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Le fichier demandé.",
            "content": {
              "application/octet-stream": {}
            }
          },
          "403": {
            "description": "Accès refusé : `invalid_link`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "invalid_link": {
                    "summary": "invalid_link",
                    "description": "Lien de fichier modifié ou expiré : relire la fiche de l'avatar.",
                    "value": {
                      "error": {
                        "code": "invalid_link",
                        "message": "Lien de fichier modifié ou expiré : relire la fiche de l'avatar.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "summary": "Fichier du visage (lien signé)",
        "tags": [
          "avatars"
        ],
        "security": []
      }
    },
    "/v1/avatars/{avatar_id}": {
      "get": {
        "description": "Clé publique ou secrète. Pas de décompte (l'affichage n'est pas facturé). Les liens des fichiers\nsont signés et valables 1 h (`assets_expire_at`) : relire la fiche pour en obtenir de nouveaux.",
        "operationId": "avatar_card_v1_avatars__avatar_id__get",
        "parameters": [
          {
            "in": "path",
            "name": "avatar_id",
            "required": true,
            "schema": {
              "title": "Avatar Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Fiche de l'avatar. `voice.available` et `theme` dépendent de la formule.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "arno-pinson",
                  "name": "Arno",
                  "langs": [
                    "fr"
                  ],
                  "default_lang": "fr",
                  "voice": {
                    "available": true
                  },
                  "theme": {
                    "primary_color": "#…",
                    "bubble_color": "#…"
                  },
                  "engine": {
                    "format": "lowerface-v1",
                    "ort_version": "1.20.1"
                  },
                  "assets": {
                    "video": "https://api-dev.atwimi.com/v1/assets/arno-pinson/speak_nod.mp4?e=…&s=…",
                    "track": "…",
                    "idle": "…",
                    "model": "…",
                    "meta": "…",
                    "neutral": "…",
                    "mask": "…",
                    "mfcc_stats": "…",
                    "thumb": "…"
                  },
                  "assets_expire_at": "2026-10-27T19:12:00Z"
                }
              }
            }
          },
          "401": {
            "description": "Authentification refusée : `invalid_key`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "invalid_key": {
                    "summary": "invalid_key",
                    "description": "Clé absente, inconnue, révoquée, ou d'un autre environnement.",
                    "value": {
                      "error": {
                        "code": "invalid_key",
                        "message": "Clé absente, inconnue, révoquée, ou d'un autre environnement.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Accès refusé : `origin_not_allowed`, `deployment_suspended`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "origin_not_allowed": {
                    "summary": "origin_not_allowed",
                    "description": "Page non déclarée pour cette clé publique.",
                    "value": {
                      "error": {
                        "code": "origin_not_allowed",
                        "message": "Page non déclarée pour cette clé publique.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  },
                  "deployment_suspended": {
                    "summary": "deployment_suspended",
                    "description": "Déploiement suspendu.",
                    "value": {
                      "error": {
                        "code": "deployment_suspended",
                        "message": "Déploiement suspendu.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Introuvable : `avatar_not_found`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "avatar_not_found": {
                    "summary": "avatar_not_found",
                    "description": "Avatar hors du déploiement.",
                    "value": {
                      "error": {
                        "code": "avatar_not_found",
                        "message": "Avatar hors du déploiement.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "summary": "Fiche de l'avatar et liens vers ses fichiers",
        "tags": [
          "avatars"
        ],
        "security": [
          {
            "clePublique": []
          },
          {
            "cleSecrete": []
          }
        ]
      }
    },
    "/v1/conversations": {
      "post": {
        "description": "Clé publique (depuis une page déclarée) ou secrète. Renvoie un jeton de conversation valable 30 min.\n\nAvec `resume` : si la conversation appartient au même déploiement, n'est pas terminée et a servi il y a\nmoins de 30 minutes, elle est reprise (réponse 200, `counted: false`). Sinon, une nouvelle est ouverte\n(réponse 201, `counted: true`). Au-delà du forfait du mois, la conversation s'ouvre quand même, en écrit\nseul (`voice.allowed: false`, `reason: \"forfait\"`) : jamais de blocage.",
        "operationId": "open_conversation_v1_conversations_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConversationIn"
              },
              "example": {
                "avatar_id": "arno-pinson",
                "lang": "fr"
              }
            }
          },
          "required": false
        },
        "responses": {
          "201": {
            "description": "Nouvelle conversation, décomptée du forfait.",
            "content": {
              "application/json": {
                "schema": {
                  "title": "Conversation",
                  "type": "object",
                  "properties": {
                    "conversation_id": {
                      "type": "string"
                    },
                    "token": {
                      "type": "string",
                      "description": "Jeton de conversation, valable 30 minutes."
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "counted": {
                      "type": "boolean",
                      "description": "`true` : nouvelle conversation, décomptée du forfait."
                    },
                    "quota": {
                      "type": "object",
                      "properties": {
                        "used": {
                          "type": "integer"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "period": {
                          "type": "string",
                          "examples": [
                            "2026-10"
                          ]
                        }
                      }
                    },
                    "voice": {
                      "type": "object",
                      "description": "Disponibilité de la voix pour cette conversation.",
                      "properties": {
                        "allowed": {
                          "type": "boolean"
                        },
                        "reason": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "forfait",
                            "conversation",
                            "fusible",
                            "formule",
                            "reponse",
                            null
                          ],
                          "description": "`forfait` : forfait du mois dépassé ; `conversation` : plafond de la conversation ; `fusible` : trafic anormal ; `formule` : Avatar SDK, la voix est la vôtre ; `reponse` : la réponse dépasserait 1 500 caractères de voix."
                        },
                        "units": {
                          "type": "integer",
                          "description": "Conversations décomptées pour cette conversation."
                        },
                        "spoken": {
                          "type": "integer",
                          "description": "Réponses parlées jusqu'ici."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "conversation_id": "conv_9xk2…",
                  "token": "eyJ…",
                  "expires_at": "2026-10-27T18:42:00Z",
                  "counted": true,
                  "quota": {
                    "used": 412,
                    "limit": 500,
                    "period": "2026-10"
                  },
                  "voice": {
                    "allowed": true,
                    "reason": null,
                    "units": 1,
                    "spoken": 0
                  }
                }
              }
            }
          },
          "200": {
            "description": "Conversation reprise avec `resume` : pas de nouveau décompte.",
            "content": {
              "application/json": {
                "schema": {
                  "title": "Conversation",
                  "type": "object",
                  "properties": {
                    "conversation_id": {
                      "type": "string"
                    },
                    "token": {
                      "type": "string",
                      "description": "Jeton de conversation, valable 30 minutes."
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "counted": {
                      "type": "boolean",
                      "description": "`true` : nouvelle conversation, décomptée du forfait."
                    },
                    "quota": {
                      "type": "object",
                      "properties": {
                        "used": {
                          "type": "integer"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "period": {
                          "type": "string",
                          "examples": [
                            "2026-10"
                          ]
                        }
                      }
                    },
                    "voice": {
                      "type": "object",
                      "description": "Disponibilité de la voix pour cette conversation.",
                      "properties": {
                        "allowed": {
                          "type": "boolean"
                        },
                        "reason": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "forfait",
                            "conversation",
                            "fusible",
                            "formule",
                            "reponse",
                            null
                          ],
                          "description": "`forfait` : forfait du mois dépassé ; `conversation` : plafond de la conversation ; `fusible` : trafic anormal ; `formule` : Avatar SDK, la voix est la vôtre ; `reponse` : la réponse dépasserait 1 500 caractères de voix."
                        },
                        "units": {
                          "type": "integer",
                          "description": "Conversations décomptées pour cette conversation."
                        },
                        "spoken": {
                          "type": "integer",
                          "description": "Réponses parlées jusqu'ici."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "conversation_id": "conv_9xk2…",
                  "token": "eyJ…",
                  "expires_at": "2026-10-27T18:42:00Z",
                  "counted": false,
                  "quota": {
                    "used": 412,
                    "limit": 500,
                    "period": "2026-10"
                  },
                  "voice": {
                    "allowed": true,
                    "reason": null,
                    "units": 1,
                    "spoken": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Requête invalide : `invalid_request`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "invalid_request": {
                    "summary": "invalid_request",
                    "description": "Requête mal formée ; le message nomme les champs.",
                    "value": {
                      "error": {
                        "code": "invalid_request",
                        "message": "Requête mal formée ; le message nomme les champs.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentification refusée : `invalid_key`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "invalid_key": {
                    "summary": "invalid_key",
                    "description": "Clé absente, inconnue, révoquée, ou d'un autre environnement.",
                    "value": {
                      "error": {
                        "code": "invalid_key",
                        "message": "Clé absente, inconnue, révoquée, ou d'un autre environnement.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Accès refusé : `origin_not_allowed`, `deployment_suspended`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "origin_not_allowed": {
                    "summary": "origin_not_allowed",
                    "description": "Page non déclarée pour cette clé publique.",
                    "value": {
                      "error": {
                        "code": "origin_not_allowed",
                        "message": "Page non déclarée pour cette clé publique.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  },
                  "deployment_suspended": {
                    "summary": "deployment_suspended",
                    "description": "Déploiement suspendu.",
                    "value": {
                      "error": {
                        "code": "deployment_suspended",
                        "message": "Déploiement suspendu.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Introuvable : `avatar_not_found`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "avatar_not_found": {
                    "summary": "avatar_not_found",
                    "description": "Avatar hors du déploiement.",
                    "value": {
                      "error": {
                        "code": "avatar_not_found",
                        "message": "Avatar hors du déploiement.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Trop de requêtes : `rate_limited`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "rate_limited",
                    "description": "Trop de requêtes ; attendre la durée de l'en-tête `Retry-After`.",
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes ; attendre la durée de l'en-tête `Retry-After`.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "summary": "Ouvrir ou reprendre une conversation",
        "tags": [
          "conversations"
        ],
        "security": [
          {
            "clePublique": []
          },
          {
            "cleSecrete": []
          }
        ]
      }
    },
    "/v1/conversations/{conversation_id}/end": {
      "post": {
        "description": "Facultatif. La conversation ne peut plus être reprise et son jeton n'est plus accepté.",
        "operationId": "end_conversation_v1_conversations__conversation_id__end_post",
        "parameters": [
          {
            "in": "path",
            "name": "conversation_id",
            "required": true,
            "schema": {
              "title": "Conversation Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Conversation terminée.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "conversation_id": "conv_9xk2…",
                  "ended": true
                }
              }
            }
          },
          "401": {
            "description": "Authentification refusée : `token_expired`, `invalid_token`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "token_expired": {
                    "summary": "token_expired",
                    "description": "Jeton de conversation périmé : rouvrir avec `resume`.",
                    "value": {
                      "error": {
                        "code": "token_expired",
                        "message": "Jeton de conversation périmé : rouvrir avec `resume`.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  },
                  "invalid_token": {
                    "summary": "invalid_token",
                    "description": "Jeton de conversation invalide.",
                    "value": {
                      "error": {
                        "code": "invalid_token",
                        "message": "Jeton de conversation invalide.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Introuvable : `conversation_not_found`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "conversation_not_found": {
                    "summary": "conversation_not_found",
                    "description": "Conversation inconnue ou terminée.",
                    "value": {
                      "error": {
                        "code": "conversation_not_found",
                        "message": "Conversation inconnue ou terminée.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "summary": "Terminer une conversation",
        "tags": [
          "conversations"
        ],
        "security": [
          {
            "jetonConversation": []
          }
        ]
      }
    },
    "/v1/conversations/{conversation_id}/token": {
      "post": {
        "description": "Avec le jeton encore valide (`Authorization: Bearer <jeton>`). Pas de nouveau décompte.\nRefusé si la clé qui a ouvert la conversation a été révoquée (`401 invalid_key`), ou au-delà de 10 min\nde conversation (`410 conversation_expired` : en ouvrir une nouvelle).",
        "operationId": "renew_token_v1_conversations__conversation_id__token_post",
        "parameters": [
          {
            "in": "path",
            "name": "conversation_id",
            "required": true,
            "schema": {
              "title": "Conversation Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Nouveau jeton de 30 minutes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "conversation_id": "conv_9xk2…",
                  "token": "eyJ…",
                  "expires_at": "2026-10-27T19:12:00Z"
                }
              }
            }
          },
          "401": {
            "description": "Authentification refusée : `token_expired`, `invalid_token`, `invalid_key`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "token_expired": {
                    "summary": "token_expired",
                    "description": "Jeton de conversation périmé : rouvrir avec `resume`.",
                    "value": {
                      "error": {
                        "code": "token_expired",
                        "message": "Jeton de conversation périmé : rouvrir avec `resume`.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  },
                  "invalid_token": {
                    "summary": "invalid_token",
                    "description": "Jeton de conversation invalide.",
                    "value": {
                      "error": {
                        "code": "invalid_token",
                        "message": "Jeton de conversation invalide.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  },
                  "invalid_key": {
                    "summary": "invalid_key",
                    "description": "Clé absente, inconnue, révoquée, ou d'un autre environnement.",
                    "value": {
                      "error": {
                        "code": "invalid_key",
                        "message": "Clé absente, inconnue, révoquée, ou d'un autre environnement.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Accès refusé : `origin_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "origin_not_allowed": {
                    "summary": "origin_not_allowed",
                    "description": "Page non déclarée pour cette clé publique.",
                    "value": {
                      "error": {
                        "code": "origin_not_allowed",
                        "message": "Page non déclarée pour cette clé publique.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Introuvable : `conversation_not_found`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "conversation_not_found": {
                    "summary": "conversation_not_found",
                    "description": "Conversation inconnue ou terminée.",
                    "value": {
                      "error": {
                        "code": "conversation_not_found",
                        "message": "Conversation inconnue ou terminée.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "410": {
            "description": "Conversation expirée : `conversation_expired`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "conversation_expired": {
                    "summary": "conversation_expired",
                    "description": "Conversation de plus de 10 minutes : en ouvrir une nouvelle.",
                    "value": {
                      "error": {
                        "code": "conversation_expired",
                        "message": "Conversation de plus de 10 minutes : en ouvrir une nouvelle.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "summary": "Renouveler le jeton d'une conversation",
        "tags": [
          "conversations"
        ],
        "security": [
          {
            "jetonConversation": []
          }
        ]
      }
    },
    "/v1/conversations/{conversation_id}/messages": {
      "post": {
        "description": "Avec le jeton de conversation. Formules Avatar Widget et Avatar Agent. Réponse en flux\n(`text/event-stream`), une ligne `data: {...}` par événement :\n- `token` `{text}` : morceau de la réponse, à afficher au fil de l'eau ;\n- `done` `{text, products, turn, voice}` : réponse complète, articles cités, numéro du tour (à redonner\n  à /speech pour chaque morceau de cette réponse), voix disponible ou non pour la dire ;\n- `error` `{code, message}` : réponse impossible (`brain_unavailable`).\n20 questions par minute et par conversation.",
        "operationId": "messages_v1_conversations__conversation_id__messages_post",
        "parameters": [
          {
            "in": "path",
            "name": "conversation_id",
            "required": true,
            "schema": {
              "title": "Conversation Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageIn"
              },
              "example": {
                "message": "Vous avez des SUV ?",
                "page": "/catalogue",
                "lang": "fr",
                "mode": "voice"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Réponse en flux : une ligne `data: {...}` par événement (`token`, `done` ou `error`).",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                },
                "example": "data: {\"type\": \"token\", \"text\": \"Oui, \"}\n\ndata: {\"type\": \"token\", \"text\": \"nous avons trois SUV.\"}\n\ndata: {\"type\": \"done\", \"text\": \"Oui, nous avons trois SUV.\", \"products\": [], \"intent\": \"…\", \"turn\": 3, \"voice\": {\"allowed\": true, \"reason\": null, \"units\": 1, \"spoken\": 2}}\n\n"
              }
            }
          },
          "400": {
            "description": "Requête invalide : `invalid_request`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "invalid_request": {
                    "summary": "invalid_request",
                    "description": "Requête mal formée ; le message nomme les champs.",
                    "value": {
                      "error": {
                        "code": "invalid_request",
                        "message": "Requête mal formée ; le message nomme les champs.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentification refusée : `token_expired`, `invalid_token`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "token_expired": {
                    "summary": "token_expired",
                    "description": "Jeton de conversation périmé : rouvrir avec `resume`.",
                    "value": {
                      "error": {
                        "code": "token_expired",
                        "message": "Jeton de conversation périmé : rouvrir avec `resume`.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  },
                  "invalid_token": {
                    "summary": "invalid_token",
                    "description": "Jeton de conversation invalide.",
                    "value": {
                      "error": {
                        "code": "invalid_token",
                        "message": "Jeton de conversation invalide.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Accès refusé : `formula_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "formula_not_allowed": {
                    "summary": "formula_not_allowed",
                    "description": "Fonction non incluse dans la formule.",
                    "value": {
                      "error": {
                        "code": "formula_not_allowed",
                        "message": "Fonction non incluse dans la formule.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Introuvable : `conversation_not_found`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "conversation_not_found": {
                    "summary": "conversation_not_found",
                    "description": "Conversation inconnue ou terminée.",
                    "value": {
                      "error": {
                        "code": "conversation_not_found",
                        "message": "Conversation inconnue ou terminée.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "410": {
            "description": "Conversation expirée : `conversation_expired`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "conversation_expired": {
                    "summary": "conversation_expired",
                    "description": "Conversation de plus de 10 minutes : en ouvrir une nouvelle.",
                    "value": {
                      "error": {
                        "code": "conversation_expired",
                        "message": "Conversation de plus de 10 minutes : en ouvrir une nouvelle.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Trop de requêtes : `rate_limited`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "rate_limited",
                    "description": "Trop de requêtes ; attendre la durée de l'en-tête `Retry-After`.",
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes ; attendre la durée de l'en-tête `Retry-After`.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "summary": "Poser une question à l'avatar",
        "tags": [
          "réponses"
        ],
        "security": [
          {
            "jetonConversation": []
          }
        ]
      }
    },
    "/v1/conversations/{conversation_id}/speech": {
      "post": {
        "description": "Avec le jeton de conversation. Formules Avatar Widget et Avatar Agent. Renvoie le son en MP3 (binaire),\navec l'en-tête `X-Twimi-Chars` (caractères lus). Envoyez une réponse longue **par phrases** : la première\nse fait entendre pendant que la suivante se prépare, en donnant à chaque morceau le même `turn`.\nDécompte : chaque réponse parlée (un `turn`) compte une fois ; 5 réponses parlées = 1 conversation\n(réglable par déploiement). Un tour reçoit au plus 1 500 caractères de voix, tous morceaux compris.\nRefus `409 voice_limit_reached` (en-tête `X-Twimi-Voice-Reason`) : forfait du mois atteint pour une\nnouvelle tranche, plafond de la conversation, fusible, ou réponse parlée trop longue (`reponse`).\n60 morceaux par minute et par conversation.",
        "operationId": "speech_v1_conversations__conversation_id__speech_post",
        "parameters": [
          {
            "in": "path",
            "name": "conversation_id",
            "required": true,
            "schema": {
              "title": "Conversation Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SpeechIn"
              },
              "example": {
                "text": "Oui, nous avons trois SUV.",
                "lang": "fr",
                "turn": 3
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Le son en MP3.",
            "headers": {
              "X-Twimi-Chars": {
                "description": "Caractères lus.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Twimi-Turn": {
                "description": "Tour compté.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Twimi-Units": {
                "description": "Conversations décomptées pour cette conversation.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "audio/mpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Requête invalide : `invalid_request`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "invalid_request": {
                    "summary": "invalid_request",
                    "description": "Requête mal formée ; le message nomme les champs.",
                    "value": {
                      "error": {
                        "code": "invalid_request",
                        "message": "Requête mal formée ; le message nomme les champs.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentification refusée : `token_expired`, `invalid_token`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "token_expired": {
                    "summary": "token_expired",
                    "description": "Jeton de conversation périmé : rouvrir avec `resume`.",
                    "value": {
                      "error": {
                        "code": "token_expired",
                        "message": "Jeton de conversation périmé : rouvrir avec `resume`.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  },
                  "invalid_token": {
                    "summary": "invalid_token",
                    "description": "Jeton de conversation invalide.",
                    "value": {
                      "error": {
                        "code": "invalid_token",
                        "message": "Jeton de conversation invalide.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Accès refusé : `formula_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "formula_not_allowed": {
                    "summary": "formula_not_allowed",
                    "description": "Fonction non incluse dans la formule.",
                    "value": {
                      "error": {
                        "code": "formula_not_allowed",
                        "message": "Fonction non incluse dans la formule.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Introuvable : `conversation_not_found`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "conversation_not_found": {
                    "summary": "conversation_not_found",
                    "description": "Conversation inconnue ou terminée.",
                    "value": {
                      "error": {
                        "code": "conversation_not_found",
                        "message": "Conversation inconnue ou terminée.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Voix indisponible : `voice_limit_reached`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-Twimi-Voice-Reason": {
                "$ref": "#/components/headers/X-Twimi-Voice-Reason"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "voice_limit_reached": {
                    "summary": "voice_limit_reached",
                    "description": "Voix indisponible : la suite par écrit (`X-Twimi-Voice-Reason`).",
                    "value": {
                      "error": {
                        "code": "voice_limit_reached",
                        "message": "Voix indisponible : la suite par écrit (`X-Twimi-Voice-Reason`).",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "410": {
            "description": "Conversation expirée : `conversation_expired`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "conversation_expired": {
                    "summary": "conversation_expired",
                    "description": "Conversation de plus de 10 minutes : en ouvrir une nouvelle.",
                    "value": {
                      "error": {
                        "code": "conversation_expired",
                        "message": "Conversation de plus de 10 minutes : en ouvrir une nouvelle.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Trop de requêtes : `rate_limited`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "rate_limited",
                    "description": "Trop de requêtes ; attendre la durée de l'en-tête `Retry-After`.",
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes ; attendre la durée de l'en-tête `Retry-After`.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "Service momentanément indisponible : `voice_unavailable`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "voice_unavailable": {
                    "summary": "voice_unavailable",
                    "description": "Synthèse vocale momentanément indisponible.",
                    "value": {
                      "error": {
                        "code": "voice_unavailable",
                        "message": "Synthèse vocale momentanément indisponible.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "summary": "Texte → voix de l'avatar",
        "tags": [
          "voix"
        ],
        "security": [
          {
            "jetonConversation": []
          }
        ]
      }
    },
    "/v1/conversations/{conversation_id}/transcriptions": {
      "post": {
        "description": "Avec le jeton de conversation. Formules Avatar Widget et Avatar Agent. Le son n'est jamais conservé.\nUne question orale dure 30 secondes au plus (offre) : coupez l'enregistrement au-delà.\n20 transcriptions par minute et par conversation.",
        "operationId": "transcription_v1_conversations__conversation_id__transcriptions_post",
        "parameters": [
          {
            "in": "path",
            "name": "conversation_id",
            "required": true,
            "schema": {
              "title": "Conversation Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/QuestionOrale"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Question transcrite.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "text": "Vous avez des SUV ?",
                  "lang": "fr"
                }
              }
            }
          },
          "400": {
            "description": "Requête invalide : `invalid_request`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "invalid_request": {
                    "summary": "invalid_request",
                    "description": "Requête mal formée ; le message nomme les champs.",
                    "value": {
                      "error": {
                        "code": "invalid_request",
                        "message": "Requête mal formée ; le message nomme les champs.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentification refusée : `token_expired`, `invalid_token`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "token_expired": {
                    "summary": "token_expired",
                    "description": "Jeton de conversation périmé : rouvrir avec `resume`.",
                    "value": {
                      "error": {
                        "code": "token_expired",
                        "message": "Jeton de conversation périmé : rouvrir avec `resume`.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  },
                  "invalid_token": {
                    "summary": "invalid_token",
                    "description": "Jeton de conversation invalide.",
                    "value": {
                      "error": {
                        "code": "invalid_token",
                        "message": "Jeton de conversation invalide.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Accès refusé : `formula_not_allowed`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "formula_not_allowed": {
                    "summary": "formula_not_allowed",
                    "description": "Fonction non incluse dans la formule.",
                    "value": {
                      "error": {
                        "code": "formula_not_allowed",
                        "message": "Fonction non incluse dans la formule.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Introuvable : `conversation_not_found`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "conversation_not_found": {
                    "summary": "conversation_not_found",
                    "description": "Conversation inconnue ou terminée.",
                    "value": {
                      "error": {
                        "code": "conversation_not_found",
                        "message": "Conversation inconnue ou terminée.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Voix indisponible : `voice_limit_reached`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-Twimi-Voice-Reason": {
                "$ref": "#/components/headers/X-Twimi-Voice-Reason"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "voice_limit_reached": {
                    "summary": "voice_limit_reached",
                    "description": "Voix indisponible : la suite par écrit (`X-Twimi-Voice-Reason`).",
                    "value": {
                      "error": {
                        "code": "voice_limit_reached",
                        "message": "Voix indisponible : la suite par écrit (`X-Twimi-Voice-Reason`).",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "410": {
            "description": "Conversation expirée : `conversation_expired`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "conversation_expired": {
                    "summary": "conversation_expired",
                    "description": "Conversation de plus de 10 minutes : en ouvrir une nouvelle.",
                    "value": {
                      "error": {
                        "code": "conversation_expired",
                        "message": "Conversation de plus de 10 minutes : en ouvrir une nouvelle.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "Trop volumineux : `audio_too_large`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "audio_too_large": {
                    "summary": "audio_too_large",
                    "description": "Son de plus de 5 Mo.",
                    "value": {
                      "error": {
                        "code": "audio_too_large",
                        "message": "Son de plus de 5 Mo.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Trop de requêtes : `rate_limited`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "rate_limited",
                    "description": "Trop de requêtes ; attendre la durée de l'en-tête `Retry-After`.",
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "Trop de requêtes ; attendre la durée de l'en-tête `Retry-After`.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "Service momentanément indisponible : `transcription_unavailable`",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erreur"
                },
                "examples": {
                  "transcription_unavailable": {
                    "summary": "transcription_unavailable",
                    "description": "Transcription momentanément indisponible.",
                    "value": {
                      "error": {
                        "code": "transcription_unavailable",
                        "message": "Transcription momentanément indisponible.",
                        "request_id": "req_7c1e…"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "summary": "Voix du visiteur → texte",
        "tags": [
          "voix"
        ],
        "security": [
          {
            "jetonConversation": []
          }
        ]
      }
    }
  },
  "servers": [
    {
      "url": "https://api-dev.atwimi.com",
      "description": "Essais : clés pk_test_… et sk_test_…"
    },
    {
      "url": "https://api.twimi.ai",
      "description": "Production (à venir) : clés pk_live_… et sk_live_…"
    }
  ],
  "tags": [
    {
      "name": "service",
      "x-displayName": "Service",
      "description": "État du service, vérification d'une clé et consommation du mois."
    },
    {
      "name": "avatars",
      "x-displayName": "Avatars",
      "description": "Fiche de l'avatar et fichiers du visage. L'affichage n'est jamais décompté. Le SDK Twimi fait tout cela pour vous ; ces routes servent à une intégration sans SDK."
    },
    {
      "name": "conversations",
      "x-displayName": "Conversations",
      "description": "Ouvrir une conversation à la **première action** du visiteur, renouveler son jeton, la terminer. Une conversation dure 10 minutes au plus depuis son ouverture."
    },
    {
      "name": "réponses",
      "x-displayName": "Questions et réponses",
      "description": "Poser la question du visiteur à l'IA de Twimi ; la réponse arrive en flux (SSE)."
    },
    {
      "name": "voix",
      "x-displayName": "Voix",
      "description": "Faire parler l'avatar (texte → MP3) et transcrire la question orale du visiteur. Formules Avatar Widget et Avatar Agent."
    }
  ]
}
