{
  "openapi": "3.1.0",
  "info": {
    "title": "EditCaption API",
    "version": "1.0.0",
    "description": "API REST à crédits prépayés pour les outils vidéo EditCaption : sous-titres automatiques, transcription, amélioration du son, conversion, fusion, filigrane, découpe, texte vers voix, changement de voix. Authentification par clé API Bearer. Voir https://editcaption.com/documentation-api pour la documentation complète.",
    "contact": { "email": "editcaption.contact@gmail.com" }
  },
  "servers": [{ "url": "https://editcaption.com" }],
  "security": [{ "bearerAuth": [] }],
  "tags": [
    { "name": "Uploads", "description": "Ingestion de fichiers médias" },
    { "name": "Jobs", "description": "Suivi et récupération des traitements" },
    { "name": "Compte", "description": "Solde de crédits" },
    { "name": "Outils", "description": "Les 10 outils de traitement vidéo/audio" }
  ],
  "paths": {
    "/api/v1/uploads": {
      "post": {
        "tags": ["Uploads"],
        "summary": "Uploader un média",
        "description": "Ingère un fichier vidéo/audio, soit par upload multipart direct, soit par téléchargement depuis une URL publique. Retourne un uploadId réutilisable pendant 3 heures par les autres endpoints.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": { "file": { "type": "string", "format": "binary", "description": "Fichier vidéo ou audio, 5 Go max." } },
                "required": ["file"]
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": { "url": { "type": "string", "format": "uri", "description": "URL publique http(s) du média à télécharger, 5 Go max." } },
                "required": ["url"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Média ingéré",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UploadResponse" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/v1/jobs/{id}": {
      "get": {
        "tags": ["Jobs"],
        "summary": "Statut d'un job",
        "description": "Interroge l'état d'un traitement en cours ou terminé (polling REST). Un job n'est consultable que par la clé API qui l'a créé.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "jobId renvoyé par un endpoint outil" }
        ],
        "responses": {
          "200": {
            "description": "Statut du job",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobStatusResponse" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/v1/account": {
      "get": {
        "tags": ["Compte"],
        "summary": "Solde de crédits",
        "description": "Retourne le solde de crédits et l'email du compte propriétaire de la clé API.",
        "responses": {
          "200": {
            "description": "Solde du compte",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountResponse" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/v1/subtitles": {
      "post": {
        "tags": ["Outils"],
        "summary": "Sous-titres automatiques incrustés",
        "description": "Transcrit et incruste des sous-titres stylés directement dans la vidéo. Coût : 20 crédits/minute de vidéo.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SubtitlesRequest" } } } },
        "responses": {
          "200": { "description": "Job mis en file", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobEnvelope" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/InsufficientCredits" }
        }
      }
    },
    "/api/v1/transcribe": {
      "post": {
        "tags": ["Outils"],
        "summary": "Transcription texte ou SRT",
        "description": "Transcrit l'audio d'une vidéo en texte brut (.txt) ou en sous-titres (.srt). Coût : 15 crédits/minute.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TranscribeRequest" } } } },
        "responses": {
          "200": { "description": "Job mis en file", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobEnvelope" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/InsufficientCredits" }
        }
      }
    },
    "/api/v1/clean-audio": {
      "post": {
        "tags": ["Outils"],
        "summary": "Amélioration du son",
        "description": "Nettoie le bruit de fond et améliore la voix d'une vidéo. Coût : 10 crédits/minute.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CleanAudioRequest" } } } },
        "responses": {
          "200": { "description": "Job mis en file", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobEnvelope" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/InsufficientCredits" }
        }
      }
    },
    "/api/v1/extract-audio": {
      "post": {
        "tags": ["Outils"],
        "summary": "Extraction audio",
        "description": "Extrait la piste audio d'une vidéo (mp3, wav ou aac). Coût fixe : 3 crédits par appel.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExtractAudioRequest" } } } },
        "responses": {
          "200": { "description": "Job mis en file", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobEnvelope" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/InsufficientCredits" }
        }
      }
    },
    "/api/v1/convert": {
      "post": {
        "tags": ["Outils"],
        "summary": "Conversion de format",
        "description": "Convertit une vidéo en mp4, webm, mov, avi ou mkv. Coût : 5 crédits/minute.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ConvertRequest" } } } },
        "responses": {
          "200": { "description": "Job mis en file", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobEnvelope" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/InsufficientCredits" }
        }
      }
    },
    "/api/v1/merge": {
      "post": {
        "tags": ["Outils"],
        "summary": "Fusion de vidéos",
        "description": "Fusionne au moins 2 vidéos uploadées en une seule, dans l'ordre fourni. Coût : 5 crédits/minute de durée totale combinée.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MergeRequest" } } } },
        "responses": {
          "200": { "description": "Job mis en file", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobEnvelope" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/InsufficientCredits" }
        }
      }
    },
    "/api/v1/watermark": {
      "post": {
        "tags": ["Outils"],
        "summary": "Filigrane image ou texte",
        "description": "Ajoute un filigrane (image uploadée ou texte généré) en mosaïque fixe ou en position flottante rotative. Coût : 5 crédits/minute.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WatermarkRequest" } } } },
        "responses": {
          "200": { "description": "Job mis en file", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobEnvelope" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/InsufficientCredits" }
        }
      }
    },
    "/api/v1/trim": {
      "post": {
        "tags": ["Outils"],
        "summary": "Découpe d'un extrait",
        "description": "Extrait le segment [start, end] (en secondes) d'une vidéo. Coût : 3 crédits/minute de l'extrait de SORTIE (pas de la vidéo source).",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TrimRequest" } } } },
        "responses": {
          "200": { "description": "Job mis en file", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobEnvelope" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/InsufficientCredits" }
        }
      }
    },
    "/api/v1/text-to-speech": {
      "post": {
        "tags": ["Outils"],
        "summary": "Texte vers voix",
        "description": "Génère un fichier audio (.wav) à partir d'un texte. Coût : 5 crédits/1000 caractères (40 en voix clonée).",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TtsRequest" } } } },
        "responses": {
          "200": { "description": "Job mis en file", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobEnvelope" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/InsufficientCredits" }
        }
      }
    },
    "/api/v1/voice-change": {
      "post": {
        "tags": ["Outils"],
        "summary": "Changement de voix",
        "description": "Remplace la voix d'une vidéo par une voix du catalogue ou la voix clonée du compte. Coût : 15 crédits/minute (60 en voix clonée).",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VoiceChangeRequest" } } } },
        "responses": {
          "200": { "description": "Job mis en file", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobEnvelope" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/InsufficientCredits" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ecap_live_...",
        "description": "Clé API créée depuis editcaption.com/mon-compte/developpeur. Envoyée en en-tête Authorization: Bearer <clé>."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Requête invalide",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" }, "example": { "error": { "code": "invalid_request", "message": "uploadId manquant." } } } }
      },
      "Unauthorized": {
        "description": "Clé API manquante, invalide ou révoquée",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" }, "example": { "error": { "code": "unauthorized", "message": "Clé API invalide ou révoquée." } } } }
      },
      "NotFound": {
        "description": "Ressource introuvable ou expirée",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" }, "example": { "error": { "code": "not_found", "message": "Job introuvable ou expiré." } } } }
      },
      "InsufficientCredits": {
        "description": "Solde de crédits insuffisant pour ce traitement",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/InsufficientCreditsResponse" }, "example": { "error": { "code": "insufficient_credits", "message": "Crédits insuffisants. Rechargez votre compte sur editcaption.com/api", "balance": 12, "required": 60 } } } }
      }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "string", "enum": ["invalid_request", "unauthorized", "insufficient_credits", "not_found", "internal_error"] },
              "message": { "type": "string" }
            },
            "required": ["code", "message"]
          }
        }
      },
      "InsufficientCreditsResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "string", "enum": ["insufficient_credits"] },
              "message": { "type": "string" },
              "balance": { "type": "integer", "description": "Solde actuel en crédits" },
              "required": { "type": "integer", "description": "Crédits nécessaires pour ce traitement" }
            }
          }
        }
      },
      "UploadResponse": {
        "type": "object",
        "properties": {
          "uploadId": { "type": "string" },
          "durationSeconds": { "type": "number" },
          "expiresIn": { "type": "integer", "description": "Secondes avant expiration (10800 = 3h)" }
        },
        "required": ["uploadId", "durationSeconds", "expiresIn"]
      },
      "AccountResponse": {
        "type": "object",
        "properties": {
          "balance": { "type": "integer" },
          "email": { "type": "string" }
        }
      },
      "JobEnvelope": {
        "type": "object",
        "properties": {
          "jobId": { "type": "string" },
          "status": { "type": "string", "enum": ["queued"] },
          "creditsUsed": { "type": "integer" },
          "creditsRemaining": { "type": "integer" },
          "estimatedSeconds": { "type": "integer", "description": "Estimation indicative, n'affecte jamais la facturation" }
        },
        "required": ["jobId", "status", "creditsUsed", "creditsRemaining", "estimatedSeconds"]
      },
      "JobResult": {
        "type": "object",
        "properties": {
          "url": { "type": "string", "description": "Chemin relatif de téléchargement, ex. /api/download/<id> — à préfixer avec https://editcaption.com" },
          "filename": { "type": "string" }
        },
        "required": ["url", "filename"]
      },
      "JobStatusResponse": {
        "type": "object",
        "properties": {
          "jobId": { "type": "string" },
          "status": { "type": "string", "enum": ["queued", "processing", "completed", "failed"] },
          "progress": { "nullable": true, "description": "Toujours null pour les 10 outils actuels (réservé à un usage futur)." },
          "result": { "$ref": "#/components/schemas/JobResult" },
          "error": { "type": "string", "description": "Présent uniquement si status = failed" }
        },
        "required": ["jobId", "status"]
      },
      "SubtitlesRequest": {
        "type": "object",
        "properties": {
          "uploadId": { "type": "string" },
          "style": { "type": "string", "enum": ["classique", "impact", "contour"], "default": "classique" },
          "color": { "type": "string", "default": "#FFFFFF", "description": "Couleur du texte, hex #RRGGBB" },
          "effectColor": { "type": "string", "default": "#000000", "description": "Couleur de l'ombre/fond/contour selon le style" },
          "position": { "type": "number", "default": 82, "description": "0 = haut, 100 = bas" },
          "size": { "type": "string", "enum": ["petite", "normale", "grande"], "default": "normale" },
          "captionWords": { "type": "integer", "enum": [1, 2, 4], "default": 4 },
          "srtContent": { "type": "string", "description": "Contenu SRT importé, utilisé tel quel au lieu de la transcription automatique" },
          "fontId": { "type": "string", "enum": ["inter", "montserrat", "poppins", "fredoka", "archivo-black", "anton"], "default": "inter" },
          "karaokeMode": { "type": "string", "enum": ["none", "couleur", "taille", "halo"], "default": "none" },
          "highlightColor": { "type": "string", "default": "#FFD166" },
          "shadowIntensity": { "type": "number", "description": "0-100, style classique uniquement", "default": 60 },
          "contourThickness": { "type": "number", "description": "0-100, style contour uniquement", "default": 50 },
          "boxOpacity": { "type": "number", "description": "0-100, style impact uniquement", "default": 75 }
        },
        "required": ["uploadId"]
      },
      "TranscribeRequest": {
        "type": "object",
        "properties": {
          "uploadId": { "type": "string" },
          "language": { "type": "string", "default": "auto", "description": "Code ISO 639-1 ou \"auto\"" },
          "format": { "type": "string", "enum": ["txt", "srt"], "default": "txt" },
          "srtSplitMode": { "type": "string", "enum": ["phrase", "mot"], "default": "phrase", "description": "Uniquement si format=srt. \"phrase\" regroupe les mots en phrases complètes, une par entrée ; \"mot\" produit une entrée par mot, avec son propre timecode. Les deux conservent le texte tel quel (ponctuation et majuscules) : pour des sous-titres mis en forme et incrustés, voir /api/v1/subtitles." }
        },
        "required": ["uploadId"]
      },
      "CleanAudioRequest": {
        "type": "object",
        "properties": { "uploadId": { "type": "string" } },
        "required": ["uploadId"]
      },
      "ExtractAudioRequest": {
        "type": "object",
        "properties": {
          "uploadId": { "type": "string" },
          "format": { "type": "string", "enum": ["mp3", "wav", "aac"], "default": "mp3" }
        },
        "required": ["uploadId"]
      },
      "ConvertRequest": {
        "type": "object",
        "properties": {
          "uploadId": { "type": "string" },
          "format": { "type": "string", "enum": ["mp4", "webm", "mov", "avi", "mkv"], "default": "mp4" }
        },
        "required": ["uploadId"]
      },
      "MergeRequest": {
        "type": "object",
        "properties": {
          "uploadIds": { "type": "array", "items": { "type": "string" }, "minItems": 2 }
        },
        "required": ["uploadIds"]
      },
      "WatermarkRequest": {
        "type": "object",
        "properties": {
          "uploadId": { "type": "string" },
          "watermarkUploadId": { "type": "string", "description": "uploadId d'une image, obtenu via /api/v1/uploads" },
          "watermarkText": { "type": "string", "description": "Requis si watermarkUploadId absent" },
          "type": { "type": "string", "enum": ["fixe", "flottant"], "default": "fixe" },
          "opacity": { "type": "integer", "minimum": 10, "maximum": 100, "default": 80 },
          "interval": { "type": "integer", "enum": [3, 5, 8], "default": 5, "description": "Secondes entre deux positions, mode flottant uniquement" }
        },
        "required": ["uploadId"]
      },
      "TrimRequest": {
        "type": "object",
        "properties": {
          "uploadId": { "type": "string" },
          "start": { "type": "number", "minimum": 0 },
          "end": { "type": "number", "description": "Doit être > start" }
        },
        "required": ["uploadId", "start", "end"]
      },
      "TtsRequest": {
        "type": "object",
        "properties": {
          "text": { "type": "string" },
          "voice": { "type": "string", "enum": ["lea", "camille", "thomas", "gilles", "clone"], "default": "lea", "description": "\"clone\" nécessite une voix clonée enregistrée sur le compte" }
        },
        "required": ["text"]
      },
      "VoiceChangeRequest": {
        "type": "object",
        "properties": {
          "uploadId": { "type": "string" },
          "voice": { "type": "string", "enum": ["lea", "camille", "thomas", "gilles", "clone"], "default": "lea" },
          "language": { "type": "string", "default": "fr" }
        },
        "required": ["uploadId"]
      }
    }
  }
}
