Referencia de la API
Endpoints, campos, respuestas, créditos, límites y errores.
Actualizado
Cómo funciona#
Cada transcripción es un trabajo. Envías una fuente, el trabajo se ejecuta en segundo plano y, cuando termina con éxito, apunta a una transcripción que puedes leer o exportar tantas veces como quieras. URL base: https://www.transcriptdock.com. Todas las solicitudes y respuestas son JSON.
# 1. Submitcurl -X POST https://www.transcriptdock.com/v1/jobs \-H "Authorization: Bearer td_live_YOUR_KEY" \-H "Idempotency-Key: video-7680721699171601694" \-H "Content-Type: application/json" \-d '{ "source": { "url": "https://www.tiktok.com/@tiktok/video/7680721699171601694" }, "mode": "captions_only" }'# 2. Wait (returns as soon as the job finishes, up to 25 s per call)curl "https://www.transcriptdock.com/v1/jobs/11111111-1111-4111-8111-111111111111?wait=25" \-H "Authorization: Bearer td_live_YOUR_KEY"# 3. Exportcurl "https://www.transcriptdock.com/v1/transcripts/22222222-2222-4222-8222-222222222222/export?format=srt" \-H "Authorization: Bearer td_live_YOUR_KEY"
Autenticación#
Crea claves en la página de claves de API. Una clave se muestra una sola vez, tiene el aspecto td_live_... y va en el encabezado Authorization de cada solicitud. Las claves llevan permisos que se eligen al crearlas: jobs:read, jobs:write, transcripts:read, uploads:write. Crea una clave por integración para poder revocarla por separado.
Authorization: Bearer td_live_YOUR_KEY
Idempotency-Key#
Obligatoria en POST /v1/jobs, /v1/batches, /v1/jobs/{id}/retry, /v1/video-discoveries y /v1/language-discoveries: cualquier cadena de 8 a 128 caracteres ASCII imprimibles. Enviar la misma clave con el mismo cuerpo dentro de 48 horas devuelve el objeto original, así que una solicitud reintentada nunca crea ni cobra dos veces. La misma clave con un cuerpo distinto devuelve 409 IDEMPOTENCY_CONFLICT. Una buena clave es tu propio id de lo que estás transcribiendo.
X-Request-Id#
Todas las respuestas incluyen uno. Cítalo cuando contactes a soporte.
Endpoints#
| Método | Ruta | Qué hace | Créditos |
|---|---|---|---|
| POST | /v1/jobs | Transcribe un video, archivo o enlace | 1 por transcripción de subtítulos, 2 por minuto de transcripción con IA |
| GET | /v1/jobs/{id} | Estado del trabajo, espera hasta 25 s | Gratis |
| GET | /v1/jobs | Historial de trabajos | Gratis |
| POST | /v1/jobs/{id}/cancel | Cancela un trabajo en cola | Gratis |
| POST | /v1/jobs/{id}/retry | Reintenta un trabajo fallido | Como un trabajo nuevo |
| POST | /v1/batches | Transcribe muchos videos (tamaño de lote del plan) | Por elemento, como arriba |
| GET | /v1/batches/{id} | Estado del lote | Gratis |
| GET | /v1/transcripts/{id} | JSON de la transcripción | Gratis |
| GET | /v1/transcripts/{id}/export | Archivo txt, srt, vtt o json | Gratis |
| DELETE | /v1/transcripts/{id} | Elimina una transcripción | Gratis |
| POST | /v1/uploads | Obtén una URL para subir un archivo | Gratis |
| POST | /v1/uploads/{id}/complete | Marca la subida como terminada | Gratis |
| POST | /v1/video-discoveries | Busca en YouTube o lista un canal, una lista de reproducción o un perfil de TikTok | 1 por página |
| GET | /v1/video-discoveries/{id} | Resultado de la búsqueda | Gratis |
| POST | /v1/language-discoveries | Lista los idiomas de subtítulos de un video de YouTube | 1 |
| GET | /v1/language-discoveries/{id} | Lista de idiomas | Gratis |
| POST | /v1/webhook-endpoints | Registra una URL de webhook (con sesión iniciada) | Gratis |
| GET | /v1/webhook-endpoints | Lista las URL de webhook | Gratis |
| DELETE | /v1/webhook-endpoints/{id} | Desactiva una URL de webhook (con sesión iniciada) | Gratis |
| GET | /v1/usage | Créditos y plan | Gratis |
| GET | /v1/capabilities | Lo que puede hacer tu clave | Gratis |
Crear un trabajo#
/v1/jobsDevuelve 202 con el trabajo. Si tu espacio de trabajo ya tiene una transcripción del mismo video y con las mismas opciones, devuelve 200 con un trabajo terminado, gratis (billing.kind: "cached").
{"id": "11111111-1111-4111-8111-111111111111","status": "queued","stage": "resolve","result_id": null,"error": null,"billing": { "credits_reserved": 2, "credits_charged": 0, "kind": "captions" },"source": { "platform": "tiktok", "media_id": "7680721699171601694", "canonical_url": "https://www.tiktok.com/@tiktok/video/7680721699171601694", "title": null, "upload_id": null },"options": { "mode": "auto", "language": null, "caption_preference": "prefer_creator" },"created_at": "2026-09-16T10:00:00.000Z"}
El objeto de trabajo#
Obtener un trabajo#
/v1/jobs/{id}?wait=25Devuelve el trabajo. Con wait (de 0 a 25 segundos) la solicitud queda abierta y responde en cuanto el trabajo llega a un estado final, así una sola llamada reemplaza un ciclo de sondeo. Las lecturas tienen su propio límite de 120 por minuto.
Listar trabajos#
/v1/jobs?limit=20&cursor=Del más reciente al más antiguo, con limit de 1 a 100. Pasa el next_cursor de la respuesta como cursor para obtener la página siguiente. Agrega platform y source_id (el source.media_id de un trabajo) juntos para ver solo los trabajos de un video, en cualquier modo. Así es como un cliente comprueba si el espacio de trabajo ya tiene una transcripción antes de volver a enviar.
Cancelar un trabajo#
/v1/jobs/{id}/cancelCancela un trabajo que todavía no empezó la transcripción con IA; se libera la retención. Después de ese punto devuelve 409 CANCELLATION_NOT_ALLOWED y el trabajo termina.
Reintentar un trabajo#
/v1/jobs/{id}/retryCrea un trabajo nuevo a partir de uno fallido cuyo error era retryable. Requiere un Idempotency-Key nuevo; cuerpo opcional { "max_credits": 40 }. Se cobra como un trabajo nuevo. Los fallos definitivos (video privado, sin subtítulos) devuelven 409 JOB_NOT_RETRYABLE: corrige la entrada y envía de nuevo.
La transcripción#
/v1/transcripts/{id}{"id": "22222222-2222-4222-8222-222222222222","source": { "platform": "youtube", "media_id": "dQw4w9WgXcQ", "canonical_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "title": "Never Gonna Give You Up" },"source_origin": "creator_captions","language": "en","text": "Never gonna give you up. Never gonna let you down.","segments": [{ "start": 0, "end": 2.4, "text": "Never gonna give you up" },{ "start": 2.4, "end": 4.9, "text": "Never gonna let you down" }],"words": null,"timing_granularity": "segment","duration_seconds": 212.0,"recognition": null,"extraction_version": "worker-0.1.0","created_at": "2026-09-16T10:00:14.000Z"}
Las transcripciones se conservan durante el período de retención de tu plan (7 días en la prueba, 30 en Starter y 90 en Pro y Scale). DELETE /v1/transcripts/{id} elimina una antes.
Exportar una transcripción#
/v1/transcripts/{id}/export?format=srt| format | Qué obtienes |
|---|---|
| txt | Texto plano, un segmento por línea. |
| srt | Subtítulos SubRip. |
| vtt | Subtítulos WebVTT. |
| json | El objeto de transcripción de arriba. |
Gratis e ilimitado. srt y vtt necesitan tiempos; una transcripción sin ellos devuelve 422 TIMESTAMPS_UNAVAILABLE.
Lotes#
/v1/batchesUna solicitud, muchos videos. Cada elemento tiene los mismos campos que Crear un trabajo; el lote completo se acepta o se rechaza en conjunto. Elementos por lote: 1 en la prueba, 10 en Starter, 25 en Pro, 50 en Scale.
curl -X POST https://www.transcriptdock.com/v1/batches \-H "Authorization: Bearer td_live_YOUR_KEY" \-H "Idempotency-Key: playlist-2026-09-16" \-H "Content-Type: application/json" \-d '{ "items": [{ "source": { "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" }, "mode": "captions_only" },{ "source": { "url": "https://www.tiktok.com/@tiktok/video/7680721699171601694" }, "mode": "captions_only" }] }'
El objeto de lote tiene status (queued, processing, succeeded, partial_success, failed, cancelled), conteos (total_items, succeeded_items, failed_items, cancelled_items) e items, cada uno con su job_id. Léelo con GET /v1/batches/{id}; cada elemento es un trabajo normal.
Subidas#
Tu propio audio o video (mp3, wav, m4a, ogg, aac, mp4, webm; hasta 250 MB y 2 horas). Reserva un espacio, haz PUT del archivo a la URL firmada, márcalo como completo y luego envíalo como trabajo con source.upload_id. Las subidas siempre usan transcripción con IA.
# 1. Reservecurl -X POST https://www.transcriptdock.com/v1/uploads \-H "Authorization: Bearer td_live_YOUR_KEY" \-H "Content-Type: application/json" \-d '{ "filename": "interview.mp3", "content_type": "audio/mpeg", "bytes": 48213920 }'# -> { "id": "33333333-...", "signed_upload_url": "https://...", "expires_at": "...", "status": "pending" }# 2. Upload the bytes (same Content-Type you declared)curl -X PUT "SIGNED_UPLOAD_URL" -H "Content-Type: audio/mpeg" --data-binary @interview.mp3# 3. Completecurl -X POST https://www.transcriptdock.com/v1/uploads/33333333-3333-4333-8333-333333333333/complete \-H "Authorization: Bearer td_live_YOUR_KEY"# 4. Transcribe itcurl -X POST https://www.transcriptdock.com/v1/jobs \-H "Authorization: Bearer td_live_YOUR_KEY" \-H "Idempotency-Key: interview-01" \-H "Content-Type: application/json" \-d '{ "source": { "upload_id": "33333333-3333-4333-8333-333333333333" }, "mode": "transcribe" }'
La URL firmada es válida por 24 horas; pasado ese tiempo, reserva un espacio nuevo.
Webhooks#
/v1/webhook-endpointsRegistra una URL https y pasa su id como webhook_endpoint_id al enviar. Te enviamos un POST con un evento cuando el trabajo o el lote termina. La respuesta incluye el secret de firma una sola vez. Registrar y desactivar endpoints requiere una sesión iniciada, así que hazlo en la página Webhooks del panel; una clave de API recibe 403 FORBIDDEN ahí, pero sí puede listar endpoints.
Eventos#
{"event_id": "44444444-4444-4444-8444-444444444444","type": "job.succeeded","created_at": "2026-09-16T10:00:14.000Z","job_id": "11111111-1111-4111-8111-111111111111","batch_id": null,"result_id": "22222222-2222-4222-8222-222222222222","metadata": { "order": "8812" },"error": null}
job.succeeded: obtén o exportaresult_id.job.failed:errortrae el código.batch.completed: todos los elementos llegaron a un estado final; lee el lote para ver los resultados de cada elemento.
Responde con cualquier 2xx en pocos segundos. Las entregas que fallan se reintentan con espera creciente y se pueden reenviar desde el panel. Un evento puede llegar más de una vez: elimina duplicados con event_id.
Verifica la firma#
Encabezados X-TranscriptDock-Event-Id, X-TranscriptDock-Timestamp y X-TranscriptDock-Signature. La firma es el HMAC-SHA256 (hex) de {event_id}.{timestamp}.{raw_body} con tu secreto. Rechaza todo lo que tenga más de cinco minutos.
import crypto from "node:crypto";export function verify(eventId: string, timestamp: string, rawBody: string, signature: string, secret: string): boolean {if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;const expected = crypto.createHmac("sha256", secret).update(`${eventId}.${timestamp}.${rawBody}`).digest("hex");return expected.length === signature.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));}
Encontrar videos#
/v1/video-discoveriesBusca en YouTube o lista un canal, una lista de reproducción o un perfil de TikTok, y luego pasa las URL a trabajos. Una búsqueda se ejecuta en segundo plano como un trabajo: léela con GET /v1/video-discoveries/{id} hasta que status sea succeeded. 1 crédito por página; los resultados se conservan 24 horas.
curl -X POST https://www.transcriptdock.com/v1/video-discoveries \-H "Authorization: Bearer td_live_YOUR_KEY" \-H "Idempotency-Key: search-async-rust-1" \-H "Content-Type: application/json" \-d '{ "kind": "youtube_search", "query": "async rust", "limit": 5 }'
El POST responde 202 con status: "queued". Un GET /v1/video-discoveries/{id} terminado se ve así:
{"id": "55555555-5555-4555-8555-555555555555","kind": "youtube_search","status": "succeeded","request": { "kind": "youtube_search", "query": "async rust", "limit": 5 },"videos": [{"platform": "youtube","video_id": "wXtngLBkK4Q","url": "https://www.youtube.com/watch?v=wXtngLBkK4Q","title": "Async Rust explained in 20 minutes","channel_id": "UCSp-OaMpsO8K0KkOqyBl7_w","channel_title": "Let's Get Rusty","duration_s": 1155,"published_at": null,"published_text": "5 months ago","view_count": 83635,"view_count_text": "83,635 views","thumbnail_url": "https://i.ytimg.com/vi/wXtngLBkK4Q/hq720.jpg","tiktok": null}],"profile": null,"next_cursor": "opaque-token","error": null,"created_at": "2026-09-16T12:00:00.000Z","updated_at": "2026-09-16T12:00:03.000Z","expires_at": "2026-09-17T12:00:00.000Z"}
Idiomas de subtítulos de un video#
/v1/language-discoveriesCuerpo { "source": { "url": "https://www.youtube.com/watch?v=..." } } con un Idempotency-Key (solo YouTube, 1 crédito). Lee GET /v1/language-discoveries/{id} para obtener la lista de pistas de subtítulos con sus códigos, si son del creador o automáticas, y cuál es la predeterminada. Usa los códigos en caption_languages.
Uso y capacidades#
/v1/usage{"plan": "pro","period_end": "2026-10-16T00:00:00.000Z","credits_remaining": 5840,"credits_total": 6000,"credits_reserved": 4,"prepaid_credits": 0,"transcribe_credits_per_minute": 2,"minimum_billable_seconds": 60,"subscription": {"status": "active","interval": "yearly","cancel_at_period_end": false,"current_period_end": "2027-09-16T00:00:00.000Z"}}
credits_remaining ya excluye credits_reserved (las retenciones de los trabajos en ejecución). prepaid_credits son créditos comprados, que nunca vencen.
subscription es la suscripción de Stripe detrás de un plan de pago y null si no hay ninguna: su status, el interval de facturación (monthly o yearly), cancel_at_period_end y current_period_end, que indica cuándo el plan se renueva o termina. period_end es cuando se renuevan los créditos mensuales, cada mes también en los planes anuales.
/v1/capabilitiesLo que tu clave puede hacer ahora mismo: por fuente (youtube, tiktok, instagram, upload, direct), qué modos están disponibles, además de los límites y los precios. Lee esto en lugar de dejarlo fijo en tu código; la prueba, por ejemplo, informa auto y transcribe como false.
{"youtube": { "enabled": true, "captions_only": true, "auto": true, "transcribe": true, "social_acquisition_fee": true },"tiktok": { "enabled": true, "captions_only": true, "auto": true, "transcribe": true, "social_acquisition_fee": true },"instagram": { "enabled": false, "captions_only": false, "auto": false, "transcribe": false, "social_acquisition_fee": true, "native_captions": "unverified_disabled" },"upload": { "enabled": true, "captions_only": false, "auto": true, "transcribe": true, "social_acquisition_fee": false },"direct": { "enabled": true, "captions_only": false, "auto": true, "transcribe": true, "social_acquisition_fee": false },"max_duration_ms": 7200000,"max_media_bytes": 262144000,"export_formats": ["txt", "json", "srt", "vtt"],"recognition_profiles": ["standard"],"transcribe_credits_per_minute": 2,"minimum_billable_seconds": 60,"price_version": "2026-09-14","asr_enabled": true,"instagram_enabled": false,"free_caption_credits": 50,"free_caption_credits_period": "month"}
Límites de frecuencia#
| Plan | Envíos / min | Trabajos en curso | Lecturas / min |
|---|---|---|---|
| Prueba | 10 | 10 | 120 |
| Starter | 60 | 100 | 120 |
| Pro | 120 | 500 | 120 |
| Scale | 300 | 2,000 | 120 |
Si superas un límite recibes 429 RATE_LIMITED con un encabezado retry-after (en segundos) y details.detail con el nombre del límite. No se cobra nada. Los webhooks y ?wait=25 te mantienen lejos del límite de lecturas.
Errores#
Todos los errores tienen la misma forma. retryable: true significa que la misma solicitud puede funcionar más tarde: espera retry-after segundos si viene en la respuesta; si no, reintenta con una espera creciente de unos segundos, y reutiliza el mismo Idempotency-Key para que nada se duplique. Cualquier otro error requiere un cambio de tu parte.
{"error": {"code": "INSUFFICIENT_BALANCE","message": "Not enough credits. Buy credits or upgrade your plan to continue.","retryable": false,"details": { "detail": "This job needs more credits than you have left. Buy credits or upgrade your plan." },"doc_url": "https://www.transcriptdock.com/docs/api-reference#error-codes"},"request_id": "66666666-6666-4666-8666-666666666666"}
Un trabajo que falla después de haberse aceptado sigue siendo 200 en GET /v1/jobs/{id}, con status: "failed" y el mismo objeto de error en error. No se cobra nada por un trabajo fallido.
Códigos#
| Código | HTTP | Reintento | Significado y qué hacer |
|---|---|---|---|
INVALID_REQUEST | 422 | No | A field is missing or has the wrong shape. details.detail names it. |
INVALID_URL | 422 | No | The URL is not a valid https link (max 2048 characters). |
UNSUPPORTED_SOURCE | 422 | No | The link is not a YouTube or TikTok video URL. |
UNSAFE_URL | 422 | No | The direct media link points at a private or blocked network address. |
INVALID_CURSOR | 422 | No | A video discovery cursor expired or is malformed. Start again from the first page. A bad job list cursor returns INVALID_REQUEST. |
CAPABILITY_UNAVAILABLE | 422 | No | This mode is not available for this source. GET /v1/capabilities shows what is. |
IDEMPOTENCY_CONFLICT | 409 | No | This Idempotency-Key was already used with a different body. Use a new key. |
UNAUTHENTICATED | 401 | No | No valid API key or OAuth token in the Authorization header. |
FORBIDDEN | 403 | No | The key lacks the scope for this endpoint, or the workspace is disabled. |
EMAIL_UNVERIFIED | 403 | No | Verify the account email before using the API. |
NOT_FOUND | 404 | No | No such object in this workspace. |
INSUFFICIENT_BALANCE | 402 | No | Not enough credits for this job. Buy credits or upgrade. |
PLAN_REQUIRED | 402 | No | This needs a paid plan (AI transcription, larger batches) or the trial credits are used up. |
BUDGET_EXCEEDED | 402 | No | The measured cost is above max_credits. Nothing was charged; raise the cap or skip the media. |
JOB_NOT_RETRYABLE | 409 | No | The failure was final (private video, no captions). Fix the input and submit a new job. |
CANCELLATION_NOT_ALLOWED | 409 | No | AI transcription already started; the job will finish. |
RATE_LIMITED | 429 | Sí | Over a rate or queue limit. Wait retry-after seconds; details.detail says which limit. |
SOURCE_NOT_FOUND | 422 | No | The video does not exist or was removed. |
SOURCE_PRIVATE | 422 | No | The video is private. Only public videos work. |
SOURCE_AUTH_REQUIRED | 422 | No | The video needs a login, age check or membership. |
SOURCE_REGION_RESTRICTED | 422 | No | The video is blocked in the regions we fetch from. |
NO_CAPTIONS | 422 | No | No captions on this video and mode was captions_only. Use auto or transcribe. |
LANGUAGE_UNAVAILABLE | 422 | No | None of caption_languages exists on this video. Drop the list to take the default track. |
LANGUAGE_UNSUPPORTED | 422 | No | AI transcription does not support the requested language. |
NO_SPEECH | 422 | No | AI transcription found no speech in the audio. |
NO_AUDIO | 422 | No | The media has no audio track. |
INVALID_MEDIA | 422 | No | The file could not be read as audio or video. |
UNSUPPORTED_MEDIA | 422 | No | Not an audio or video file type. |
DURATION_LIMIT_EXCEEDED | 413 | No | Longer than 2 hours. |
FILE_TOO_LARGE | 413 | No | Larger than 250 MB. |
TIMESTAMPS_UNAVAILABLE | 422 | No | This transcript has no timings, so srt and vtt exports are unavailable. Use txt or json. |
PROVIDER_REJECTED | 422 | No | The AI transcription model could not process this audio. |
SOURCE_RATE_LIMITED | 503 | Sí | The source platform is throttling us. The job retries automatically. |
SOURCE_BLOCKED | 503 | Sí | The source platform blocked the fetch. The job retries automatically. |
SOURCE_TIMEOUT | 503 | Sí | The source platform timed out. The job retries automatically. |
SOURCE_CHANGED | 503 | Sí | The source changed while we read it. The job retries automatically. |
PROVIDER_UNAVAILABLE | 503 | Sí | AI transcription is temporarily unavailable. The job retries automatically. |
STORAGE_UNAVAILABLE | 503 | Sí | File storage is temporarily unavailable. Retry the request. |
INTERNAL_ERROR | 500 | Sí | Our fault. Retry with the same Idempotency-Key; quote X-Request-Id if it persists. |