{
    "openapi": "3.1.0",
    "info": {
        "title": "API de génération audio",
        "version": "1.00",
        "description": "Génération asynchrone de contenus et de créations audio pour la radio. Authentification HTTP Basic (identifiant de clé : secret). Chaque génération est facturée au tarif fixe du service ; en cas d'échec les crédits sont rendus et un fichier de silence est fourni."
    },
    "servers": [
        {
            "url": "https://api.4radio.fr"
        }
    ],
    "security": [
        {
            "basicAuth": []
        }
    ],
    "paths": {
        "/v1/me": {
            "get": {
                "summary": "Compte et clé",
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "401": {
                        "description": "Non authentifié",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/v1/pricing": {
            "get": {
                "summary": "Tarifs fixes par service",
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/v1/services": {
            "get": {
                "summary": "Catalogue + état de configuration du compte",
                "parameters": [
                    {
                        "name": "lang",
                        "in": "query",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "fr"
                            ]
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/v1/voices": {
            "get": {
                "summary": "Voix disponibles (service spot)",
                "parameters": [
                    {
                        "name": "service",
                        "in": "query",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "spot"
                            ]
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/v1/generations": {
            "post": {
                "summary": "Lancer une génération",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "service"
                                ],
                                "properties": {
                                    "service": {
                                        "type": "string",
                                        "enum": [
                                            "national_news",
                                            "weather",
                                            "traffic",
                                            "fuel_prices",
                                            "local_news",
                                            "local_sport",
                                            "local_agenda",
                                            "wildfire",
                                            "horoscope",
                                            "horoscope_love",
                                            "crime_chronicle",
                                            "invention_of_the_day",
                                            "world_day",
                                            "saint_of_the_day",
                                            "beach_weather",
                                            "tv_audience",
                                            "tv_tonight",
                                            "spot",
                                            "promo",
                                            "jingle",
                                            "jingle_musical",
                                            "bed",
                                            "host_intervention"
                                        ]
                                    },
                                    "lang": {
                                        "type": "string",
                                        "enum": [
                                            "fr"
                                        ],
                                        "default": "fr"
                                    },
                                    "params": {
                                        "oneOf": [
                                            {
                                                "title": "national_news",
                                                "type": "object",
                                                "properties": {
                                                    "voice": {
                                                        "type": "string",
                                                        "description": "journaliste (voix humaine du journaliste, tarif de base) ou l'identifiant d'une voix de GET /v1/voices?service=national_news (conversion de voix : + 0,33 €)."
                                                    },
                                                    "bed": {
                                                        "type": "string",
                                                        "enum": [
                                                            "bed-1",
                                                            "bed-2",
                                                            "bed-3",
                                                            "own",
                                                            "none"
                                                        ],
                                                        "description": "Tapis du catalogue, own = votre tapis envoyé (bed_url), none = voix seule."
                                                    },
                                                    "bed_url": {
                                                        "type": "string",
                                                        "maxLength": 500,
                                                        "description": "URL https publique d'un MP3 (≥ 1 min 30, ≤ 10 Mo) : devient votre tapis personnel et le tapis actif."
                                                    },
                                                    "bed_volume_pct": {
                                                        "type": "integer",
                                                        "minimum": 10,
                                                        "maximum": 100,
                                                        "description": "Volume du tapis (%)."
                                                    },
                                                    "voice_delay": {
                                                        "type": "integer",
                                                        "minimum": 0,
                                                        "maximum": 30,
                                                        "description": "Délai (s) avant la voix sur le tapis."
                                                    }
                                                }
                                            },
                                            {
                                                "title": "weather",
                                                "type": "object",
                                                "properties": {
                                                    "radio_name": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Nom de la radio tel qu'il sera prononcé."
                                                    },
                                                    "department_code": {
                                                        "type": "string",
                                                        "description": "Code département (voir GET /v1/reference/departments)."
                                                    },
                                                    "voice_id": {
                                                        "type": "integer",
                                                        "description": "Voix (voir GET /v1/voices?service=…)."
                                                    },
                                                    "include_sunset": {
                                                        "type": "boolean",
                                                        "description": "Annoncer le coucher du soleil."
                                                    },
                                                    "include_tomorrow": {
                                                        "type": "boolean",
                                                        "description": "Ajouter la tendance de demain."
                                                    },
                                                    "voice_delay": {
                                                        "type": "integer",
                                                        "minimum": 0,
                                                        "maximum": 30,
                                                        "description": "Délai (s) avant la voix sur le tapis musical."
                                                    },
                                                    "bed": {
                                                        "type": "string",
                                                        "enum": [
                                                            "none"
                                                        ],
                                                        "description": "none = retirer le tapis (voix seule)."
                                                    },
                                                    "bed_url": {
                                                        "type": "string",
                                                        "maxLength": 500,
                                                        "description": "URL https publique d'un MP3 (≥ 1 min 30, ≤ 10 Mo) utilisé comme tapis musical sous la voix — remplace le tapis actuel."
                                                    }
                                                }
                                            },
                                            {
                                                "title": "traffic",
                                                "type": "object",
                                                "properties": {
                                                    "radio_name": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Nom de la radio tel qu'il sera prononcé."
                                                    },
                                                    "department_code": {
                                                        "type": "string",
                                                        "description": "Code département (voir GET /v1/reference/departments)."
                                                    },
                                                    "voice_id": {
                                                        "type": "integer",
                                                        "description": "Voix (voir GET /v1/voices?service=…)."
                                                    },
                                                    "city": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Ville centrale (vérifiée dans le département)."
                                                    },
                                                    "radius_km": {
                                                        "type": "integer",
                                                        "minimum": 8,
                                                        "maximum": 50,
                                                        "description": "Rayon autour de la ville."
                                                    },
                                                    "voice_delay": {
                                                        "type": "integer",
                                                        "minimum": 0,
                                                        "maximum": 30,
                                                        "description": "Délai (s) avant la voix sur le tapis musical."
                                                    },
                                                    "bed": {
                                                        "type": "string",
                                                        "enum": [
                                                            "none"
                                                        ],
                                                        "description": "none = retirer le tapis (voix seule)."
                                                    },
                                                    "bed_url": {
                                                        "type": "string",
                                                        "maxLength": 500,
                                                        "description": "URL https publique d'un MP3 (≥ 1 min 30, ≤ 10 Mo) utilisé comme tapis musical sous la voix — remplace le tapis actuel."
                                                    }
                                                }
                                            },
                                            {
                                                "title": "fuel_prices",
                                                "type": "object",
                                                "properties": {
                                                    "radio_name": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Nom de la radio tel qu'il sera prononcé."
                                                    },
                                                    "department_code": {
                                                        "type": "string",
                                                        "description": "Code département (voir GET /v1/reference/departments)."
                                                    },
                                                    "voice_id": {
                                                        "type": "integer",
                                                        "description": "Voix (voir GET /v1/voices?service=…)."
                                                    },
                                                    "city": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Ville centrale (vérifiée dans le département)."
                                                    },
                                                    "radius_km": {
                                                        "type": "integer",
                                                        "minimum": 8,
                                                        "maximum": 50,
                                                        "description": "Rayon autour de la ville."
                                                    },
                                                    "voice_delay": {
                                                        "type": "integer",
                                                        "minimum": 0,
                                                        "maximum": 30,
                                                        "description": "Délai (s) avant la voix sur le tapis musical."
                                                    },
                                                    "bed": {
                                                        "type": "string",
                                                        "enum": [
                                                            "none"
                                                        ],
                                                        "description": "none = retirer le tapis (voix seule)."
                                                    },
                                                    "bed_url": {
                                                        "type": "string",
                                                        "maxLength": 500,
                                                        "description": "URL https publique d'un MP3 (≥ 1 min 30, ≤ 10 Mo) utilisé comme tapis musical sous la voix — remplace le tapis actuel."
                                                    }
                                                }
                                            },
                                            {
                                                "title": "local_news",
                                                "type": "object",
                                                "properties": {
                                                    "radio_name": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Nom de la radio tel qu'il sera prononcé."
                                                    },
                                                    "department_code": {
                                                        "type": "string",
                                                        "description": "Code département (voir GET /v1/reference/departments)."
                                                    },
                                                    "voice_id": {
                                                        "type": "integer",
                                                        "description": "Voix (voir GET /v1/voices?service=…)."
                                                    },
                                                    "city": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Ville centrale (vérifiée dans le département)."
                                                    },
                                                    "radius_km": {
                                                        "type": "integer",
                                                        "minimum": 5,
                                                        "maximum": 15,
                                                        "description": "Rayon autour de la ville."
                                                    },
                                                    "department2_code": {
                                                        "type": "string",
                                                        "description": "Second département (radio à cheval sur deux zones). Vide = un seul."
                                                    },
                                                    "city2": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Ville centrale de la zone 2."
                                                    },
                                                    "writing_style": {
                                                        "type": "string",
                                                        "enum": [
                                                            "neutre",
                                                            "senior",
                                                            "adulte",
                                                            "jeune_adulte"
                                                        ],
                                                        "description": "Style d'écriture."
                                                    },
                                                    "voice_delay": {
                                                        "type": "integer",
                                                        "minimum": 0,
                                                        "maximum": 30,
                                                        "description": "Délai (s) avant la voix sur le tapis musical."
                                                    },
                                                    "sector_cities2": {
                                                        "type": "array",
                                                        "maximum": 10,
                                                        "description": "Communes de la zone 2 (remplace la liste détectée)."
                                                    },
                                                    "sector_cities": {
                                                        "type": "array",
                                                        "maximum": 100,
                                                        "description": "Communes du secteur (remplace la liste détectée automatiquement à partir de city/radius_km)."
                                                    },
                                                    "source_urls": {
                                                        "type": "array",
                                                        "maximum": 5,
                                                        "description": "Jusqu'à 5 URL de sites d'information locale (https) utilisés comme sources."
                                                    },
                                                    "bed": {
                                                        "type": "string",
                                                        "enum": [
                                                            "bed-1",
                                                            "bed-2",
                                                            "bed-3",
                                                            "none"
                                                        ],
                                                        "description": "Tapis musical du catalogue, ou none (voix seule)."
                                                    },
                                                    "bed_url": {
                                                        "type": "string",
                                                        "maxLength": 500,
                                                        "description": "URL https publique d'un MP3 (≥ 1 min 30, ≤ 10 Mo) utilisé comme tapis musical sous la voix — remplace le tapis actuel."
                                                    },
                                                    "bed_volume_pct": {
                                                        "type": "integer",
                                                        "minimum": 10,
                                                        "maximum": 100,
                                                        "description": "Volume du tapis (%)."
                                                    }
                                                }
                                            },
                                            {
                                                "title": "local_sport",
                                                "type": "object",
                                                "properties": {
                                                    "radio_name": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Nom de la radio tel qu'il sera prononcé."
                                                    },
                                                    "department_code": {
                                                        "type": "string",
                                                        "description": "Code département (voir GET /v1/reference/departments)."
                                                    },
                                                    "voice_id": {
                                                        "type": "integer",
                                                        "description": "Voix (voir GET /v1/voices?service=…)."
                                                    },
                                                    "city": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Ville centrale (vérifiée dans le département)."
                                                    },
                                                    "radius_km": {
                                                        "type": "integer",
                                                        "minimum": 5,
                                                        "maximum": 15,
                                                        "description": "Rayon autour de la ville."
                                                    },
                                                    "department2_code": {
                                                        "type": "string",
                                                        "description": "Second département (zone 2). Vide = un seul."
                                                    },
                                                    "city2": {
                                                        "type": "string",
                                                        "maxLength": 80
                                                    },
                                                    "voice_delay": {
                                                        "type": "integer",
                                                        "minimum": 0,
                                                        "maximum": 30,
                                                        "description": "Délai (s) avant la voix sur le tapis musical."
                                                    },
                                                    "sector_cities2": {
                                                        "type": "array",
                                                        "maximum": 10,
                                                        "description": "Communes de la zone 2 (remplace la liste détectée)."
                                                    },
                                                    "sector_cities": {
                                                        "type": "array",
                                                        "maximum": 100,
                                                        "description": "Communes du secteur (remplace la liste détectée automatiquement à partir de city/radius_km)."
                                                    },
                                                    "source_urls": {
                                                        "type": "array",
                                                        "maximum": 5,
                                                        "description": "Jusqu'à 5 URL de sites d'information locale (https) utilisés comme sources."
                                                    },
                                                    "bed": {
                                                        "type": "string",
                                                        "enum": [
                                                            "bed-1",
                                                            "bed-2",
                                                            "bed-3",
                                                            "none"
                                                        ],
                                                        "description": "Tapis musical du catalogue, ou none (voix seule)."
                                                    },
                                                    "bed_url": {
                                                        "type": "string",
                                                        "maxLength": 500,
                                                        "description": "URL https publique d'un MP3 (≥ 1 min 30, ≤ 10 Mo) utilisé comme tapis musical sous la voix — remplace le tapis actuel."
                                                    },
                                                    "bed_volume_pct": {
                                                        "type": "integer",
                                                        "minimum": 10,
                                                        "maximum": 100,
                                                        "description": "Volume du tapis (%)."
                                                    }
                                                }
                                            },
                                            {
                                                "title": "local_agenda",
                                                "type": "object",
                                                "properties": {
                                                    "radio_name": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Nom de la radio tel qu'il sera prononcé."
                                                    },
                                                    "department_code": {
                                                        "type": "string",
                                                        "description": "Code département (voir GET /v1/reference/departments)."
                                                    },
                                                    "voice_id": {
                                                        "type": "integer",
                                                        "description": "Voix (voir GET /v1/voices?service=…)."
                                                    },
                                                    "city": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Ville centrale (vérifiée dans le département)."
                                                    },
                                                    "radius_km": {
                                                        "type": "integer",
                                                        "minimum": 5,
                                                        "maximum": 15,
                                                        "description": "Rayon autour de la ville."
                                                    },
                                                    "voice_delay": {
                                                        "type": "integer",
                                                        "minimum": 0,
                                                        "maximum": 30,
                                                        "description": "Délai (s) avant la voix sur le tapis musical."
                                                    },
                                                    "sector_cities": {
                                                        "type": "array",
                                                        "maximum": 100,
                                                        "description": "Communes du secteur (remplace la liste détectée automatiquement à partir de city/radius_km)."
                                                    },
                                                    "source_urls": {
                                                        "type": "array",
                                                        "maximum": 5,
                                                        "description": "Jusqu'à 5 URL de sites d'information locale (https) utilisés comme sources."
                                                    },
                                                    "bed": {
                                                        "type": "string",
                                                        "enum": [
                                                            "bed-1",
                                                            "bed-2",
                                                            "bed-3",
                                                            "none"
                                                        ],
                                                        "description": "Tapis musical du catalogue, ou none (voix seule)."
                                                    },
                                                    "bed_url": {
                                                        "type": "string",
                                                        "maxLength": 500,
                                                        "description": "URL https publique d'un MP3 (≥ 1 min 30, ≤ 10 Mo) utilisé comme tapis musical sous la voix — remplace le tapis actuel."
                                                    },
                                                    "bed_volume_pct": {
                                                        "type": "integer",
                                                        "minimum": 10,
                                                        "maximum": 100,
                                                        "description": "Volume du tapis (%)."
                                                    }
                                                }
                                            },
                                            {
                                                "title": "wildfire",
                                                "type": "object",
                                                "properties": {
                                                    "radio_name": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Nom de la radio tel qu'il sera prononcé."
                                                    },
                                                    "department_code": {
                                                        "type": "string",
                                                        "description": "Code département (voir GET /v1/reference/departments)."
                                                    },
                                                    "voice_id": {
                                                        "type": "integer",
                                                        "description": "Voix (voir GET /v1/voices?service=…)."
                                                    },
                                                    "scope": {
                                                        "type": "string",
                                                        "enum": [
                                                            "dept",
                                                            "region",
                                                            "nat"
                                                        ],
                                                        "description": "Échelle : département, région ou national."
                                                    },
                                                    "region_name": {
                                                        "type": "string",
                                                        "enum": [
                                                            "Auvergne-Rhône-Alpes",
                                                            "Bourgogne-Franche-Comté",
                                                            "Bretagne",
                                                            "Centre-Val de Loire",
                                                            "Corse",
                                                            "Grand Est",
                                                            "Hauts-de-France",
                                                            "Île-de-France",
                                                            "Normandie",
                                                            "Nouvelle-Aquitaine",
                                                            "Occitanie",
                                                            "Pays de la Loire",
                                                            "Provence-Alpes-Côte d'Azur"
                                                        ],
                                                        "description": "Région (échelle region). Défaut : région du département."
                                                    },
                                                    "city": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Ville centrale (vérifiée dans le département)."
                                                    },
                                                    "radius_km": {
                                                        "type": "integer",
                                                        "minimum": 5,
                                                        "maximum": 15,
                                                        "description": "Rayon autour de la ville."
                                                    },
                                                    "voice_delay": {
                                                        "type": "integer",
                                                        "minimum": 0,
                                                        "maximum": 30,
                                                        "description": "Délai (s) avant la voix sur le tapis musical."
                                                    },
                                                    "sector_cities": {
                                                        "type": "array",
                                                        "maximum": 100,
                                                        "description": "Communes du secteur (remplace la liste détectée automatiquement à partir de city/radius_km)."
                                                    },
                                                    "source_urls": {
                                                        "type": "array",
                                                        "maximum": 5,
                                                        "description": "Jusqu'à 5 URL de sites d'information locale (https) utilisés comme sources."
                                                    },
                                                    "bed": {
                                                        "type": "string",
                                                        "enum": [
                                                            "bed-1",
                                                            "bed-2",
                                                            "bed-3",
                                                            "none"
                                                        ],
                                                        "description": "Tapis musical du catalogue, ou none (voix seule)."
                                                    },
                                                    "bed_url": {
                                                        "type": "string",
                                                        "maxLength": 500,
                                                        "description": "URL https publique d'un MP3 (≥ 1 min 30, ≤ 10 Mo) utilisé comme tapis musical sous la voix — remplace le tapis actuel."
                                                    },
                                                    "bed_volume_pct": {
                                                        "type": "integer",
                                                        "minimum": 10,
                                                        "maximum": 100,
                                                        "description": "Volume du tapis (%)."
                                                    }
                                                }
                                            },
                                            {
                                                "title": "horoscope",
                                                "type": "object",
                                                "properties": {
                                                    "radio_name": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Nom de la radio tel qu'il sera prononcé."
                                                    },
                                                    "voice_id": {
                                                        "type": "integer",
                                                        "description": "Voix (voir GET /v1/voices?service=…)."
                                                    },
                                                    "voice_delay": {
                                                        "type": "integer",
                                                        "minimum": 0,
                                                        "maximum": 30,
                                                        "description": "Délai (s) avant la voix sur le tapis musical."
                                                    },
                                                    "bed": {
                                                        "type": "string",
                                                        "enum": [
                                                            "none"
                                                        ],
                                                        "description": "none = retirer le tapis (voix seule)."
                                                    },
                                                    "bed_url": {
                                                        "type": "string",
                                                        "maxLength": 500,
                                                        "description": "URL https publique d'un MP3 (≥ 1 min 30, ≤ 10 Mo) utilisé comme tapis musical sous la voix — remplace le tapis actuel."
                                                    }
                                                }
                                            },
                                            {
                                                "title": "horoscope_love",
                                                "type": "object",
                                                "properties": {
                                                    "radio_name": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Nom de la radio tel qu'il sera prononcé."
                                                    },
                                                    "voice_id": {
                                                        "type": "integer",
                                                        "description": "Voix (voir GET /v1/voices?service=…)."
                                                    },
                                                    "voice_delay": {
                                                        "type": "integer",
                                                        "minimum": 0,
                                                        "maximum": 30,
                                                        "description": "Délai (s) avant la voix sur le tapis musical."
                                                    },
                                                    "bed": {
                                                        "type": "string",
                                                        "enum": [
                                                            "none"
                                                        ],
                                                        "description": "none = retirer le tapis (voix seule)."
                                                    },
                                                    "bed_url": {
                                                        "type": "string",
                                                        "maxLength": 500,
                                                        "description": "URL https publique d'un MP3 (≥ 1 min 30, ≤ 10 Mo) utilisé comme tapis musical sous la voix — remplace le tapis actuel."
                                                    }
                                                }
                                            },
                                            {
                                                "title": "crime_chronicle",
                                                "type": "object",
                                                "properties": {
                                                    "radio_name": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Nom de la radio tel qu'il sera prononcé."
                                                    },
                                                    "voice_id": {
                                                        "type": "integer",
                                                        "description": "Voix (voir GET /v1/voices?service=…)."
                                                    },
                                                    "voice_delay": {
                                                        "type": "integer",
                                                        "minimum": 0,
                                                        "maximum": 30,
                                                        "description": "Délai (s) avant la voix sur le tapis musical."
                                                    },
                                                    "bed": {
                                                        "type": "string",
                                                        "enum": [
                                                            "none"
                                                        ],
                                                        "description": "none = retirer le tapis (voix seule)."
                                                    },
                                                    "bed_url": {
                                                        "type": "string",
                                                        "maxLength": 500,
                                                        "description": "URL https publique d'un MP3 (≥ 1 min 30, ≤ 10 Mo) utilisé comme tapis musical sous la voix — remplace le tapis actuel."
                                                    }
                                                }
                                            },
                                            {
                                                "title": "invention_of_the_day",
                                                "type": "object",
                                                "properties": {
                                                    "radio_name": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Nom de la radio tel qu'il sera prononcé."
                                                    },
                                                    "voice_id": {
                                                        "type": "integer",
                                                        "description": "Voix (voir GET /v1/voices?service=…)."
                                                    },
                                                    "voice_delay": {
                                                        "type": "integer",
                                                        "minimum": 0,
                                                        "maximum": 30,
                                                        "description": "Délai (s) avant la voix sur le tapis musical."
                                                    },
                                                    "bed": {
                                                        "type": "string",
                                                        "enum": [
                                                            "none"
                                                        ],
                                                        "description": "none = retirer le tapis (voix seule)."
                                                    },
                                                    "bed_url": {
                                                        "type": "string",
                                                        "maxLength": 500,
                                                        "description": "URL https publique d'un MP3 (≥ 1 min 30, ≤ 10 Mo) utilisé comme tapis musical sous la voix — remplace le tapis actuel."
                                                    }
                                                }
                                            },
                                            {
                                                "title": "world_day",
                                                "type": "object",
                                                "properties": {
                                                    "radio_name": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Nom de la radio tel qu'il sera prononcé."
                                                    },
                                                    "voice_id": {
                                                        "type": "integer",
                                                        "description": "Voix (voir GET /v1/voices?service=…)."
                                                    },
                                                    "voice_delay": {
                                                        "type": "integer",
                                                        "minimum": 0,
                                                        "maximum": 30,
                                                        "description": "Délai (s) avant la voix sur le tapis musical."
                                                    },
                                                    "bed": {
                                                        "type": "string",
                                                        "enum": [
                                                            "none"
                                                        ],
                                                        "description": "none = retirer le tapis (voix seule)."
                                                    },
                                                    "bed_url": {
                                                        "type": "string",
                                                        "maxLength": 500,
                                                        "description": "URL https publique d'un MP3 (≥ 1 min 30, ≤ 10 Mo) utilisé comme tapis musical sous la voix — remplace le tapis actuel."
                                                    }
                                                }
                                            },
                                            {
                                                "title": "saint_of_the_day",
                                                "type": "object",
                                                "properties": {
                                                    "radio_name": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Nom de la radio tel qu'il sera prononcé."
                                                    },
                                                    "voice_id": {
                                                        "type": "integer",
                                                        "description": "Voix (voir GET /v1/voices?service=…)."
                                                    },
                                                    "voice_delay": {
                                                        "type": "integer",
                                                        "minimum": 0,
                                                        "maximum": 30,
                                                        "description": "Délai (s) avant la voix sur le tapis musical."
                                                    },
                                                    "bed": {
                                                        "type": "string",
                                                        "enum": [
                                                            "none"
                                                        ],
                                                        "description": "none = retirer le tapis (voix seule)."
                                                    },
                                                    "bed_url": {
                                                        "type": "string",
                                                        "maxLength": 500,
                                                        "description": "URL https publique d'un MP3 (≥ 1 min 30, ≤ 10 Mo) utilisé comme tapis musical sous la voix — remplace le tapis actuel."
                                                    }
                                                }
                                            },
                                            {
                                                "title": "beach_weather",
                                                "type": "object",
                                                "properties": {
                                                    "radio_name": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Nom de la radio tel qu'il sera prononcé."
                                                    },
                                                    "voice_id": {
                                                        "type": "integer",
                                                        "description": "Voix (voir GET /v1/voices?service=…)."
                                                    },
                                                    "mode": {
                                                        "type": "string",
                                                        "enum": [
                                                            "mer",
                                                            "baignade",
                                                            "mixte"
                                                        ],
                                                        "description": "mer = zone côtière ; baignade = lacs/rivières d'un département ; mixte = les deux."
                                                    },
                                                    "zone": {
                                                        "type": "string",
                                                        "enum": [
                                                            "cote-opale",
                                                            "baie-somme",
                                                            "cote-albatre",
                                                            "cote-fleurie",
                                                            "bessin",
                                                            "cotentin",
                                                            "baie-mont-saint-michel",
                                                            "cote-emeraude",
                                                            "cote-granit-rose",
                                                            "finistere-nord",
                                                            "morbihan",
                                                            "loire-atlantique",
                                                            "vendee",
                                                            "charente-maritime",
                                                            "gironde",
                                                            "landes",
                                                            "cote-basque",
                                                            "golfe-lion",
                                                            "cote-bleue",
                                                            "cote-azur-var",
                                                            "riviera",
                                                            "corse",
                                                            "antilles-martinique",
                                                            "antilles-guadeloupe",
                                                            "antilles-saint-martin",
                                                            "antilles-saint-barth",
                                                            "guyane",
                                                            "ocean-indien-reunion",
                                                            "ocean-indien-mayotte",
                                                            "pacifique-nc",
                                                            "pacifique-polynesie"
                                                        ],
                                                        "description": "Zone côtière (modes mer/mixte ; voir GET /v1/reference/beach-zones)."
                                                    },
                                                    "department_code": {
                                                        "type": "string",
                                                        "description": "Code département (voir GET /v1/reference/departments)."
                                                    },
                                                    "sites": {
                                                        "type": "array",
                                                        "maximum": 6,
                                                        "description": "Codes des lieux de baignade (1 à 6, voir GET /v1/reference/bathing-sites?department=…)."
                                                    },
                                                    "voice_delay": {
                                                        "type": "integer",
                                                        "minimum": 0,
                                                        "maximum": 30,
                                                        "description": "Délai (s) avant la voix sur le tapis musical."
                                                    },
                                                    "bed": {
                                                        "type": "string",
                                                        "enum": [
                                                            "none"
                                                        ],
                                                        "description": "none = retirer le tapis (voix seule)."
                                                    },
                                                    "bed_url": {
                                                        "type": "string",
                                                        "maxLength": 500,
                                                        "description": "URL https publique d'un MP3 (≥ 1 min 30, ≤ 10 Mo) utilisé comme tapis musical sous la voix — remplace le tapis actuel."
                                                    }
                                                }
                                            },
                                            {
                                                "title": "tv_audience",
                                                "type": "object",
                                                "properties": {
                                                    "radio_name": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Nom de la radio tel qu'il sera prononcé."
                                                    },
                                                    "voice_id": {
                                                        "type": "integer",
                                                        "description": "Voix (voir GET /v1/voices?service=…)."
                                                    },
                                                    "voice_delay": {
                                                        "type": "integer",
                                                        "minimum": 0,
                                                        "maximum": 30,
                                                        "description": "Délai (s) avant la voix sur le tapis musical."
                                                    },
                                                    "bed": {
                                                        "type": "string",
                                                        "enum": [
                                                            "none"
                                                        ],
                                                        "description": "none = retirer le tapis (voix seule)."
                                                    },
                                                    "bed_url": {
                                                        "type": "string",
                                                        "maxLength": 500,
                                                        "description": "URL https publique d'un MP3 (≥ 1 min 30, ≤ 10 Mo) utilisé comme tapis musical sous la voix — remplace le tapis actuel."
                                                    }
                                                }
                                            },
                                            {
                                                "title": "tv_tonight",
                                                "type": "object",
                                                "properties": {
                                                    "radio_name": {
                                                        "type": "string",
                                                        "maxLength": 80,
                                                        "description": "Nom de la radio tel qu'il sera prononcé."
                                                    },
                                                    "voice_id": {
                                                        "type": "integer",
                                                        "description": "Voix (voir GET /v1/voices?service=…)."
                                                    },
                                                    "country_code": {
                                                        "type": "string",
                                                        "enum": [
                                                            "FR",
                                                            "BE",
                                                            "CH",
                                                            "LU"
                                                        ],
                                                        "description": "Pays des chaînes."
                                                    },
                                                    "voice_delay": {
                                                        "type": "integer",
                                                        "minimum": 0,
                                                        "maximum": 30,
                                                        "description": "Délai (s) avant la voix sur le tapis musical."
                                                    },
                                                    "bed": {
                                                        "type": "string",
                                                        "enum": [
                                                            "none"
                                                        ],
                                                        "description": "none = retirer le tapis (voix seule)."
                                                    },
                                                    "bed_url": {
                                                        "type": "string",
                                                        "maxLength": 500,
                                                        "description": "URL https publique d'un MP3 (≥ 1 min 30, ≤ 10 Mo) utilisé comme tapis musical sous la voix — remplace le tapis actuel."
                                                    }
                                                }
                                            },
                                            {
                                                "title": "spot",
                                                "type": "object",
                                                "properties": {
                                                    "text": {
                                                        "type": "string",
                                                        "minLength": 1,
                                                        "maxLength": 500,
                                                        "description": "Texte dit par la voix."
                                                    },
                                                    "brief": {
                                                        "type": "string",
                                                        "minLength": 80,
                                                        "maxLength": 300,
                                                        "description": "Indications de réalisation (une phrase)."
                                                    },
                                                    "voice_id": {
                                                        "type": "integer",
                                                        "description": "Identifiant d'une voix de GET /v1/voices?service=spot."
                                                    },
                                                    "with_music": {
                                                        "type": "boolean",
                                                        "default": false,
                                                        "description": "Ajoute un fond musical (même prix)."
                                                    }
                                                },
                                                "required": [
                                                    "text",
                                                    "brief",
                                                    "voice_id"
                                                ]
                                            },
                                            {
                                                "title": "promo",
                                                "type": "object",
                                                "properties": {
                                                    "text": {
                                                        "type": "string",
                                                        "minLength": 1,
                                                        "maxLength": 400
                                                    },
                                                    "brief": {
                                                        "type": "string",
                                                        "minLength": 80,
                                                        "maxLength": 300
                                                    },
                                                    "duration": {
                                                        "type": "integer",
                                                        "minimum": 8,
                                                        "maximum": 20,
                                                        "description": "Secondes. Absent = durée automatique selon le texte."
                                                    }
                                                },
                                                "required": [
                                                    "text",
                                                    "brief"
                                                ]
                                            },
                                            {
                                                "title": "jingle",
                                                "type": "object",
                                                "properties": {
                                                    "text": {
                                                        "type": "string",
                                                        "minLength": 1,
                                                        "maxLength": 300
                                                    },
                                                    "brief": {
                                                        "type": "string",
                                                        "minLength": 10,
                                                        "maxLength": 300
                                                    },
                                                    "duration": {
                                                        "type": "integer",
                                                        "minimum": 3,
                                                        "maximum": 15,
                                                        "description": "Secondes. Absent = durée automatique."
                                                    }
                                                },
                                                "required": [
                                                    "text",
                                                    "brief"
                                                ]
                                            },
                                            {
                                                "title": "jingle_musical",
                                                "type": "object",
                                                "properties": {
                                                    "text": {
                                                        "type": "string",
                                                        "minLength": 60,
                                                        "maxLength": 120,
                                                        "description": "Texte chanté (nom de la radio + slogan)."
                                                    },
                                                    "theme": {
                                                        "type": "string",
                                                        "enum": [
                                                            "dance",
                                                            "dance-halloween",
                                                            "dance-christmas",
                                                            "dance-summer",
                                                            "variete",
                                                            "variete-halloween",
                                                            "variete-christmas",
                                                            "variete-summer",
                                                            "pop",
                                                            "pop-halloween",
                                                            "pop-christmas",
                                                            "pop-summer",
                                                            "disco",
                                                            "disco-halloween",
                                                            "disco-christmas",
                                                            "disco-summer",
                                                            "lounge",
                                                            "lounge-halloween",
                                                            "lounge-christmas",
                                                            "lounge-summer",
                                                            "hiphop",
                                                            "hiphop-halloween",
                                                            "hiphop-christmas",
                                                            "hiphop-summer",
                                                            "latino",
                                                            "latino-halloween",
                                                            "latino-christmas",
                                                            "latino-summer",
                                                            "zouk",
                                                            "zouk-halloween",
                                                            "zouk-christmas",
                                                            "zouk-summer",
                                                            "reggae",
                                                            "reggae-halloween",
                                                            "reggae-christmas",
                                                            "reggae-summer",
                                                            "country",
                                                            "country-halloween",
                                                            "country-christmas",
                                                            "country-summer",
                                                            "croyance",
                                                            "croyance-christmas",
                                                            "croyance-summer",
                                                            "annees-60",
                                                            "annees-70",
                                                            "annees-80",
                                                            "annees-90"
                                                        ],
                                                        "description": "Thème musical (voir GET /v1/services)."
                                                    },
                                                    "vocal_gender": {
                                                        "type": "string",
                                                        "enum": [
                                                            "m",
                                                            "f"
                                                        ],
                                                        "description": "Absent = indifférent."
                                                    }
                                                },
                                                "required": [
                                                    "text",
                                                    "theme"
                                                ]
                                            },
                                            {
                                                "title": "bed",
                                                "type": "object",
                                                "properties": {
                                                    "brief": {
                                                        "type": "string",
                                                        "minLength": 10,
                                                        "maxLength": 300,
                                                        "description": "Ambiance souhaitée."
                                                    }
                                                },
                                                "required": [
                                                    "brief"
                                                ]
                                            },
                                            {
                                                "title": "host_intervention",
                                                "type": "object",
                                                "properties": {
                                                    "voice": {
                                                        "type": "string",
                                                        "enum": [
                                                            "hugo",
                                                            "mary",
                                                            "myr"
                                                        ]
                                                    },
                                                    "style": {
                                                        "type": "string",
                                                        "enum": [
                                                            "energique",
                                                            "chaleureux",
                                                            "pose",
                                                            "complice",
                                                            "pro"
                                                        ]
                                                    },
                                                    "radio_name": {
                                                        "type": "string",
                                                        "maxLength": 60
                                                    },
                                                    "slogan": {
                                                        "type": "string",
                                                        "maxLength": 120
                                                    },
                                                    "text": {
                                                        "type": "string",
                                                        "maxLength": 1200,
                                                        "description": "Ce que doit dire l'animateur (phonétique si nécessaire)."
                                                    },
                                                    "music_bed": {
                                                        "type": "boolean",
                                                        "default": false
                                                    },
                                                    "music_style": {
                                                        "type": "string",
                                                        "enum": [
                                                            "dance-electro",
                                                            "variete-francaise",
                                                            "pop-rock",
                                                            "disco-funk",
                                                            "lounge-jazz",
                                                            "hiphop-urban",
                                                            "latino-reggaeton",
                                                            "zouk-kompa",
                                                            "reggae-caraibes",
                                                            "country-folk",
                                                            "croyance",
                                                            "annees-60",
                                                            "annees-70",
                                                            "annees-80",
                                                            "annees-90"
                                                        ],
                                                        "description": "Requis si music_bed = true."
                                                    },
                                                    "date": {
                                                        "type": "string",
                                                        "format": "date",
                                                        "description": "Ancre temporelle (YYYY-MM-DD). Absent = intervention intemporelle."
                                                    }
                                                },
                                                "required": [
                                                    "voice",
                                                    "style",
                                                    "radio_name",
                                                    "slogan",
                                                    "text"
                                                ]
                                            }
                                        ]
                                    },
                                    "dry_run": {
                                        "type": "boolean",
                                        "default": false,
                                        "description": "Valide et renvoie le prix sans rien débiter."
                                    },
                                    "idempotency_key": {
                                        "type": "string",
                                        "maxLength": 80
                                    },
                                    "webhook_url": {
                                        "type": "string",
                                        "format": "uri",
                                        "description": "https:// public. POST signé HMAC-SHA256 à la fin de la génération."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "202": {
                        "description": "Génération acceptée",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "generation": {
                                            "$ref": "#/components/schemas/Generation"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "200": {
                        "description": "dry_run ou rejeu idempotent"
                    },
                    "402": {
                        "description": "Crédits insuffisants",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "Génération déjà en cours / conflit idempotence",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Paramètres invalides / configuration incomplète",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Trop de requêtes",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            },
            "get": {
                "summary": "Historique",
                "parameters": [
                    {
                        "name": "limit",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "maximum": 100,
                            "default": 20
                        }
                    },
                    {
                        "name": "service",
                        "in": "query",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "status",
                        "in": "query",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/v1/generations/{id}": {
            "get": {
                "summary": "État d'une génération",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "generation": {
                                            "$ref": "#/components/schemas/Generation"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Introuvable",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/v1/generations/{id}/file": {
            "get": {
                "summary": "Fichier audio (MP3)",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "variant",
                        "in": "query",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "main",
                                "voice"
                            ]
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "audio/mpeg"
                    },
                    "409": {
                        "description": "Pas encore prête"
                    },
                    "404": {
                        "description": "Expiré / introuvable",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/v1/latest/{service}": {
            "get": {
                "summary": "Dernière génération réussie du service (fichier direct)",
                "parameters": [
                    {
                        "name": "service",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "variant",
                        "in": "query",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "main",
                                "voice"
                            ]
                        }
                    },
                    {
                        "name": "meta",
                        "in": "query",
                        "schema": {
                            "type": "boolean"
                        },
                        "description": "true = JSON de la génération au lieu du fichier"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "audio/mpeg"
                    },
                    "404": {
                        "description": "Aucune génération réussie",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "basicAuth": {
                "type": "http",
                "scheme": "basic"
            }
        },
        "schemas": {
            "Generation": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string",
                        "example": "gen_k3x9v2m8q1w4e7r6t5y2z0"
                    },
                    "service": {
                        "type": "string"
                    },
                    "lang": {
                        "type": "string",
                        "example": "fr"
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "queued",
                            "processing",
                            "completed",
                            "failed"
                        ]
                    },
                    "price": {
                        "type": "number"
                    },
                    "currency": {
                        "type": "string",
                        "example": "EUR"
                    },
                    "refunded": {
                        "type": "boolean"
                    },
                    "error": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "file": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "properties": {
                            "url": {
                                "type": "string"
                            },
                            "voice_url": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            },
                            "format": {
                                "type": "string"
                            },
                            "size": {
                                "type": [
                                    "integer",
                                    "null"
                                ]
                            },
                            "duration": {
                                "type": [
                                    "number",
                                    "null"
                                ]
                            },
                            "silence": {
                                "type": "boolean"
                            },
                            "expires_at": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "format": "date-time"
                            }
                        }
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "completed_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                }
            },
            "Error": {
                "type": "object",
                "properties": {
                    "error": {
                        "type": "object",
                        "properties": {
                            "code": {
                                "type": "string",
                                "enum": [
                                    "unauthorized",
                                    "forbidden",
                                    "ip_not_allowed",
                                    "not_found",
                                    "method_not_allowed",
                                    "invalid_input",
                                    "unsupported_lang",
                                    "unknown_service",
                                    "config_incomplete",
                                    "text_rejected",
                                    "insufficient_credits",
                                    "autorecharge_required",
                                    "generation_in_progress",
                                    "unavailable_now",
                                    "idempotency_conflict",
                                    "rate_limited",
                                    "generation_failed",
                                    "delivery_failed",
                                    "service_unavailable",
                                    "timeout",
                                    "internal_error"
                                ]
                            },
                            "message": {
                                "type": "string"
                            },
                            "details": {
                                "type": "object"
                            }
                        }
                    }
                }
            }
        }
    }
}