mp4tosrt.fr

Documentation de l’API de transcription

Par Adam Zeid · Mis à jour le 2 novembre 2026

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.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Conservez 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

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.

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.srt

Erreurs : 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

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.

HTTPerror.codeSituation
400missing_idempotency_keyIdempotency-Key absent ou longueur hors de 8 à 200 caractères (POST).
400unsupported_mediaChamp file absent (POST).
401invalid_api_keyClé absente, mal formée, inconnue ou révoquée (tous les appels).
403account_inactiveCompte API inactif ou abonnement non éligible (tous les appels).
403organization_suspendedOrganisation inactive pour une clé rattachée à une organisation (tous les appels).
404job_not_foundJob absent ou appartenant à une autre clé (les deux GET).
409idempotency_conflictMême clé d’idempotence pour un fichier de taille ou d’empreinte différente (POST).
409job_not_readySRT demandé alors que le job n’est ni completed ni expired (GET SRT).
410result_expiredRésultat expiré (GET SRT).
411missing_content_lengthContent-Length absent, invalide, nul ou présence de Transfer-Encoding (POST).
413file_too_largeFichier trop volumineux ou corps multipart dépassant la limite (POST).
415unsupported_mediaExtension ou type MIME refusé (POST).
429upload_rate_exceededNombre d’envois par minute ou volume horaire dépassé (POST).
429service_overloadedTrop de jobs actifs pour cette clé (POST).
503service_overloadedCapacité globale, stockage temporaire ou uploads saturés (POST).
503queue_fullFile de traitement saturée (POST).
503api_disabledAPI désactivée : POST ; tous les appels si la configuration des clés manque.
500internal_errorErreur 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 :

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