REST · JSON · v1

Documentation API

L’API Techtuel convertit un fichier audio ou vidéo en texte. Deux appels suffisent : vous soumettez une URL, puis vous récupérez la transcription. Tout est en JSON, sur HTTPS, avec une authentification par clé.

URL de base
https://api.techtuel.com/v1
Format
JSON · UTF-8
Authentification
Clé API (Bearer)

Authentification

Chaque requête doit inclure votre clé API dans l’en-tête Authorization. Les clés sont préfixées par txl_. Gardez-les secrètes : une clé donne accès à votre quota.

Header
Authorization: Bearer txl_your_secret_key

Démarrage rapide

Un seul appel. Envoyez une source — URL publique (YouTube, podcast, MP3) ou fichier téléversé via POST /uploads — et récupérez la transcription dans la même réponse. Pas d’identifiant de job, pas de boucle d’attente.

Transcrire
curl -X POST https://api.techtuel.com/v1/transcribe \
  -H "Authorization: Bearer txl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "source_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" }'

{ "status": "completed", "transcript": "..." }

Un long audio ne tient pas dans une requête HTTP. Si la transcription tourne encore au bout de 30 secondes (ajustable via ?wait=, jusqu’à 120), vous recevez un 202 et un id de job à la place — le job continue, et GET /transcriptions/{id} le récupère une fois terminé. Ajoutez-y ?format=srt pour télécharger les sous-titres. Chaque endpoint est détaillé ci-dessous.

Référence API

Tous les endpoints publics, générés depuis le schéma de l'API — ils correspondent donc toujours à ce que le service expose réellement.

Transcriptions

Soumettre un audio à transcrire et récupérer le résultat.

POST/v1/transcribe

Transcribe and wait for the result

Submit a source and receive the finished transcript in the SAME response — no job id, no polling loop. Returns **200** with `transcript` (and `segments`) once the transcription completes, which is immediate when the source is already in the shared cache. Long audio does not fit in one HTTP request. When the transcription is still running after the wait window (default 30s, `?wait=` up to 120s), the response is **202** with the job `id` and `status: "processing"` — the job keeps running, and GET /transcriptions/{id} retrieves it exactly as with the asynchronous API. Treat 202 as "not finished yet", never as a failure. A job that fails within the window returns **200** with `status: "failed"` and `error` set: the request succeeded, the transcription did not.

Fournissez exactement une source : source_url pour une URL résolue par le worker (audio/vidéo direct, page de podcast ou YouTube), ou upload_id pour un fichier envoyé via POST /uploads. audio_url est un alias historique de source_url ; préférez source_url dans du code neuf. Si une URL et un upload_id sont envoyés ensemble, upload_id l'emporte.

Paramètres de requête

ChampTypeDescription
waitintegerSeconds to wait before falling back to 202 (default 30, max 120)

Corps de la requête

ChampTypeDescription
audio_urlau choixstring
preferred_languagestringPreferredLanguage is the ISO 639-1 code the caller wants the transcript in. Omit it to get the language actually spoken in the audio. It is a PREFERENCE, not a guarantee: when the transcript cannot be translated the original is returned rather than an error, and the response's `language` field always states what was really produced.
source_urlau choixstring
upload_idau choixstring

Exactement une source est requise

curl -X POST https://api.techtuel.com/v1/transcribe \
  -H "Authorization: Bearer txl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "source_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "preferred_language": "fr"
  }'
Réponse · 200
{
  "api_key_id": "key_abc123",
  "created_at": "2026-07-21T09:30:00Z",
  "degraded_windows": 0,
  "detected_language": "it",
  "error": "source could not be resolved",
  "id": "job_abc123",
  "language": "fr",
  "minutes": 12.5,
  "segments": [
    {
      "start_seconds": 12.5,
      "text": "Bonjour et bienvenue dans cet"
    }
  ],
  "source_kind": "url",
  "source_url": "https://youtu.be/dQw4w9WgXcQ",
  "status": "completed",
  "title": "Le 6-9 de France Inter",
  "transcript": "Bonjour et bienvenue dans cet épisode…",
  "translated": true
}
400
Bad Request
401
Unauthorized
402
Refused: `reason` says which — `quota_exceeded`, `source_too_long` (longer than the plan's whole monthly allowance covers) or `payment_failed`
POST/v1/transcribe/file

Transcribe a local file in one call

Send the audio/video file itself and receive the finished transcript in the SAME response — no upload slot, no polling loop. This is the one-call form of the three-step upload flow (POST /uploads → PUT → POST /transcribe). Send `multipart/form-data` with the bytes in a `file` part; `Content-Type` of that part decides the accepted format. Limited to 100 MiB per request, well below what POST /uploads accepts (200 MiB audio, 1 GiB video): here the bytes travel through the API, whereas a presigned upload goes straight to storage. A larger file gets **413** naming the presigned flow — use it, it is not a downgrade but the shape that works for big files. Wait semantics are identical to POST /transcribe: **200** once the transcription completes, **202** with the job `id` when it is still running after the wait window (default 30s, `?wait=` up to 120s). Upload time counts against that window, so a large file is likelier to answer 202 — the job keeps running and GET /transcriptions/{id} retrieves it.

Paramètres de requête

ChampTypeDescription
waitintegerSeconds to wait before falling back to 202 (default 30, max 120)
Requête
curl -X POST https://api.techtuel.com/v1/transcribe/file \
  -H "Authorization: Bearer txl_your_api_key" \
  -F file=@/path/to/episode.mp3 \
  -F preferred_language=fr
Réponse · 200
{
  "api_key_id": "key_abc123",
  "created_at": "2026-07-21T09:30:00Z",
  "degraded_windows": 0,
  "detected_language": "it",
  "error": "source could not be resolved",
  "id": "job_abc123",
  "language": "fr",
  "minutes": 12.5,
  "segments": [
    {
      "start_seconds": 12.5,
      "text": "Bonjour et bienvenue dans cet"
    }
  ],
  "source_kind": "url",
  "source_url": "https://youtu.be/dQw4w9WgXcQ",
  "status": "completed",
  "title": "Le 6-9 de France Inter",
  "transcript": "Bonjour et bienvenue dans cet épisode…",
  "translated": true
}
400
Missing file part or unsupported format
401
Unauthorized
402
Refused: `reason` says which — `quota_exceeded` or `payment_failed`
413
File exceeds the inline limit — use POST /uploads
503
Uploads not configured
GET/v1/transcriptions

Lister les transcriptions

Retourne les jobs de transcription du compte authentifié, du plus récent au plus ancien, page par page. Utilisez ?page= (à partir de 1) et ?per_page= (20 par défaut, 100 au maximum) ; la réponse porte `total` et `has_more` pour permettre à un client d'enchaîner la page suivante. Les lignes n'incluent PAS le transcript complet — seulement `preview_segments`.

Paramètres de requête

ChampTypeDescription
api_key_idstringRestrict to jobs submitted by this API key id
pageinteger1-based page number
per_pageintegerJobs per page (1-100)
Requête
curl -X GET https://api.techtuel.com/v1/transcriptions \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 200
{
  "has_more": true,
  "jobs": [
    {
      "api_key_id": "key_abc123",
      "created_at": "2026-07-21T09:30:00Z",
      "error": "string",
      "has_transcript": true,
      "id": "job_abc123",
      "minutes": 12.5,
      "preview_segments": [],
      "source_kind": "url",
      "source_url": "https://youtu.be/dQw4w9WgXcQ",
      "status": "processing",
      "title": "Le 6-9 de France Inter"
    }
  ],
  "page": 1,
  "per_page": 10,
  "total": 47
}
401
Unauthorized
POST/v1/transcriptions

Créer une transcription

Soumet un audio à transcrire. Répond immédiatement avec un job en cours de traitement ; interrogez ensuite l'endpoint de récupération, ou appuyez-vous sur un webhook pour être notifié du résultat.

Fournissez exactement une source : source_url pour une URL résolue par le worker (audio/vidéo direct, page de podcast ou YouTube), ou upload_id pour un fichier envoyé via POST /uploads. audio_url est un alias historique de source_url ; préférez source_url dans du code neuf. Si une URL et un upload_id sont envoyés ensemble, upload_id l'emporte.

Corps de la requête

ChampTypeDescription
audio_urlau choixstring
preferred_languagestringPreferredLanguage is the ISO 639-1 code the caller wants the transcript in. Omit it to get the language actually spoken in the audio. It is a PREFERENCE, not a guarantee: when the transcript cannot be translated the original is returned rather than an error, and the response's `language` field always states what was really produced.
source_urlau choixstring
upload_idau choixstring

Exactement une source est requise

curl -X POST https://api.techtuel.com/v1/transcriptions \
  -H "Authorization: Bearer txl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "source_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "preferred_language": "fr"
  }'
Réponse · 202
{
  "api_key_id": "key_abc123",
  "created_at": "2026-07-21T09:30:00Z",
  "degraded_windows": 0,
  "detected_language": "it",
  "error": "source could not be resolved",
  "id": "job_abc123",
  "language": "fr",
  "minutes": 12.5,
  "segments": [
    {
      "start_seconds": 12.5,
      "text": "Bonjour et bienvenue dans cet"
    }
  ],
  "source_kind": "url",
  "source_url": "https://youtu.be/dQw4w9WgXcQ",
  "status": "completed",
  "title": "Le 6-9 de France Inter",
  "transcript": "Bonjour et bienvenue dans cet épisode…",
  "translated": true
}
400
Bad Request
401
Unauthorized
402
Refused: `reason` says which — `quota_exceeded` (the period's allowance is spent; it refills on renewal), `source_too_long` (the source is longer than the plan's whole monthly allowance can cover; only a larger plan helps, waiting does not), or `payment_failed` (the subscription is unpaid; update the payment method)
GET/v1/transcriptions/{id}

Récupérer une transcription

Récupère un job de transcription par son id. La réponse par défaut est l'enveloppe JSON du job (statut + texte assemblé). Passez ?format= pour télécharger le transcript dans un format horodaté une fois le job terminé : `txt` (texte brut), `json` (texte + segments horodatés), `srt` (sous-titres SubRip), `vtt` (sous-titres WebVTT).

Paramètres de chemin

ChampTypeDescription
idrequisstringJob id

Paramètres de requête

ChampTypeDescription
formatstringOutput formattxt · json · srt · vtt
Requête
curl -X GET https://api.techtuel.com/v1/transcriptions/job_abc123 \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 200
{
  "api_key_id": "key_abc123",
  "created_at": "2026-07-21T09:30:00Z",
  "degraded_windows": 0,
  "detected_language": "it",
  "error": "source could not be resolved",
  "id": "job_abc123",
  "language": "fr",
  "minutes": 12.5,
  "segments": [
    {
      "start_seconds": 12.5,
      "text": "Bonjour et bienvenue dans cet"
    }
  ],
  "source_kind": "url",
  "source_url": "https://youtu.be/dQw4w9WgXcQ",
  "status": "completed",
  "title": "Le 6-9 de France Inter",
  "transcript": "Bonjour et bienvenue dans cet épisode…",
  "translated": true
}
401
Unauthorized
404
Not Found
409
Conflict
422
Unprocessable Entity
DELETE/v1/transcriptions/{id}

Supprimer une transcription

Retire de l'historique de l'appelant une transcription terminée (réussie ou en échec). Le transcript lui-même reste dans le cache de sources partagé : resoumettre la même source plus tard reste servi sans re-transcrire. Un job encore en cours ne peut pas être supprimé — attendez qu'il se termine.

Paramètres de chemin

ChampTypeDescription
idrequisstringJob id
Requête
curl -X DELETE https://api.techtuel.com/v1/transcriptions/job_abc123 \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 204
"string"
404
Not Found
POST/v1/transcriptions/{id}/retry

Relancer une transcription en échec

Remet en file une transcription en échec sur le MÊME job, afin que l'historique conserve une ligne par source au lieu d'en accumuler une par tentative. Seuls les jobs en échec peuvent être relancés, et la relance est soumise au quota mensuel.

Paramètres de chemin

ChampTypeDescription
idrequisstringJob id
Requête
curl -X POST https://api.techtuel.com/v1/transcriptions/job_abc123/retry \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 202
{
  "api_key_id": "key_abc123",
  "created_at": "2026-07-21T09:30:00Z",
  "degraded_windows": 0,
  "detected_language": "it",
  "error": "source could not be resolved",
  "id": "job_abc123",
  "language": "fr",
  "minutes": 12.5,
  "segments": [
    {
      "start_seconds": 12.5,
      "text": "Bonjour et bienvenue dans cet"
    }
  ],
  "source_kind": "url",
  "source_url": "https://youtu.be/dQw4w9WgXcQ",
  "status": "completed",
  "title": "Le 6-9 de France Inter",
  "transcript": "Bonjour et bienvenue dans cet épisode…",
  "translated": true
}
402
Refused: `reason` is `quota_exceeded`, `source_too_long` or `payment_failed`
404
Not Found

Traductions

Traduire un transcript terminé dans une autre langue et relire les traductions.

POST/v1/transcriptions/{id}/translate

Translate a transcript into another language

Renders a completed transcript into `language` (ISO 639-1), preserving timings. Cached per language, so asking twice is free. Translating into the transcript's own language returns it unchanged. Costs no extra credit today.

Paramètres de chemin

ChampTypeDescription
idrequisstringJob id

Corps de la requête

ChampTypeDescription
languagestring
Requête
curl -X POST https://api.techtuel.com/v1/transcriptions/job_abc123/translate \
  -H "Authorization: Bearer txl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "language": "fr"
  }'
Réponse · 200
{
  "degraded_windows": 0,
  "detected_language": "en",
  "language": "fr",
  "segments": [
    {
      "start_seconds": 12.5,
      "text": "Bonjour et bienvenue dans cet"
    }
  ],
  "source_id": "string",
  "transcript": "string"
}
400
language is required or malformed
404
transcript not found
409
transcript is not ready to translate
422
this transcript cannot be translated
503
translation backend is not configured
GET/v1/transcriptions/{id}/translations

List a transcript's available translations

Returns the languages already produced for this transcript, without their content.

Paramètres de chemin

ChampTypeDescription
idrequisstringJob id
Requête
curl -X GET https://api.techtuel.com/v1/transcriptions/job_abc123/translations \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 200
{
  "languages": [
    "fr",
    "es"
  ]
}
404
transcript not found
GET/v1/transcriptions/{id}/translations/{lang}

Read one translation of a transcript

Returns the transcript rendered into `lang` when it exists, else 404. Produce it first with POST /transcriptions/{id}/translate.

Paramètres de chemin

ChampTypeDescription
idrequisstringJob id
langrequisstringISO 639-1 language code
Requête
curl -X GET https://api.techtuel.com/v1/transcriptions/job_abc123/translations/fr \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 200
{
  "degraded_windows": 0,
  "detected_language": "en",
  "language": "fr",
  "segments": [
    {
      "start_seconds": 12.5,
      "text": "Bonjour et bienvenue dans cet"
    }
  ],
  "source_id": "string",
  "transcript": "string"
}
404
transcript or translation not found

Extraction web

Transformer n'importe quelle page web publique en texte propre et structuré : titre, métadonnées et contenu lisible, sans navigation ni publicité.

POST/v1/extract

Extract clean, structured text from a web page

Fetches a public web page, strips navigation/ads/boilerplate, and returns its title, metadata and readable text — both flattened and split into positioned segments. Synchronous: the text comes back in this response, with no polling. The extraction is also recorded under an `id`, so it is listed by GET /v1/transcriptions and can be re-read or deleted like a transcription. Costs 1 credit; a page already in the global cache is free.

Corps de la requête

ChampTypeDescription
source_urlstring
urlstring
Requête
curl -X POST https://api.techtuel.com/v1/extract \
  -H "Authorization: Bearer txl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "source_url": "https://example.com/article",
    "url": "https://example.com/article"
  }'
Réponse · 200
{
  "author": "string",
  "cached": false,
  "char_count": 0,
  "credits_charged": 0,
  "description": "string",
  "extracted_at": "string",
  "id": "string",
  "image_url": "string",
  "segments": [
    {
      "char_length": 0,
      "char_offset": 0,
      "section_title": "string",
      "text": "string"
    }
  ],
  "site_name": "string",
  "source_id": "string",
  "source_type": "webpage",
  "text": "string",
  "title": "string",
  "url": "string"
}
400
invalid or disallowed url
402
Refused: `reason` says which — `quota_exceeded` or `payment_failed`
404
page not found
422
page yielded no readable content (paywall, JS-only app, or no prose)
502
page could not be fetched
503
site is rate limiting us; retry later

Envois de fichiers

Transcrire un fichier local : demander un emplacement, envoyer les octets, puis créer un job depuis l'id retourné.

POST/v1/uploads

Créer un emplacement d'envoi de fichier

Retourne une URL PUT présignée. Envoyez le fichier audio/vidéo directement sur `put_url` avec exactement les `size_bytes` déclarés, puis appelez POST /transcriptions avec l'`upload_id` retourné. `content_type` et `size_bytes` sont signés dans l'URL : le stockage rejette un envoi qui ne correspond pas. Les limites de taille dépendent du médium : 25 Mio pour l'audio, 200 Mio pour la vidéo.

Flux en trois étapes : appelez cet endpoint, envoyez les octets bruts en PUT sur le put_url retourné avec exactement les content_type et size_bytes déclarés (les deux sont signés dans l'URL — toute divergence est rejetée), puis appelez POST /transcriptions avec l'upload_id retourné.

Corps de la requête

ChampTypeDescription
content_typerequisstring
size_bytesrequisinteger
filenamestring
Requête
curl -X POST https://api.techtuel.com/v1/uploads \
  -H "Authorization: Bearer txl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "content_type": "audio/mpeg",
    "filename": "episode.mp3",
    "size_bytes": 18400000
  }'
Réponse · 201
{
  "expires_at": "2026-06-04T12:15:00Z",
  "put_url": "https://s3.fr-par.scw.cloud/...",
  "upload_id": "up_3f8c2a1e-9d41-4e7b-a12b-c45de6789012"
}
400
Missing/unsupported content_type or size_bytes
401
Unauthorized
413
File exceeds the size limit
503
Uploads not configured

Clés API

Gérer les clés `txl_` qui authentifient chaque appel.

GET/v1/keys

Lister les clés API

Liste les clés API de l'utilisateur authentifié. Le hash du jeton et le secret brut ne sont jamais retournés — uniquement les métadonnées (id, nom, préfixe, 4 derniers caractères, dates).

Requête
curl -X GET https://api.techtuel.com/v1/keys \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 200
{
  "keys": [
    {
      "created_at": "2026-06-05T10:00:00Z",
      "id": "key_abc123",
      "last4": "a1b2",
      "last_used_at": "2026-06-05T12:30:00Z",
      "name": "production server",
      "prefix": "txl_"
    }
  ]
}
401
Unauthorized
POST/v1/keys

Créer une clé API

Génère une nouvelle clé API pour l'utilisateur authentifié. Le jeton brut n'est retourné qu'une seule fois, dans le champ `token` — conservez-le immédiatement, il ne pourra plus jamais être récupéré. Accessible uniquement depuis une session web connectée (une clé API ne peut pas en générer une autre).

La clé complète n'est retournée qu'une seule fois, à la création. Elle est stockée hachée et ne peut pas être relue — conservez-la immédiatement.

Corps de la requête

ChampTypeDescription
namestring
Requête
curl -X POST https://api.techtuel.com/v1/keys \
  -H "Authorization: Bearer txl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "production server"
  }'
Réponse · 201
{
  "created_at": "2026-06-05T10:00:00Z",
  "id": "key_abc123",
  "last4": "a1b2",
  "last_used_at": "2026-06-05T12:30:00Z",
  "name": "production server",
  "prefix": "txl_",
  "token": "txl_a1b2c3d4e5f6..."
}
400
Bad Request
401
Unauthorized
403
Authenticated with an API key, not a session
DELETE/v1/keys/{id}

Révoquer une clé API

Révoque définitivement une des clés API de l'utilisateur authentifié. Idempotent : révoquer une clé inconnue ou déjà révoquée retourne quand même 204. Accessible uniquement depuis une session web connectée.

Paramètres de chemin

ChampTypeDescription
idrequisstringKey id
Requête
curl -X DELETE https://api.techtuel.com/v1/keys/key_abc123 \
  -H "Authorization: Bearer txl_your_api_key"
401
Unauthorized
403
Authenticated with an API key, not a session

Facturation & usage

Grilles tarifaires, cycle de vie de l'abonnement et quota restant.

POST/v1/billing/checkout

Démarrer un paiement (abonnement ou crédits)

Retourne l'URL d'une page de paiement hébergée pour le plan demandé. Redirigez l'utilisateur vers cette URL pour souscrire ou acheter des crédits.

Corps de la requête

ChampTypeDescription
cancel_urlstringCancelURL is where a buyer who gives up on the provider's page is sent. Optional; usually the same page as success_url, which then reads the session to tell the two apart.
emailstring
namestringName is the customer's display name, forwarded to the provider so the customer record and its invoices are not anonymous. Optional.
payment_methodstringPaymentMethod is one of the ids GET /billing/checkout/methods returned for this plan. Omitted, the provider's own selection screen decides.
planstring
success_urlstring
Requête
curl -X POST https://api.techtuel.com/v1/billing/checkout \
  -H "Authorization: Bearer txl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "cancel_url": "https://app.example.com/billing/return",
    "email": "dev@example.com",
    "name": "Ada Lovelace",
    "payment_method": "card",
    "plan": "pro",
    "success_url": "https://app.example.com/billing/return"
  }'
Réponse · 200
{
  "checkout_url": "string"
}
400
Bad Request
401
Unauthorized
409
Already subscribed
503
Unavailable: `reason` says which — `billing_not_configured` (nothing wired, normal in dev) or `billing_unavailable` (configured and broken)
GET/v1/billing/checkout/{session_id}

Read how a checkout ended

Reports the state of a checkout the caller opened, for the page the provider returns to. The redirect itself carries no outcome — a refused card lands on the same URL as a paid one — so poll this while `status` is `pending` or `redirected`, and open access on `paid`. Someone else's session reads as not found.

Paramètres de chemin

ChampTypeDescription
session_idrequisstringCheckout session id, from the `lungor_session_id` query parameter on the return URL
Requête
curl -X GET https://api.techtuel.com/v1/billing/checkout/6f1c2a0e-9b7d-4c3e-8a5f-2d1e0b9c8a7f \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 200
{
  "failure_reason": "insufficient_funds",
  "paid": false,
  "session_id": "6f1c…",
  "status": "failed",
  "subscription_status": "incomplete"
}
401
Unauthorized
404
Not Found
503
Service Unavailable
GET/v1/billing/checkout/methods

List how a plan may be paid for

Returns the payment methods this plan accepts, in the order to offer them. Ask before showing a choice: a method absent here is one the checkout refuses.

Paramètres de requête

ChampTypeDescription
planrequisstringPlan name (pro, max)
Requête
curl -X GET https://api.techtuel.com/v1/billing/checkout/methods?plan= \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 200
{
  "methods": [
    {
      "id": "card",
      "label": "Carte bancaire"
    }
  ]
}
400
Bad Request
401
Unauthorized
503
Unavailable: `reason` says which
POST/v1/billing/downgrade

Schedule a move to a smaller tier

Schedules a move DOWN to a smaller tier at the next renewal. Nothing changes today: the tier already paid for keeps its full allowance and rate limit until the end of the current period, and the smaller price is charged from the renewal that applies the smaller tier. Nothing is refunded and nothing is shortened. Refused when the target is larger than the current tier (use /billing/upgrade, which is immediate) or is the free tier (use DELETE /billing/subscription, which is a cancellation), and when the subscription is unpaid or already cancelled.

Corps de la requête

ChampTypeDescription
planstringPlan is the smaller tier to move to, by name.
Requête
curl -X POST https://api.techtuel.com/v1/billing/downgrade \
  -H "Authorization: Bearer txl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "plan": "pro"
  }'
Réponse · 200
{
  "effective_at": "2026-08-24T09:00:00Z",
  "pending_plan": "pro",
  "plan": "max"
}
400
Unknown or unsellable plan, or nothing to downgrade
401
Unauthorized
409
Not a downgrade, or the subscription cannot be downgraded (unpaid or cancelled)
503
Unavailable: `reason` says which — `billing_not_configured` (nothing wired, normal in dev) or `billing_unavailable` (configured and broken)
DELETE/v1/billing/pending-change

Withdraw a scheduled tier change

Cancels a downgrade that was scheduled but has not taken effect yet. The tier currently held stays in force and keeps being charged at its own price. Free, and idempotent: withdrawing when nothing is scheduled succeeds.

Requête
curl -X DELETE https://api.techtuel.com/v1/billing/pending-change \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 200
{
  "effective_at": "2026-08-24T09:00:00Z",
  "pending_plan": "pro",
  "plan": "max"
}
401
Unauthorized
502
Payment provider refused to restore the schedule
503
Unavailable: `reason` says which — `billing_not_configured` (nothing wired, normal in dev) or `billing_unavailable` (configured and broken)
GET/v1/billing/plans
Sans authentification

Lister les grilles tarifaires

Retourne les plans publics (Free, Pro, Max) avec leurs allocations mensuelles et leur prix, pour la page tarifs et le choix de montée en gamme. Un plan n'est souscriptible que si `purchasable` vaut true. Aucune authentification requise.

Requête
curl -X GET https://api.techtuel.com/v1/billing/plans \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 200
{
  "plans": [
    {
      "credits": 2000,
      "currency": "EUR",
      "name": "pro",
      "price_cents": 900,
      "purchasable": true,
      "rate_per_min": 120
    }
  ]
}
POST/v1/billing/repair

Restore the caller's free-tier subscription

Grants the free tier when Lungor holds no subscription for the caller, and reports whether it now does. Idempotent on the ledger: granting an owner Lungor already holds is a no-op there, so the button is safe to press twice. It deliberately does not skip the call for an owner recorded as granted locally — that record is exactly what a repair exists to disprove. Signup grants it best-effort, so an account created while Lungor was unreachable has no allowance and is refused every job. The balance reads repair it on their own, but only once the user hits one — this is the way out that does not depend on that.

Requête
curl -X POST https://api.techtuel.com/v1/billing/repair \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 200
{
  "granted": false
}
401
Unauthorized
500
Internal Server Error
503
Service Unavailable
DELETE/v1/billing/subscription

Résilier votre abonnement

Interrompt les prélèvements à venir. L'accès continue jusqu'à la fin de la période déjà payée — le mois entamé n'est ni remboursé, ni révoqué. Idempotent : résilier sans abonnement actif réussit quand même.

Requête
curl -X DELETE https://api.techtuel.com/v1/billing/subscription \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 200
{
  "access_until": "2026-08-24T09:00:00Z",
  "active": true,
  "status": "canceled"
}
401
Unauthorized
502
Payment provider refused the cancellation
503
Unavailable: `reason` says which — `billing_not_configured` (nothing wired, normal in dev) or `billing_unavailable` (configured and broken)
POST/v1/billing/upgrade

Move your subscription to another tier

Changes the tier of an active subscription. Requires explicit consent (`consented`), and charges the prorated difference for the remainder of the current period on the card already on file — call POST /billing/upgrade/quote first to obtain that amount and show it. The charge happens BEFORE the tier is applied: a declined card answers 402 and leaves the subscription untouched. Below the collection floor nothing is charged and the tier is granted anyway. The new allowance applies immediately; the new price applies from the next renewal, and the period already paid is neither re-charged in full nor refunded.

Corps de la requête

ChampTypeDescription
consent_versionstringConsentVersion identifies the wording the customer accepted, echoed back from the quote. The server re-renders that version rather than trusting any sentence from the client: the text IS the consent, so evidence of it must not be authored by the party it binds.
consentedbooleanConsented records that the customer was shown the amount and agreed to it. Required (FR2). An upgrade raises a recurring debit, and the checkout it mirrors already demands an explicit tick — the act that RAISES the amount must not be the one that asks for less. Absent or false is a 400, never a silent charge.
localestringLocale is which language the wording was displayed in ("fr" or "en"), so the archived sentence is the one that was actually on screen. Empty defaults to French — the authoritative text.
planstringPlan is the tier to move to, by name.
quoted_centsintegerQuotedCents is the amount the customer was shown, echoed back from the quote. It is what turns "they clicked" into "they agreed to THIS figure": a stale page quoting a larger period would otherwise consent to one amount and be charged another.
Requête
curl -X POST https://api.techtuel.com/v1/billing/upgrade \
  -H "Authorization: Bearer txl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "consent_version": "upgrade-v1",
    "consented": true,
    "locale": "fr",
    "plan": "max",
    "quoted_cents": 350
  }'
Réponse · 200
{
  "allowance_from": "now",
  "charged_from": "2026-08-24T09:00:00Z",
  "charged_now_cents": 350,
  "plan": "max"
}
400
Unknown or unsellable plan, nothing to upgrade, or consent missing
401
Unauthorized
402
The card was declined. Nothing changed: the tier is applied only once the payment lands
409
Subscription cannot be upgraded (unpaid or cancelled), or the plan is a downgrade — this endpoint only moves UP, since a smaller tier applied today would cut an allowance already paid for
503
Unavailable: `reason` says which — `billing_not_configured` (nothing wired, normal in dev) or `billing_unavailable` (configured and broken)
POST/v1/billing/upgrade/quote

Quote what an upgrade will cost today

Returns the amount charged immediately for moving to a larger tier (the price difference, prorated over the days left in the period already paid), and the recurring price from the next renewal. Charges nothing — call POST /billing/upgrade to commit. A charge of 0 means the proration fell under the collection floor and the tier is granted with no payment today.

Corps de la requête

ChampTypeDescription
consent_versionstringConsentVersion identifies the wording the customer accepted, echoed back from the quote. The server re-renders that version rather than trusting any sentence from the client: the text IS the consent, so evidence of it must not be authored by the party it binds.
consentedbooleanConsented records that the customer was shown the amount and agreed to it. Required (FR2). An upgrade raises a recurring debit, and the checkout it mirrors already demands an explicit tick — the act that RAISES the amount must not be the one that asks for less. Absent or false is a 400, never a silent charge.
localestringLocale is which language the wording was displayed in ("fr" or "en"), so the archived sentence is the one that was actually on screen. Empty defaults to French — the authoritative text.
planstringPlan is the tier to move to, by name.
quoted_centsintegerQuotedCents is the amount the customer was shown, echoed back from the quote. It is what turns "they clicked" into "they agreed to THIS figure": a stale page quoting a larger period would otherwise consent to one amount and be charged another.
Requête
curl -X POST https://api.techtuel.com/v1/billing/upgrade/quote \
  -H "Authorization: Bearer txl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "consent_version": "upgrade-v1",
    "consented": true,
    "locale": "fr",
    "plan": "max",
    "quoted_cents": 350
  }'
Réponse · 200
{
  "charge_cents": 350,
  "charged_from": "2026-08-24T09:00:00Z",
  "consent_required": true,
  "consent_text": "J'accepte le prélèvement de 8 € aujourd'hui, puis 17 € par mois jusqu'à résiliation.",
  "consent_version": "upgrade-v1",
  "currency": "EUR",
  "pending_plan_cancelled": "pro",
  "plan": "max",
  "prorated_days": 15,
  "recurring_cents": 1200
}
400
Unknown or unsellable plan
401
Unauthorized
409
Subscription cannot be upgraded (unpaid or cancelled), or the plan is a downgrade — schedule it with /billing/downgrade
503
Service Unavailable
POST/v1/billing/webhook/lungor
Sans authentification

Lungor subscription webhook

Ingests Lungor's subscription lifecycle events. Authenticated by HMAC signature, not by API key. Not called by clients.

Requête
curl -X POST https://api.techtuel.com/v1/billing/webhook/lungor \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 200
"string"
401
invalid signature
GET/v1/invoices

List the caller's invoices

Returns the authenticated customer's own invoices, newest first.

Requête
curl -X GET https://api.techtuel.com/v1/invoices \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 200
[
  {
    "amount": 1900,
    "currency": "EUR",
    "customer_email": "client@example.com",
    "customer_name": "Client Test",
    "id": "3f8c2a1e-...",
    "issued_at": "2026-07-01T00:00:00Z",
    "number": "2026-0001",
    "status": "paid"
  }
]
401
Unauthorized
503
Invoicing is not configured
GET/v1/invoices/{id}

Get one of the caller's invoices

Paramètres de chemin

ChampTypeDescription
idrequisstringInvoice ID
Requête
curl -X GET https://api.techtuel.com/v1/invoices/3f8c2a1e-7b4d-4e2a-9c11-8a6f5d3b2c10 \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 200
{
  "currency": "EUR",
  "id": "string",
  "issued_at": "string",
  "lines": [
    {
      "amount": 1900,
      "description": "Abonnement Techtuel Pro",
      "kind": "flat",
      "quantity": 1,
      "unit": "credit",
      "unit_amount": 1900
    }
  ],
  "number": "2026-0001",
  "paid_at": "string",
  "pdf_url": "/v1/invoices/3f8c.../pdf",
  "period_end": "string",
  "period_start": "string",
  "status": "paid",
  "subtotal": 1900,
  "tax_amount": 0,
  "tax_rate": 0,
  "total": 1900
}
401
Unauthorized
404
Not Found
GET/v1/invoices/{id}/pdf

Download an invoice as a Factur-X PDF

Paramètres de chemin

ChampTypeDescription
idrequisstringInvoice ID
Requête
curl -X GET https://api.techtuel.com/v1/invoices/3f8c2a1e-7b4d-4e2a-9c11-8a6f5d3b2c10/pdf \
  -H "Authorization: Bearer txl_your_api_key"
401
Unauthorized
404
Not Found
GET/v1/usage

Consulter l'usage et le quota

Retourne le statut d'abonnement de l'appelant et sa consommation de transcription sur le mois en cours.

Requête
curl -X GET https://api.techtuel.com/v1/usage \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 200
{
  "access_ends_at": "2026-08-03T00:00:00Z",
  "active": true,
  "billing_state": "healthy",
  "cancel_at_period_end": false,
  "credits_limit": 2000,
  "credits_used": 84,
  "currency": "EUR",
  "next_charge_cents": 1200,
  "pending_plan": "pro",
  "pending_plan_at": "2026-08-24T09:00:00Z",
  "period_start": "2026-07-03T00:00:00Z",
  "plan": "pro",
  "rate_per_min": 120,
  "renews_at": "2026-08-18T00:00:00Z",
  "status": "active"
}
401
Unauthorized
503
Unavailable: `reason` says which — `billing_not_configured` (nothing wired, normal in dev) or `billing_unavailable` (configured and broken)

Compte

Opérations au niveau du compte.

DELETE/v1/account

Supprimer votre compte

Résilie l'abonnement de l'appelant auprès du prestataire de paiement et purge ses données transcript-api (clés API, jobs, usage). Irréversible.

Requête
curl -X DELETE https://api.techtuel.com/v1/account \
  -H "Authorization: Bearer txl_your_api_key"
401
Unauthorized
500
Internal Server Error
502
Subscription cancellation failed; nothing was deleted
GET/v1/account/export
Sans authentification

Export all your transcriptions

Returns every transcription the account owns, in one JSON document, with the full text and timed segments of each. This is the GDPR portability export: unlike GET /transcriptions it is neither paginated nor projected — completeness is the point. Session-only: an export is the whole account history in one file, so a raw API key — which travels in scripts and CI — cannot trigger one.

Requête
curl -X GET https://api.techtuel.com/v1/account/export \
  -H "Authorization: Bearer txl_your_api_key"
Réponse · 200
{
  "count": 57,
  "exported_at": "2026-07-31T09:30:00Z",
  "transcriptions": [
    {
      "created_at": "2026-07-21T09:30:00Z",
      "detected_language": "en",
      "error": "source could not be resolved",
      "id": "job_abc123",
      "language": "fr",
      "minutes": 12.5,
      "segments": [],
      "source_kind": "url",
      "source_url": "https://youtu.be/dQw4w9WgXcQ",
      "status": "completed",
      "title": "Le 6-9 de France Inter",
      "transcript": "Bonjour et bienvenue dans cet épisode…",
      "translated": true
    }
  ]
}
401
Unauthorized
500
Internal Server Error

Erreurs

Toutes les erreurs renvoient le même format, avec un message lisible. Aucune surprise : le code HTTP et le message disent exactement ce qui s’est passé.

Format d’erreur
{ "message": "source_url or upload_id required" }
CodeSignification
400Requête invalide. Ni source_url ni upload_id fourni, ou URL mal formée, non http(s), ou pointant vers une adresse interdite.
401Non authentifié. Clé API absente, mal formée ou révoquée.
402Crédits insuffisants. Solde épuisé ou quota mensuel dépassé. Consultez GET /usage.
403Interdit. Opération réservée à une session connectée : la création et la révocation de clés API ne sont pas accessibles via une clé API.
404Introuvable. Aucun job ne correspond à cet identifiant.
409Job non terminé. Un format horodaté a été demandé sur une transcription qui n’est pas encore completed.
422Format indisponible. Aucun segment horodaté n’existe pour ce transcript : srt et vtt sont impossibles, utilisez txt ou json.
429Trop de requêtes. Limite de débit par clé dépassée — le plafond dépend de votre offre (voir Limites). L’en-tête Retry-After indique le délai d’attente.
500Erreur interne. Incident côté serveur. La requête peut être rejouée.
503Service indisponible. Une dépendance est momentanément inaccessible (facturation, stockage). Réessayez avec un backoff.

Limites

Dépasser l’une de ces limites ne renvoie pas toujours une erreur HTTP : une source trop longue est acceptée puis le job termine en failed, avec la raison dans le champ error et les crédits réservés intégralement remboursés.

Durée maximale par transcription120 minutes
Quota mensuel (crédits)Gratuit 100 · Pro 2 000 · Max 10 000
Coût en crédits2 / minute d’audio · 1 par vidéo sous-titrée
Débit par clé APIGratuit 12 req/min · Pro et Max 120 req/min
Taille maximale d’un fichier audio téléversé200 Mio — soit les 2 h complètes jusqu’à 232 kbit/s
Taille maximale d’une vidéo téléversée1 Gio — environ 30 min en 1080p (4 Mbit/s)
Vidéo plus longue que 30 minPar URL — seule la durée maximale s’applique
Validité d’une URL de téléversement30 minutes
Conservation d’un fichier téléversé7 jours
Jobs renvoyés par GET /transcriptions100 max
Formats audio téléversablesmp3 m4a wav ogg opus aac flac webm
Formats vidéo téléversablesmp4 mpeg webm mov mkv
Sources non supportéesflux en direct