Documentation de l’API de transcription
Envoyez un fichier audio ou vidéo, suivez son traitement, puis téléchargez le SRT. Base publique : https://mp4tosrt.fr/v1. Les appels sont authentifiés par une clé API côté serveur.
Créer une clé et authentifier vos appels
Connectez-vous au site, puis ouvrez « Mon abonnement » → « Accès API ». Le propriétaire d’un abonnement payant actif peut créer une clé (nom de 3 à 80 caractères), avec deux clés actives maximum par abonnement. Copiez la clé complète : elle n’est affichée qu’une fois. Révoquez-la depuis le même espace si nécessaire.
L’API est incluse dans tous les abonnements dès 6 €/mois pour 90 minutes ; elle utilise le même compteur de minutes que le site. L’essai gratuit est réservé au site. Voir les tarifs.
Pour les trois appels, envoyez l’en-tête suivant en HTTPS. Cette clé est fictive :
Authorization: Bearer m4s_live_demo.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAConservez la clé dans votre configuration serveur. Ne l’exposez pas au navigateur ni dans un dépôt public. Le serveur renvoie X-Request-Id pour identifier une requête.
Pour votre assistant ou générateur de client : spécification OpenAPI 3.1.
1. POST /v1/transcriptions — envoyer le média
URL : https://mp4tosrt.fr/v1/transcriptions
Authorization: Bearer …: obligatoire.Idempotency-Key: obligatoire, de 8 à 200 caractères. Utilisez une valeur distincte par fichier à traiter, conservée pour vos reprises.Content-Type: multipart/form-data; boundary=…: généré par votre client HTTP.Content-Length: obligatoire, strictement positif. Les envois avecTransfer-Encodingsont refusés ; utilisez un fichier local de taille connue.
Corps : un unique champ fichier file. Aucun autre champ multipart n’est lu pour configurer la transcription : ni langue, ni format, ni URL de rappel. Le type MIME doit commencer par audio/ ou video/ et l’extension doit être autorisée (voir les limites).
202 Accepted : nouveau job accepté, pas encore terminé. Exemple de réponse :
{
"id": "11111111-1111-4111-8111-111111111111",
"status": "queued",
"created_at": {},
"updated_at": {}
}200 OK : réutilisation d’un job existant pour la même clé API, la même clé d’idempotence et un fichier de taille et SHA-256 identiques. Le JSON décrit l’état actuel du job, avec les mêmes champs que la consultation ci-dessous. Un autre fichier avec la même clé d’idempotence produit 409 idempotency_conflict.
Une reprise repasse par les contrôles de débit, volume et capacité avant la recherche d’idempotence : elle peut donc être temporairement refusée. Si vous avez déjà l’identifiant, préférez consulter le job.
Les dates sont renvoyées telles quelles : sur un nouveau POST, les horodatages de création non encore résolus se sérialisent en {}. Ne supposez pas une chaîne ISO 8601.
Exemple curl (shell POSIX)
# Clé fictive : remplacez-la par votre clé côté serveur.
export MP4TOSRT_API_KEY='m4s_live_demo.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
# Gardez cette valeur pour retenter l’envoi du même fichier.
export MP4TOSRT_IDEMPOTENCY_KEY='video-exemple-20261102-001'
curl --fail-with-body https://mp4tosrt.fr/v1/transcriptions \
-H "Authorization: Bearer $MP4TOSRT_API_KEY" \
-H "Idempotency-Key: $MP4TOSRT_IDEMPOTENCY_KEY" \
-F 'file=@video.mp4;type=video/mp4'curl calcule la longueur et la boundary pour ce fichier local. Ne définissez pas manuellement le Content-Type multipart.
Erreurs possibles : 400, 401, 403, 409, 411, 413, 415, 429, 500, 503 ; les codes détaillés figurent dans le tableau des erreurs.
2. GET /v1/transcriptions/{id} — consulter le statut
URL : https://mp4tosrt.fr/v1/transcriptions/{id}. En-tête obligatoire : Authorization: Bearer …. Aucun corps ni Idempotency-Key requis.
200 OK : objet JSON contenant toujours id, status, created_at et updated_at. Les dates persistées sont des objets de type {"_seconds": 1793664000, "_nanoseconds": 0}.
Exemple de job terminé (identifiant et dates fictifs) :
{
"id": "11111111-1111-4111-8111-111111111111",
"status": "completed",
"created_at": {"_seconds": 1793664000, "_nanoseconds": 0},
"updated_at": {"_seconds": 1793664060, "_nanoseconds": 0},
"duration_seconds": 12.4,
"billable_seconds": 13,
"srt_url": "/v1/transcriptions/11111111-1111-4111-8111-111111111111/srt"
}Seul completed ajoute duration_seconds, billable_seconds (secondes réservées pour l’usage) et srt_url. Ce dernier est un chemin relatif, toujours protégé par la clé. Seul failed ajoute error_code.
queued: en attente.processing: traitement en cours.completed: SRT disponible.failed: traitement échoué ; lireerror_code.expired: résultat expiré ; le statut reste consultable.
export MP4TOSRT_JOB_ID='11111111-1111-4111-8111-111111111111'
curl --fail-with-body "https://mp4tosrt.fr/v1/transcriptions/$MP4TOSRT_JOB_ID" \
-H "Authorization: Bearer $MP4TOSRT_API_KEY"Erreurs : 401, 403, 404 job_not_found, 500 ; 503 si la configuration des clés est indisponible. Un job failed reste une réponse HTTP 200.
3. GET /v1/transcriptions/{id}/srt — télécharger
URL : https://mp4tosrt.fr/v1/transcriptions/{id}/srt. En-tête obligatoire : Authorization: Bearer …. Aucun corps ni Idempotency-Key requis.
200 OK : le contenu du fichier SRT, pas un JSON, envoyé en pièce jointe (Content-Disposition: attachment) avec le nom {id}.srt.
curl --fail-with-body "https://mp4tosrt.fr/v1/transcriptions/$MP4TOSRT_JOB_ID/srt" \
-H "Authorization: Bearer $MP4TOSRT_API_KEY" \
--output sous-titres.srtErreurs : 401, 403, 404 job_not_found, 409 job_not_ready (y compris un job échoué), 410 result_expired, 500 ; 503 si la configuration des clés est indisponible.
Exemples complets : Node et Python avec attente du statut
Ces deux exemples envoient video.mp4, interrogent le statut toutes les 5 secondes et enregistrent sous-titres.srt. La limite d’attente de 30 minutes est un choix des exemples, pas un délai garanti par le service. En cas de dépassement, conservez l’identifiant pour consulter le job plus tard.
Définissez MP4TOSRT_API_KEY dans l’environnement serveur. Les exemples curl ci-dessus montrent une clé fictive. Les exemples ci-dessous chargent le média en mémoire pour produire un multipart de longueur connue : prévoyez la mémoire nécessaire. Ils s’arrêtent sur une erreur HTTP ; en production, gérez notamment les réponses 429/503 avec Retry-After.
Node.js (fetch natif)
// Node.js 22+, fichier transcription.mjs. Clé fournie par l’environnement.
import { readFile, writeFile } from "node:fs/promises";
import { randomUUID } from "node:crypto";
const base = "https://mp4tosrt.fr";
const key = process.env.MP4TOSRT_API_KEY;
if (!key) throw new Error("MP4TOSRT_API_KEY manquante");
const headers = { Authorization: `Bearer ${key}` };
async function json(response) {
const data = await response.json();
if (!response.ok) throw new Error(JSON.stringify(data));
return data;
}
const form = new FormData();
form.append("file", new Blob([await readFile("video.mp4")],
{ type: "video/mp4" }), "video.mp4");
// Stockez cette valeur pour les reprises du même envoi.
const idempotencyKey = process.env.MP4TOSRT_IDEMPOTENCY_KEY || randomUUID();
let job = await json(await fetch(`${base}/v1/transcriptions`, {
method: "POST", headers: { ...headers, "Idempotency-Key": idempotencyKey },
body: form
}));
const statusUrl = `${base}/v1/transcriptions/${encodeURIComponent(job.id)}`;
const deadline = Date.now() + 30 * 60 * 1000; // Délai choisi par cet exemple.
while (job.status === "queued" || job.status === "processing") {
if (Date.now() > deadline) throw new Error(`Délai local dépassé : ${job.id}`);
await new Promise(resolve => setTimeout(resolve, 5000));
job = await json(await fetch(statusUrl, { headers }));
}
if (job.status !== "completed")
throw new Error(`${job.status}: ${job.error_code || "résultat expiré"}`);
const result = await fetch(new URL(job.srt_url, base), { headers });
if (!result.ok) throw new Error(await result.text());
await writeFile("sous-titres.srt", Buffer.from(await result.arrayBuffer()));
console.log("SRT enregistré : sous-titres.srt");Python (requests)
# Python 3, bibliothèque requests déjà disponible dans votre projet.
import os
import time
import uuid
from pathlib import Path
import requests
base = "https://mp4tosrt.fr"
headers = {"Authorization": "Bearer " + os.environ["MP4TOSRT_API_KEY"]}
# Conservez cette valeur pour une reprise du même envoi.
idempotency_key = os.environ.get("MP4TOSRT_IDEMPOTENCY_KEY") or str(uuid.uuid4())
def read_json(response):
if not response.ok:
raise RuntimeError(f"HTTP {response.status_code}: {response.text}")
return response.json()
with open("video.mp4", "rb") as media:
job = read_json(requests.post(
base + "/v1/transcriptions",
headers={**headers, "Idempotency-Key": idempotency_key},
files={"file": ("video.mp4", media, "video/mp4")}, timeout=1200))
status_url = base + "/v1/transcriptions/" + job["id"]
deadline = time.monotonic() + 30 * 60 # Délai choisi par cet exemple.
while job["status"] in ("queued", "processing"):
if time.monotonic() > deadline:
raise TimeoutError("Délai local dépassé : " + job["id"])
time.sleep(5)
job = read_json(requests.get(status_url, headers=headers, timeout=30))
if job["status"] != "completed":
raise RuntimeError(f'{job["status"]}: {job.get("error_code", "résultat expiré")}')
result = requests.get(base + job["srt_url"], headers=headers, timeout=120)
if not result.ok:
raise RuntimeError(f"HTTP {result.status_code}: {result.text}")
Path("sous-titres.srt").write_bytes(result.content)
print("SRT enregistré : sous-titres.srt")Limites et conservation
- Formats acceptés : mp3, mp4, m4a, wav, aac, ogg, webm, mov, mkv, avi, flac, wma ; type MIME audio ou vidéo obligatoire.
- Durée maximale : 120 minutes par média par défaut (limite serveur configurable). Un dépassement peut apparaître comme
failedavecmedia_too_longaprès l’acceptation. - Taille : le proxy public plafonne le corps multipart à 601 MB (notation Caddy), fichier et enveloppe compris. Le backend impose séparément
MAX_FILE_MB × 1 048 576octets au fichier (2 048 Mio par défaut) et un plafond du corps égal à cette valeur + 1 Mio. La plus petite limite s’applique ; ne considérez pas 2 Gio comme une taille publique garantie. - Clé créée depuis un abonnement : 10 tentatives d’envoi par minute et par clé ; 2 jobs actifs (
queuedouprocessing) par clé ; 2 147 483 648 octets par heure UTC et par clé, calculés sur Content-Length, enveloppe multipart comprise. Le volume est réservé avant réception du fichier. - Capacité du service : des plafonds globaux peuvent refuser un envoi même sous vos limites. Les valeurs serveur par défaut sont 4 jobs actifs, 4 uploads simultanés et 100 jobs dans la file ; elles sont configurables. Le worker traite 1 job à la fois par défaut (configurable).
- Conservation du SRT : 72 heures après la fin du traitement par défaut, durée configurable. Le nettoyage passe toutes les 15 minutes ; récupérez le résultat dès qu’il est prêt. Après expiration, le statut devient
expiredet le téléchargement renvoie HTTP 410. - Média source : sa suppression est tentée à la fin du traitement, succès ou échec. La conservation des métadonnées de job n’a pas de durée publique définie ici.
Les plafonds par clé ci-dessus concernent les clés d’abonnement ; d’autres clés administrées peuvent avoir des limites différentes. Les valeurs configurables décrivent le code, sans garantir les réglages du serveur déployé.
Erreurs HTTP et échecs de traitement
Les erreurs produites par le routeur API suivent ce JSON (exemple fictif) :
{
"error": {"code": "job_not_found", "message": "Job introuvable."},
"request_id": "22222222-2222-4222-8222-222222222222"
}Utilisez error.code pour votre logique et conservez request_id pour le diagnostic. Un refus du proxy (par exemple un corps dépassant 601 MB) peut être renvoyé avant le routeur et n’a pas cette forme JSON.
| HTTP | error.code | Situation |
|---|---|---|
| 400 | missing_idempotency_key | Idempotency-Key absent ou longueur hors de 8 à 200 caractères (POST). |
| 400 | unsupported_media | Champ file absent (POST). |
| 401 | invalid_api_key | Clé absente, mal formée, inconnue ou révoquée (tous les appels). |
| 403 | account_inactive | Compte API inactif ou abonnement non éligible (tous les appels). |
| 403 | organization_suspended | Organisation inactive pour une clé rattachée à une organisation (tous les appels). |
| 404 | job_not_found | Job absent ou appartenant à une autre clé (les deux GET). |
| 409 | idempotency_conflict | Même clé d’idempotence pour un fichier de taille ou d’empreinte différente (POST). |
| 409 | job_not_ready | SRT demandé alors que le job n’est ni completed ni expired (GET SRT). |
| 410 | result_expired | Résultat expiré (GET SRT). |
| 411 | missing_content_length | Content-Length absent, invalide, nul ou présence de Transfer-Encoding (POST). |
| 413 | file_too_large | Fichier trop volumineux ou corps multipart dépassant la limite (POST). |
| 415 | unsupported_media | Extension ou type MIME refusé (POST). |
| 429 | upload_rate_exceeded | Nombre d’envois par minute ou volume horaire dépassé (POST). |
| 429 | service_overloaded | Trop de jobs actifs pour cette clé (POST). |
| 503 | service_overloaded | Capacité globale, stockage temporaire ou uploads saturés (POST). |
| 503 | queue_full | File de traitement saturée (POST). |
| 503 | api_disabled | API désactivée : POST ; tous les appels si la configuration des clés manque. |
| 500 | internal_error | Erreur interne, résultat invalide ou autre erreur d’envoi non classée (tous les appels). |
Les refus de débit/capacité renvoient Retry-After : délai restant de la fenêtre minute, 3 600 secondes pour le volume horaire, 30 secondes pour les jobs/uploads, 60 secondes pour la file et 300 secondes pour le stockage ou le POST désactivé. Respectez ce délai avant de retenter.
Échecs asynchrones : error_code dans un job failed
La consultation reste HTTP 200 avec les champs communs et "status": "failed". Les codes exposés par le traitement sont :
account_inactive,organization_suspended: accès devenu inactif.billing_inactive,insufficient_seconds,operation_expired: usage non autorisé, minutes insuffisantes ou opération expirée.safety_limit_exceeded: plafond de sécurité d’une clé administrée atteint.unsupported_media,invalid_media_duration,media_too_long: média ou durée refusés.server_busy,alignment_too_large,acoustic_out_of_media,srt_out_of_media: capacité ou garde-fou du traitement.worker_interrupted: reprise impossible après interruption.internal_error: autre échec non exposé en détail.
Exemple de fin en échec, avec des dates fictives :
{
"id": "11111111-1111-4111-8111-111111111111",
"status": "failed",
"created_at": {"_seconds": 1793664000, "_nanoseconds": 0},
"updated_at": {"_seconds": 1793664060, "_nanoseconds": 0},
"error_code": "insufficient_seconds"
}Découvrir l’API de transcription en français · Choisir un abonnement