DocumentaciónDocumentación de la API
Documentación de la API
Consulta los parámetros y ejemplos de cada endpoint /v1. También puedes descargar la especificación OpenAPI en JSON o YAML.
Introducción
https://api.transcript1.com/v1Usa https://api.transcript1.com/v1 como URL base para llamar a la API.
- El cuerpo de los requests y de las respuestas es JSON. Los nombres de campo usan
snake_case. - Las fechas y horas siguen ISO 8601 en UTC: por ejemplo,
2026-10-13T09:30:00.000Z. - Las duraciones y las marcas de tiempo de la transcripción se expresan en segundos.
- Los errores comparten una estructura; aprende a leerla en Errores.
- Las respuestas a requests autenticados llevan los headers
X-RateLimit-LimityX-RateLimit-Remaining. Revisa Facturación y límites. - Con
source=auto, se buscan primero subtítulos en el idioma original en YouTube, X, TikTok, Bluesky, Dailymotion y LinkedIn. Si no se pueden usar, se transcribe el audio con IA. No todos los videos tienen subtítulos. El modosource=captions(solo subtítulos) nunca descarga audio ni pasa a STT; si falta una pista válida, falla conno_captions. Para transcribir directamente el audio, usasource=stto activa la identificación de hablantes. En Fuentes y opciones puedes revisar las restricciones de cada plataforma y cuáles incluyen marcas de tiempo por segmento o por palabra.
Para herramientas e integraciones, descarga la especificación OpenAPI en el formato que necesites:
Autenticación
curl https://api.transcript1.com/v1/account \
-H "Authorization: Bearer $TRANSCRIPT1_API_KEY"Autentica cada request con el header Authorization, usando Authorization: Bearer <token>.
- Las claves de API empiezan con
t1_y dan acceso a todos los endpoints. Crea la tuya en “API keys”. - Los tokens de acceso OAuth empiezan con
t1o_. Claude, ChatGPT y otros clientes MCP reciben uno al conectarse. Sus permisos sontranscriptions:readytranscriptions:write.
| Permiso (scope) | Endpoints disponibles |
|---|---|
transcriptions:read | GET /v1/transcriptions, GET /v1/transcriptions/:id, GET /v1/transcriptions/:id/upload y GET /v1/account |
transcriptions:write | POST /v1/transcriptions, DELETE /v1/transcriptions/:id y POST /v1/transcriptions/:id/upload/complete |
- Si al token le falta el permiso necesario, recibirás
403 insufficient_scope. - Si no envías una credencial o no es válida, recibirás
401 unauthorized.
Los requests para enviar partes de un archivo usan otra credencial: autentica PUT /v1/transcriptions/:id/upload/chunks/:index con X-Upload-Token. Ese token se entrega al crear la subida. Consulta Cómo subir archivos.
Idempotencia
curl -X POST https://api.transcript1.com/v1/transcriptions \
-H "Authorization: Bearer $TRANSCRIPT1_API_KEY" \
-H "Idempotency-Key: 5f1c2b0e-8d4a-4c3e-9b7a-2e6f1d0c9a8b" \
-H "Content-Type: application/json" \
-d '{"url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"}'Agrega el header Idempotency-Key a POST /v1/transcriptions para poder repetir el request sin crear otra transcripción.
Elige un valor de 1 a 255 caracteres. Debe ir en el header, no en los parámetros de la URL.
- Si repites la clave y los parámetros, recuperas la transcripción original sin pagar de nuevo.
- Si conservas la clave pero cambias los parámetros, recibirás
409 idempotency_conflict. - Para una subida por partes, repetir el request de creación con la misma clave devuelve un nuevo token de subida.
Errores
{
"error": {
"code": "insufficient_minutes",
"message": "Not enough minutes",
"retryable": false,
"action_url": "https://transcript1.com/dashboard/billing",
"minutes_needed": 20,
"minutes_remaining": 3
}
}- Si el error viene del request, la API no aceptó la llamada.
- Si falla la transcripción, la API sí aceptó el request, pero no pudo terminar de transcribir. El objeto queda con
statusenfailed.
Cómo leer un error
Cuando /v1 rechaza un request, devuelve un objeto error con los campos code, message y retryable.
- Usa
codepara decidir qué debe hacer tu aplicación. - Muestra la explicación de
messageal usuario, pero no bases tu lógica en ese texto: puede cambiar. - Si
retryableestrue, puedes volver a enviar el mismo request más adelante.
Según el error, encontrarás otros campos junto a retryable:
| Campo | Aparece en | Qué indica |
|---|---|---|
action_url | insufficient_minutes | Enlace para contratar un plan o comprar minutos. |
minutes_needed, minutes_remaining | insufficient_minutes | Minutos necesarios para el request y saldo disponible. El primero es null si aún no se sabe cuántos minutos necesita. |
supported_platforms | unsupported_platform | Valores de plataforma admitidos: ["youtube", "x", "tiktok", "instagram", "facebook", "bluesky", "twitch", "dailymotion", "pinterest", "linkedin", "snapchat"]. |
missing_chunks | upload_incomplete | Índices de las partes del archivo que faltan por recibir. |
max_bytes | media_too_large | Tamaño máximo de archivo que puedes enviar, en bytes. |
params | invalid_request | Objeto que detalla el problema de cada campo al validar el cuerpo o los parámetros de la URL. |
Cuando la API rechaza el request
| Código HTTP | code | ¿Reintentar? | Qué ocurrió |
|---|---|---|---|
| 400, 422 | invalid_request | No | Falta un campo o parámetro, su valor no es válido o el cuerpo no es JSON válido. También ocurre al combinar diarize con source: "captions", o reuse_recent con webhook_url. |
| 422 | invalid_url | No | El enlace no se admite o no corresponde a platform. Usa YouTube, X, TikTok, Instagram, Facebook, Bluesky u otra plataforma de Fuentes y opciones. |
| 422 | unsupported_platform | No | El valor de platform no está entre los admitidos. |
| 422 | unsupported_media | No | La extensión del archivo no está admitida. |
| 401 | unauthorized | No | Falta la credencial, no es válida, venció o se revocó. También puede indicar que el token de subida no es válido o venció. |
| 403 | insufficient_scope | No | El token OAuth no tiene el permiso transcriptions:read o transcriptions:write necesario. |
| 403 | email_not_verified | No | Debes verificar tu correo antes de crear una transcripción. |
| 402 | insufficient_minutes | No | Tu saldo disponible es 0 o no alcanza para esta transcripción. |
| 402 | no_active_plan | No | No puedes comprar una recarga sin un plan de pago activo. Este error viene del endpoint de facturación del panel, no de los endpoints de transcripción. |
| 404 | not_found | No | No existe esa transcripción o es de otro equipo. |
| 409 | upload_incomplete | No | Faltan partes del archivo. Envíalas y vuelve a confirmar la subida. |
| 409 | invalid_state | No | La transcripción ya no espera una subida. No puedes enviar más partes ni confirmar que terminaste. |
| 409 | idempotency_conflict | No | Usaste antes el mismo Idempotency-Key con parámetros diferentes. |
| 413 | media_too_large | No | El archivo pesa más de lo permitido. Revisa Cómo subir archivos. |
| 413 | invalid_request | No | El cuerpo JSON supera 1 MB. Para archivos demasiado grandes, el código es media_too_large. |
| 429 | rate_limited | Sí | Superaste los requests por minuto. Espera el tiempo en segundos que indica Retry-After. |
| 429 | too_many_active_jobs | Sí | Llegaste al límite de transcripciones en curso o de subidas abiertas. Espera los segundos indicados en Retry-After. |
| 500 | internal_error | Sí | Falló algo en transcript1. Espera un momento y vuelve a intentarlo. |
| 503 | service_unavailable | Sí | El servicio no está disponible por el momento. Prueba de nuevo más tarde. |
Si el navegador se autentica con una cookie de sesión, también se comprueba el origen. Un request desde otro sitio devuelve 403 forbidden_origin.
Esta comprobación no afecta a las llamadas que usan el header Authorization.
El header Retry-After aparece en todas las respuestas 429 e indica cuántos segundos debes esperar. Puedes consultar los límites de tu plan en Facturación y límites.
Cuándo volver a intentarlo
- Reenvía el request solo si
retryableestrue:rate_limited,too_many_active_jobs,internal_erroroservice_unavailable. - Si hay
Retry-After, respétalo. Si no, aumenta la espera entre intentos. - Si repites
POST /v1/transcriptions, conserva el mismo headerIdempotency-Key. Así no pagas dos veces si el primer request sí se completó. - Para los demás códigos, primero corrige lo que causó el error.
Cuando falla la transcripción
Si status es failed, encontrarás el motivo en el objeto error, con code y message.
La transcripción puede fallar después de que la API haya respondido al request de creación. Comprueba error al consultar el estado o al recibir el webhook transcription.failed.
error.code | Qué ocurrió | ¿Qué puedes hacer? |
|---|---|---|
media_unavailable | El video no existe o dejó de estar disponible. | No repitas el mismo envío; revisa la URL. |
media_private | No se puede acceder porque el video es privado. | Usa una publicación pública o, en YouTube, un video público o no listado. |
login_required | Hay que iniciar sesión en la plataforma para ver el video. | Es un fallo permanente; no se reintenta. |
no_audio | El video no contiene una pista de audio. | Es un fallo permanente; no se reintenta. |
age_restricted | La plataforma restringe el video por edad. | No repitas el enlace; sube el archivo. |
live_stream | La transmisión sigue en vivo o todavía no empieza. | Espera a que termine para volver a enviarla. |
no_captions | Pediste captions en source, pero no hay una pista de subtítulos que se pueda usar. | Cambia a source: "auto" o "stt". |
media_too_long | La grabación supera las 5 horas que admite voz a texto. | Divide la grabación antes de enviarla. |
unsupported_media | No se puede leer el archivo o le falta una pista de audio. | Envía un archivo en un formato admitido. |
no_speech | No se detectó habla. | No lo reintentes. |
unsupported_language | El idioma de la grabación no está admitido. | Configura language con un código admitido o "auto" antes de volver a enviarla. |
insufficient_minutes | El saldo se agotó antes de cobrar la transcripción. | Recarga minutos y vuelve a intentarlo. |
fetch_failed | No se pudo descargar la grabación después de varios intentos. | Puedes probar de nuevo más tarde. |
transcription_failed | La transcripción de voz a texto falló después de varios intentos. | Puedes probar de nuevo más tarde. |
Si la transcripción falla o la cancelas, no pagas nada. Si ya se habían descontado minutos, vuelven a tu saldo.
Objeto Transcription
{
"id": "tr_4fHq8ZbN2xLm0PqRsTuVwX",
"object": "transcription",
"status": "completed",
"source": {
"platform": "youtube",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"media_id": "dQw4w9WgXcQ"
},
"media": {
"title": "Example video",
"author": "Example channel",
"author_url": "https://www.youtube.com/@examplechannel",
"thumbnail_url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
"duration_seconds": 212
},
"options": {
"source": "auto",
"language": "auto",
"diarize": false
},
"transcript_source": "youtube_captions",
"caption_kind": "auto_generated",
"language": "en",
"billed_minutes": 4,
"cached": false,
"error": null,
"created_at": "2026-10-13T09:30:00.000Z",
"completed_at": "2026-10-13T09:30:04.000Z",
"deleted_at": null,
"transcript": {
"text": "Welcome back to the channel. Today we are looking at how transcripts are made.",
"speakers": [],
"segments": [
{
"start": 0,
"end": 4.2,
"speaker": null,
"language": "en",
"text": "Welcome back to the channel."
},
{
"start": 4.2,
"end": 9.8,
"speaker": null,
"language": "en",
"text": "Today we are looking at how transcripts are made."
}
]
}
}Campos
- idstringobligatorio
- objectstringobligatorio
Valores admitidos
transcription - statusstringobligatorio
Valores admitidos
awaiting_uploadqueuedfetchingtranscribingcompletedfailedcanceled - sourceobjectobligatorio
Ver campos anidadosOcultar campos anidados
With
url- platformstringobligatorio
- urlstringobligatorioacepta null
- media_idstringobligatorioacepta null
With
filename- platformstringobligatorio
Valores admitidos
upload - filenamestringobligatorioacepta null
- sizenumberobligatorioacepta null
- mediaobjectobligatorioacepta null
Ver campos anidadosOcultar campos anidados
- titlestringopcionalacepta null
- authorstringopcionalacepta null
- author_urlstringopcionalacepta null
- thumbnail_urlstringopcionalacepta null
- filenamestringopcional
- mime_typestringopcional
- sizenumberopcional
- duration_secondsnumberopcionalacepta null
- optionsobjectobligatorio
Ver campos anidadosOcultar campos anidados
- sourcestringobligatorio
Valores admitidos
autocaptionsstt - languagestringobligatorio
- diarizebooleanobligatorio
- transcript_sourcestringobligatorioacepta null
Valores admitidos
youtube_captionsx_captionsspeech_to_text - caption_kindstringobligatorioacepta null
Valores admitidos
manualauto_generated - languagestringobligatorioacepta null
- billed_minutesintegerobligatorioacepta null
- cachedbooleanobligatorio
- errorobjectobligatorioacepta null
Ver campos anidadosOcultar campos anidados
- codestringobligatorio
- messagestringobligatorioacepta null
- created_atstring · date-timeobligatorioacepta null
- completed_atstring · date-timeobligatorioacepta null
- deleted_atstring · date-timeobligatorioacepta null
When the transcription was deleted. Always
nullunless the request includesinclude_deleted=true. - transcriptobjectopcional
Included in
GETresponses for completed transcriptions.Ver campos anidadosOcultar campos anidados
- textstringobligatorio
- speakersarray of stringsobligatorio
- segmentsarray of objectsobligatorio
Ver campos anidadosOcultar campos anidados
- startnumberobligatorio
- endnumberobligatorio
- speakerstringobligatorioacepta null
- languagestringobligatorioacepta null
- textstringobligatorio
- wordsarray of objectsopcionalacepta null
Ver campos anidadosOcultar campos anidados
- startnumberobligatorio
- endnumberobligatorio
- textstringobligatorio
- confidencenumberopcionalacepta null
- speakerstringobligatorioacepta null
Transcriptions
Create, read, list and delete transcriptions of video links and uploaded files.
Create a transcription
POST /v1/transcriptionsTranscribes an uploaded file or a YouTube, X, TikTok, Instagram, Facebook, Bluesky, Twitch, Dailymotion, Pinterest, LinkedIn, Snapchat link. Social posts must be public; only the first video is used.
Twitch accepts clips and completed VODs; Pinterest accepts native video pins; LinkedIn accepts public video posts; Snapchat accepts Spotlight. New platforms require public recorded native media without supplied account cookies; no live streams or external embeds.
platform is optional and detected from the link. Captions are YouTube/X-only. All sources use the same per-minute price.
Returns 200 for a final result, 201 when awaiting an upload and 202 while processing. Pass wait to wait up to 60 seconds for a final status.
Pass reuse_recent=true with a platform URL to reuse a transcription of the same video and options from the last 24 hours. The response includes reused: true, with no additional charge. Short links may not be matched. An Idempotency-Key replay takes precedence.
curl -X POST https://api.transcript1.com/v1/transcriptions \
-H "Authorization: Bearer $TRANSCRIPT1_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"}'{
"id": "tr_4fHq8ZbN2xLm0PqRsTuVwX",
"object": "transcription",
"status": "queued",
"source": {
"platform": "youtube",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"media_id": "dQw4w9WgXcQ"
},
"media": null,
"options": {
"source": "auto",
"language": "auto",
"diarize": false
},
"transcript_source": null,
"caption_kind": null,
"language": null,
"billed_minutes": null,
"cached": false,
"error": null,
"created_at": "2026-10-13T09:30:00.000Z",
"completed_at": null,
"deleted_at": null,
"reused": false
}Query
- waitintegeropcionalacepta nullPor defecto: 0
Seconds to wait for a final status before responding, up to 60. Defaults to 0.
- reuse_recentstringopcionalPor defecto: false
For platform URLs only. Cannot be combined with
webhook_url. Returns the team's most recent transcription of the same video with the samesource,languageanddiarize. It must have been created in the last 24 hours and must not befailedorcanceled. Reuses that transcription without creating or charging for a new one.Valores admitidos
truefalse
Headers
- Idempotency-Keystringopcional
Returns the original transcription when you repeat a request with the same key and parameters.
Body
With url
- platformstringopcional
Optional; detected from the link. If supplied, must match the link.
Valores admitidos
youtubextiktokinstagramfacebookblueskytwitchdailymotionpinterestlinkedinsnapchat - urlstringobligatorio
YouTube, X, TikTok, Instagram, Facebook, Bluesky, Twitch, Dailymotion, Pinterest, LinkedIn, Snapchat video link. Social posts must be public; only the first video is used. New platforms support public recorded native video without supplied account cookies, not live streams or external embeds.
- sourcestringopcional
"auto" uses YouTube or X captions when available, otherwise speech-to-text. "captions" is YouTube/X-only. "stt" always uses speech-to-text.
Valores admitidos
autocaptionsstt - languagestringopcional
- diarizebooleanopcional
- webhook_urlstring · uriopcional
With upload
- uploadobjectobligatorio
Ver campos anidadosOcultar campos anidados
- filenamestringobligatorio
- sizeintegerobligatorio
- mime_typestringopcional
- sourcestringopcional
Valores admitidos
autostt - languagestringopcional
- diarizebooleanopcional
- webhook_urlstring · uriopcional
multipart/form-data
- filestring · binaryobligatorio
- sourcestringopcional
Valores admitidos
autostt - languagestringopcional
- diarizebooleanopcional
- webhook_urlstring · uriopcional
Respuesta
200 OK— The transcription reached a final status201 Created— The transcription is awaiting a chunked upload.uploaddescribes where to send each chunk.202 Accepted— The transcription is processing
Otros códigos de estado
400 Bad Request— The JSON request body is malformed401 Unauthorized— Missing or invalid credential402 Payment Required— Not enough minutes. Applies to every request body format.403 Forbidden— Insufficient scope, unverified email, cross-site request or a single-member team plan409 Conflict—Idempotency-Keywas reused with different parameters.413 Content Too Large— The upload or the JSON request body is too large422 Unprocessable Content— Invalid request, URL, platform or file type429 Too Many Requests— Too many requests or too many active transcriptions. Wait for the number of seconds inRetry-After.
Get a transcription
GET /v1/transcriptions/{id}Returns the transcription object. Completed transcriptions include the transcript. Use format to render it as text, SRT or WebVTT.
If you request a rendered format before completion, the response is JSON. It returns 202 while processing or 200 if the transcription failed or was canceled.
With include_deleted=true, a deleted transcription is returned as a record without a transcript. For deleted records, wait is ignored and rendered formats return 404.
curl https://api.transcript1.com/v1/transcriptions/tr_4fHq8ZbN2xLm0PqRsTuVwX \
-H "Authorization: Bearer $TRANSCRIPT1_API_KEY"{
"id": "tr_4fHq8ZbN2xLm0PqRsTuVwX",
"object": "transcription",
"status": "completed",
"source": {
"platform": "youtube",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"media_id": "dQw4w9WgXcQ"
},
"media": {
"title": "Example video",
"author": "Example channel",
"author_url": "https://www.youtube.com/@examplechannel",
"thumbnail_url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
"duration_seconds": 212
},
"options": {
"source": "auto",
"language": "auto",
"diarize": false
},
"transcript_source": "youtube_captions",
"caption_kind": "auto_generated",
"language": "en",
"billed_minutes": 4,
"cached": false,
"error": null,
"created_at": "2026-10-13T09:30:00.000Z",
"completed_at": "2026-10-13T09:30:04.000Z",
"deleted_at": null,
"transcript": {
"text": "Welcome back to the channel. Today we are looking at how transcripts are made.",
"speakers": [],
"segments": [
{
"start": 0,
"end": 4.2,
"speaker": null,
"language": "en",
"text": "Welcome back to the channel."
},
{
"start": 4.2,
"end": 9.8,
"speaker": null,
"language": "en",
"text": "Today we are looking at how transcripts are made."
}
]
}
}Path
- idstringobligatorio
Query
- waitintegeropcionalacepta nullPor defecto: 0
Seconds to wait for a final status before responding, up to 60. Defaults to 0.
- formatstringopcionalPor defecto: json
Valores admitidos
jsontextsrtvtt - includestringopcional
Valores admitidos
words - timestampsstringopcionalPor defecto: false
Valores admitidos
truefalse - include_deletedstringopcionalPor defecto: false
Also returns deleted transcriptions as read-only records without a transcript. Their
deleted_atis set. Defaults tofalse.Valores admitidos
truefalse
Respuesta
200 OK— The transcription, or the rendered transcript202 Accepted— A rendered format was requested while the transcription is still processing
Otros códigos de estado
401 Unauthorized— Missing or invalid credential403 Forbidden— Insufficient scope or a single-member team plan404 Not Found— Transcription not found422 Unprocessable Content— Invalid query parameters429 Too Many Requests— Too many requests. Wait for the number of seconds inRetry-After.
List transcriptions
GET /v1/transcriptionsLists your team's transcriptions, newest first, without transcripts. Pass next_cursor as cursor to get the next page.
Deleted transcriptions are hidden unless you pass include_deleted=true.
curl https://api.transcript1.com/v1/transcriptions \
-H "Authorization: Bearer $TRANSCRIPT1_API_KEY"{
"object": "list",
"data": [
{
"id": "tr_4fHq8ZbN2xLm0PqRsTuVwX",
"object": "transcription",
"status": "completed",
"source": {
"platform": "youtube",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"media_id": "dQw4w9WgXcQ"
},
"media": {
"title": "Example video",
"author": "Example channel",
"author_url": "https://www.youtube.com/@examplechannel",
"thumbnail_url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
"duration_seconds": 212
},
"options": {
"source": "auto",
"language": "auto",
"diarize": false
},
"transcript_source": "youtube_captions",
"caption_kind": "auto_generated",
"language": "en",
"billed_minutes": 4,
"cached": false,
"error": null,
"created_at": "2026-10-13T09:30:00.000Z",
"completed_at": "2026-10-13T09:30:04.000Z",
"deleted_at": null
}
],
"next_cursor": "eyJjcmVhdGVkQXQiOiIyMDI2LTEwLTEzVDA5OjMwOjAwLjAwMFoiLCJpZCI6IjViMGU2YzFjLTBkNmYtNGMxZi05YjVlLThmM2Y0ZDBhN2ExMSJ9"
}Query
- limitintegeropcionalPor defecto: 20
- cursorstringopcional
- statusstringopcional
Valores admitidos
awaiting_uploadqueuedfetchingtranscribingcompletedfailedcanceled - platformstringopcional
- include_deletedstringopcionalPor defecto: false
Also returns deleted transcriptions as read-only records without a transcript. Their
deleted_atis set. Defaults tofalse.Valores admitidos
truefalse
Respuesta
200 OK— A page of transcriptions
Otros códigos de estado
401 Unauthorized— Missing or invalid credential403 Forbidden— Insufficient scope or a single-member team plan422 Unprocessable Content— Invalid query parameters or cursor429 Too Many Requests— Too many requests. Wait for the number of seconds inRetry-After.
Cancel or delete a transcription
DELETE /v1/transcriptions/{id}Cancels an active transcription or one awaiting an upload. Any charged minutes are refunded.
Deletes a completed, failed or canceled transcription together with its stored transcript.
curl -X DELETE https://api.transcript1.com/v1/transcriptions/tr_4fHq8ZbN2xLm0PqRsTuVwX \
-H "Authorization: Bearer $TRANSCRIPT1_API_KEY"La respuesta no tiene cuerpo
Path
- idstringobligatorio
Respuesta
204 No Content— Canceled or deleted
Otros códigos de estado
401 Unauthorized— Missing or invalid credential403 Forbidden— Insufficient scope, cross-site request or a single-member team plan404 Not Found— Transcription not found429 Too Many Requests— Too many requests. Wait for the number of seconds inRetry-After.
Uploads
Send a large file in chunks. Start with Create a transcription using an upload body. Send each chunk, then complete the upload.
Upload one chunk
PUT /v1/transcriptions/{id}/upload/chunks/{index}Sends one chunk of a chunked upload as application/octet-stream.
Authenticate with the X-Upload-Token from the create response. This endpoint does not use a bearer credential.
The body must match the planned chunk size exactly. Sending a chunk again replaces it.
curl -X PUT https://api.transcript1.com/v1/transcriptions/tr_4fHq8ZbN2xLm0PqRsTuVwX/upload/chunks/0 \
-H "X-Upload-Token: t1u_Zk3vYb8Nw1ZrT5xLpD0aHcE7sUj9GfMiO2yXtVnR4Bw" \
-H "Content-Type: application/octet-stream" \
--data-binary @chunk-0.bin{
"object": "upload_chunk",
"index": 0,
"size": 838860
}Path
- idstringobligatorio
- indexintegeropcionalacepta null
Zero-based chunk number from the upload plan
Headers
- X-Upload-Tokenstringobligatorio
Body
- (cuerpo sin procesar)string · binaryobligatorio
Respuesta
200 OK— The chunk was stored
Otros códigos de estado
401 Unauthorized— Missing, invalid or expired upload token404 Not Found— Transcription not found409 Conflict— The transcription is no longer awaiting an upload422 Unprocessable Content— Index outside the chunk plan or wrong body size
Get upload progress
GET /v1/transcriptions/{id}/uploadLists the received chunks of a chunked upload so an interrupted upload can resume.
curl https://api.transcript1.com/v1/transcriptions/tr_4fHq8ZbN2xLm0PqRsTuVwX/upload \
-H "Authorization: Bearer $TRANSCRIPT1_API_KEY"{
"object": "upload",
"status": "awaiting_upload",
"total_bytes": 48210334,
"uploaded_bytes": 4194300,
"received_chunks": [
0,
1,
2,
3,
4
],
"chunks": [
{
"index": 0,
"offset": 0,
"size": 838860
},
{
"index": 1,
"offset": 838860,
"size": 838860
},
{
"index": 2,
"offset": 1677720,
"size": 838860
},
{
"index": 3,
"offset": 2516580,
"size": 838860
},
{
"index": 4,
"offset": 3355440,
"size": 838860
},
{
"index": 5,
"offset": 4194300,
"size": 8388608
},
{
"index": 6,
"offset": 12582908,
"size": 8388608
},
{
"index": 7,
"offset": 20971516,
"size": 8388608
},
{
"index": 8,
"offset": 29360124,
"size": 8388608
},
{
"index": 9,
"offset": 37748732,
"size": 8388608
},
{
"index": 10,
"offset": 46137340,
"size": 2072994
}
],
"expires_at": "2026-10-13T10:30:00.000Z"
}Path
- idstringobligatorio
Respuesta
200 OK— Upload progress
Otros códigos de estado
401 Unauthorized— Missing or invalid credential403 Forbidden— Insufficient scope or a single-member team plan404 Not Found— Transcription or upload not found429 Too Many Requests— Too many requests. Wait for the number of seconds inRetry-After.
Complete a chunked upload
POST /v1/transcriptions/{id}/upload/completeChecks that every planned chunk arrived with the planned size. Then assembles the file and queues the transcription.
curl -X POST https://api.transcript1.com/v1/transcriptions/tr_4fHq8ZbN2xLm0PqRsTuVwX/upload/complete \
-H "Authorization: Bearer $TRANSCRIPT1_API_KEY"{
"id": "tr_4fHq8ZbN2xLm0PqRsTuVwX",
"object": "transcription",
"status": "queued",
"source": {
"platform": "upload",
"filename": "interview.m4a",
"size": 48210334
},
"media": {
"filename": "interview.m4a",
"mime_type": "audio/mp4",
"size": 48210334
},
"options": {
"source": "auto",
"language": "auto",
"diarize": false
},
"transcript_source": null,
"caption_kind": null,
"language": null,
"billed_minutes": null,
"cached": false,
"error": null,
"created_at": "2026-10-13T09:30:00.000Z",
"completed_at": null,
"deleted_at": null
}Path
- idstringobligatorio
Respuesta
202 Accepted— The transcription is queued
Otros códigos de estado
401 Unauthorized— Missing or invalid credential402 Payment Required— Not enough minutes to queue the transcription. The upload stays awaiting completion.403 Forbidden— Insufficient scope, cross-site request or a single-member team plan404 Not Found— Transcription or upload not found409 Conflict—upload_incompletemeans chunks are missing.missing_chunkslists them.invalid_statemeans the transcription is not awaiting an upload.429 Too Many Requests— Too many requests or too many active transcriptions. Wait for the number of seconds inRetry-After.
Account
Your plan, remaining minutes and rate limits.
Get your account
GET /v1/accountReturns the authenticated user, current plan and remaining transcription minutes.
curl https://api.transcript1.com/v1/account \
-H "Authorization: Bearer $TRANSCRIPT1_API_KEY"{
"object": "account",
"user": {
"id": "5b0e6c1c-0d6f-4c1f-9b5e-8f3f4d0a7a11",
"email": "[email protected]"
},
"plan": {
"id": "9d4e0c0a-2b8a-4f0c-8d63-6a8d3c8a1f22",
"name": "Monthly"
},
"minutes": {
"remaining": 1180,
"cycle": 980,
"topup": 200,
"topup_usable": true,
"resets_at": "2026-11-13T09:30:00.000Z"
},
"renews_at": "2026-11-13T09:30:00.000Z",
"limits": {
"requests_per_minute": 200,
"max_active_jobs": 10
}
}Respuesta
200 OK— The authenticated user's account
Otros códigos de estado
401 Unauthorized— Missing or invalid credential403 Forbidden— Insufficient scope or a single-member team plan404 Not Found— No account found429 Too Many Requests— Too many requests. Wait for the number of seconds inRetry-After.
Objeto Account
{
"object": "account",
"user": {
"id": "5b0e6c1c-0d6f-4c1f-9b5e-8f3f4d0a7a11",
"email": "[email protected]"
},
"plan": {
"id": "9d4e0c0a-2b8a-4f0c-8d63-6a8d3c8a1f22",
"name": "Monthly"
},
"minutes": {
"remaining": 1180,
"cycle": 980,
"topup": 200,
"topup_usable": true,
"resets_at": "2026-11-13T09:30:00.000Z"
},
"renews_at": "2026-11-13T09:30:00.000Z",
"limits": {
"requests_per_minute": 200,
"max_active_jobs": 10
}
}Campos
- objectstringobligatorio
Valores admitidos
account - userobjectobligatorio
Ver campos anidadosOcultar campos anidados
- idstringobligatorio
- emailstringobligatorio
- planobjectobligatorioacepta null
Ver campos anidadosOcultar campos anidados
- idstringobligatorio
- namestringobligatorio
- minutesobjectobligatorio
Ver campos anidadosOcultar campos anidados
- remainingnumberobligatorio
- cyclenumberobligatorio
- topupnumberobligatorio
- topup_usablebooleanobligatorio
- resets_atstring · date-timeobligatorioacepta null
- renews_atstring · date-timeobligatorioacepta null
- limitsobjectobligatorio
Ver campos anidadosOcultar campos anidados
- requests_per_minutenumberobligatorio
- max_active_jobsnumberobligatorio
Webhooks
import { Buffer } from 'node:buffer';
import crypto from 'node:crypto';
// header: X-Transcript1-Signature, rawBody: the request body exactly as received,
// secret: your full signing secret including the whsec_ prefix.
export const verifySignature = (secret, header, rawBody, nowSeconds = Math.floor(Date.now() / 1000)) => {
const fields = Object.fromEntries(header.split(',').map(part => part.trim().split('=')));
const t = Number(fields.t);
if (!Number.isInteger(t) || !fields.v1 || Math.abs(nowSeconds - t) > 300) {
return false;
}
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return fields.v1.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(fields.v1), Buffer.from(expected));
};{
"type": "transcription.completed",
"created_at": "2026-10-13T09:30:04.000Z",
"data": {
"id": "tr_8fK2Lm0PqRsTuVwXyZaBcD",
"object": "transcription",
"status": "completed",
"transcript_source": "youtube_captions",
"billed_minutes": 4,
"error": null
}
}Incluye webhook_url al crear la transcripción para recibir el aviso en tu servidor. Cuando termine o falle, transcript1 enviará un POST firmado a esa URL.
Esta opción solo está en la API REST. El servidor MCP no acepta un parámetro para webhooks.
Configura dónde recibir el aviso
La ubicación de webhook_url en POST /v1/transcriptions depende de lo que envíes:
- Si envías un enlace de video admitido o reservas una subida por partes, inclúyelo en el cuerpo JSON.
- Si subes el archivo completo en un request, inclúyelo como campo del formulario.
Usa una URL que cumpla estos requisitos:
- El protocolo es HTTPS.
- No lleva usuario ni contraseña.
- Resuelve a una dirección pública.
Las direcciones privadas, loopback, link-local y cualquier otra dirección no pública devuelven 422 invalid_request. La API comprueba la dirección al crear la transcripción y antes de cada envío.
No combines webhook_url con reuse_recent=true.
Eventos
type | Cuándo lo recibes |
|---|---|
transcription.completed | Cuando la transcripción pasa a completed. |
transcription.failed | Cuando pasa a failed. Revisa code y message en data.error. |
Si cancelas la transcripción, no se envía un webhook.
Qué contiene el request
El aviso llega con un cuerpo JSON que incluye:
created_at: la fecha y hora de creación del evento.data: el objeto de transcripción, sin el campotranscript.
Para obtener el texto, toma el id de data y consulta GET /v1/transcriptions/:id.
El request lleva estos headers:
Content-Type: application/jsonUser-Agent: transcript1-webhooks/1.0X-Transcript1-Signature, la firma que se explica en la siguiente sección.
Comprueba la firma antes de leer el JSON
Busca el header X-Transcript1-Signature en cada envío. Su valor tiene el formato t=<unix seconds>,v1=<hex digest>.
tindica cuándo se firmó el request, en segundos Unix.v1contiene el HMAC-SHA256 en hexadecimal de"<t>.<raw body>". Para calcularlo, concatena el valor det, un punto y el cuerpo exacto que recibiste. Usa tu secreto de firma completo como clave, sin quitar el prefijowhsec_.
Comprueba la firma con los bytes originales del cuerpo, antes de parsear el JSON. Si lo parseas y vuelves a serializar, los espacios o el orden de las claves pueden cambiar y la firma dejará de coincidir.
- Haz la comparación en tiempo constante.
- Rechaza los requests cuyo
tse aleje demasiado de la hora actual. Los ejemplos aceptan una diferencia de hasta 5 minutos. - En Express, recibe el cuerpo como texto sin parsearlo primero.
- En Flask, obtén el cuerpo con
request.get_data(as_text=True)y pásalo araw_body.
Dónde consultar o rotar el secreto
El secreto empieza con whsec_ y es compartido por todo el equipo: firma los webhooks de todas sus transcripciones.
Abre “Webhooks” en el panel para verlo o rotarlo.
Si lo rotas, el cambio es inmediato. Todos los envíos posteriores usan el secreto nuevo.
Respuestas, reintentos y duplicados
- Responde con cualquier código
2xxpara confirmar la recepción. - Otro código HTTP, un error de conexión o más de 10 segundos sin respuesta cuentan como un intento fallido.
- Si falla el envío, transcript1 vuelve a intentarlo hasta completar 6 intentos en total. Espera 2, 4, 8, 16 y 32 minutos entre intentos; el último ocurre aproximadamente una hora después del primero.
- Configura la URL final. transcript1 no sigue redirecciones: un
3xxtambién cuenta como fallo. - Puedes recibir el mismo aviso más de una vez, por ejemplo si tu servidor responde después del timeout. Usa la combinación de
data.idytypepara detectar e ignorar duplicados.