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

URL base
https://api.transcript1.com/v1

Usa 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-Limit y X-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 modo source=captions (solo subtítulos) nunca descarga audio ni pasa a STT; si falta una pista válida, falla con no_captions. Para transcribir directamente el audio, usa source=stt o 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 son transcriptions:read y transcriptions:write.
Permiso (scope)Endpoints disponibles
transcriptions:readGET /v1/transcriptions, GET /v1/transcriptions/:id, GET /v1/transcriptions/:id/upload y GET /v1/account
transcriptions:writePOST /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

402 Payment Required
{
  "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 status en failed.

Cómo leer un error

Cuando /v1 rechaza un request, devuelve un objeto error con los campos code, message y retryable.

  • Usa code para decidir qué debe hacer tu aplicación.
  • Muestra la explicación de message al usuario, pero no bases tu lógica en ese texto: puede cambiar.
  • Si retryable es true, puedes volver a enviar el mismo request más adelante.

Según el error, encontrarás otros campos junto a retryable:

CampoAparece enQué indica
action_urlinsufficient_minutesEnlace para contratar un plan o comprar minutos.
minutes_needed, minutes_remaininginsufficient_minutesMinutos necesarios para el request y saldo disponible. El primero es null si aún no se sabe cuántos minutos necesita.
supported_platformsunsupported_platformValores de plataforma admitidos: ["youtube", "x", "tiktok", "instagram", "facebook", "bluesky", "twitch", "dailymotion", "pinterest", "linkedin", "snapchat"].
missing_chunksupload_incompleteÍndices de las partes del archivo que faltan por recibir.
max_bytesmedia_too_largeTamaño máximo de archivo que puedes enviar, en bytes.
paramsinvalid_requestObjeto 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 HTTPcode¿Reintentar?Qué ocurrió
400, 422invalid_requestNoFalta 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.
422invalid_urlNoEl enlace no se admite o no corresponde a platform. Usa YouTube, X, TikTok, Instagram, Facebook, Bluesky u otra plataforma de Fuentes y opciones.
422unsupported_platformNoEl valor de platform no está entre los admitidos.
422unsupported_mediaNoLa extensión del archivo no está admitida.
401unauthorizedNoFalta 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ó.
403insufficient_scopeNoEl token OAuth no tiene el permiso transcriptions:read o transcriptions:write necesario.
403email_not_verifiedNoDebes verificar tu correo antes de crear una transcripción.
402insufficient_minutesNoTu saldo disponible es 0 o no alcanza para esta transcripción.
402no_active_planNoNo 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.
404not_foundNoNo existe esa transcripción o es de otro equipo.
409upload_incompleteNoFaltan partes del archivo. Envíalas y vuelve a confirmar la subida.
409invalid_stateNoLa transcripción ya no espera una subida. No puedes enviar más partes ni confirmar que terminaste.
409idempotency_conflictNoUsaste antes el mismo Idempotency-Key con parámetros diferentes.
413media_too_largeNoEl archivo pesa más de lo permitido. Revisa Cómo subir archivos.
413invalid_requestNoEl cuerpo JSON supera 1 MB. Para archivos demasiado grandes, el código es media_too_large.
429rate_limitedSíSuperaste los requests por minuto. Espera el tiempo en segundos que indica Retry-After.
429too_many_active_jobsSíLlegaste al límite de transcripciones en curso o de subidas abiertas. Espera los segundos indicados en Retry-After.
500internal_errorSíFalló algo en transcript1. Espera un momento y vuelve a intentarlo.
503service_unavailableSí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 retryable es true: rate_limited, too_many_active_jobs, internal_error o service_unavailable.
  • Si hay Retry-After, respétalo. Si no, aumenta la espera entre intentos.
  • Si repites POST /v1/transcriptions, conserva el mismo header Idempotency-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.codeQué ocurrió¿Qué puedes hacer?
media_unavailableEl video no existe o dejó de estar disponible.No repitas el mismo envío; revisa la URL.
media_privateNo 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_requiredHay que iniciar sesión en la plataforma para ver el video.Es un fallo permanente; no se reintenta.
no_audioEl video no contiene una pista de audio.Es un fallo permanente; no se reintenta.
age_restrictedLa plataforma restringe el video por edad.No repitas el enlace; sube el archivo.
live_streamLa transmisión sigue en vivo o todavía no empieza.Espera a que termine para volver a enviarla.
no_captionsPediste captions en source, pero no hay una pista de subtítulos que se pueda usar.Cambia a source: "auto" o "stt".
media_too_longLa grabación supera las 5 horas que admite voz a texto.Divide la grabación antes de enviarla.
unsupported_mediaNo se puede leer el archivo o le falta una pista de audio.Envía un archivo en un formato admitido.
no_speechNo se detectó habla.No lo reintentes.
unsupported_languageEl idioma de la grabación no está admitido.Configura language con un código admitido o "auto" antes de volver a enviarla.
insufficient_minutesEl saldo se agotó antes de cobrar la transcripción.Recarga minutos y vuelve a intentarlo.
fetch_failedNo se pudo descargar la grabación después de varios intentos.Puedes probar de nuevo más tarde.
transcription_failedLa 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

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 admitidostranscription

  • statusstringobligatorio

    Valores admitidosawaiting_uploadqueuedfetchingtranscribingcompletedfailedcanceled

  • sourceobjectobligatorio
    Ver campos anidados
    With url
    • platformstringobligatorio
    • urlstringobligatorioacepta null
    • media_idstringobligatorioacepta null
    With filename
    • platformstringobligatorio

      Valores admitidosupload

    • filenamestringobligatorioacepta null
    • sizenumberobligatorioacepta null
  • mediaobjectobligatorioacepta null
    Ver campos anidados
    • titlestringopcionalacepta null
    • authorstringopcionalacepta null
    • author_urlstringopcionalacepta null
    • thumbnail_urlstringopcionalacepta null
    • filenamestringopcional
    • mime_typestringopcional
    • sizenumberopcional
    • duration_secondsnumberopcionalacepta null
  • optionsobjectobligatorio
    Ver campos anidados
    • sourcestringobligatorio

      Valores admitidosautocaptionsstt

    • languagestringobligatorio
    • diarizebooleanobligatorio
  • transcript_sourcestringobligatorioacepta null

    Valores admitidosyoutube_captionsx_captionsspeech_to_text

  • caption_kindstringobligatorioacepta null

    Valores admitidosmanualauto_generated

  • languagestringobligatorioacepta null
  • billed_minutesintegerobligatorioacepta null
  • cachedbooleanobligatorio
  • errorobjectobligatorioacepta null
    Ver 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 null unless the request includes include_deleted=true.

  • transcriptobjectopcional

    Included in GET responses for completed transcriptions.

    Ver campos anidados
    • textstringobligatorio
    • speakersarray of stringsobligatorio
    • segmentsarray of objectsobligatorio
      Ver campos anidados
      • startnumberobligatorio
      • endnumberobligatorio
      • speakerstringobligatorioacepta null
      • languagestringobligatorioacepta null
      • textstringobligatorio
      • wordsarray of objectsopcionalacepta null
        Ver 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/transcriptions

Transcribes 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 same source, language and diarize. It must have been created in the last 24 hours and must not be failed or canceled. Reuses that transcription without creating or charging for a new one.

    Valores admitidostruefalse

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 admitidosyoutubextiktokinstagramfacebookblueskytwitchdailymotionpinterestlinkedinsnapchat

  • 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 admitidosautocaptionsstt

  • languagestringopcional
  • diarizebooleanopcional
  • webhook_urlstring · uriopcional
With upload
  • uploadobjectobligatorio
    Ver campos anidados
    • filenamestringobligatorio
    • sizeintegerobligatorio
    • mime_typestringopcional
  • sourcestringopcional

    Valores admitidosautostt

  • languagestringopcional
  • diarizebooleanopcional
  • webhook_urlstring · uriopcional
multipart/form-data
  • filestring · binaryobligatorio
  • sourcestringopcional

    Valores admitidosautostt

  • languagestringopcional
  • diarizebooleanopcional
  • webhook_urlstring · uriopcional

Respuesta

  • 200 OK — The transcription reached a final status
  • 201 Created — The transcription is awaiting a chunked upload. upload describes where to send each chunk.
  • 202 Accepted — The transcription is processing

Otros códigos de estado

  • 400 Bad Request — The JSON request body is malformed
  • 401 Unauthorized — Missing or invalid credential
  • 402 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 plan
  • 409 Conflict — Idempotency-Key was reused with different parameters.
  • 413 Content Too Large — The upload or the JSON request body is too large
  • 422 Unprocessable Content — Invalid request, URL, platform or file type
  • 429 Too Many Requests — Too many requests or too many active transcriptions. Wait for the number of seconds in Retry-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 admitidosjsontextsrtvtt

  • includestringopcional

    Valores admitidoswords

  • timestampsstringopcionalPor defecto: false

    Valores admitidostruefalse

  • include_deletedstringopcionalPor defecto: false

    Also returns deleted transcriptions as read-only records without a transcript. Their deleted_at is set. Defaults to false.

    Valores admitidostruefalse

Respuesta

  • 200 OK — The transcription, or the rendered transcript
  • 202 Accepted — A rendered format was requested while the transcription is still processing

Otros códigos de estado

  • 401 Unauthorized — Missing or invalid credential
  • 403 Forbidden — Insufficient scope or a single-member team plan
  • 404 Not Found — Transcription not found
  • 422 Unprocessable Content — Invalid query parameters
  • 429 Too Many Requests — Too many requests. Wait for the number of seconds in Retry-After.

List transcriptions

GET /v1/transcriptions

Lists 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 admitidosawaiting_uploadqueuedfetchingtranscribingcompletedfailedcanceled

  • platformstringopcional
  • include_deletedstringopcionalPor defecto: false

    Also returns deleted transcriptions as read-only records without a transcript. Their deleted_at is set. Defaults to false.

    Valores admitidostruefalse

Respuesta

  • 200 OK — A page of transcriptions

Otros códigos de estado

  • 401 Unauthorized — Missing or invalid credential
  • 403 Forbidden — Insufficient scope or a single-member team plan
  • 422 Unprocessable Content — Invalid query parameters or cursor
  • 429 Too Many Requests — Too many requests. Wait for the number of seconds in Retry-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 credential
  • 403 Forbidden — Insufficient scope, cross-site request or a single-member team plan
  • 404 Not Found — Transcription not found
  • 429 Too Many Requests — Too many requests. Wait for the number of seconds in Retry-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 token
  • 404 Not Found — Transcription not found
  • 409 Conflict — The transcription is no longer awaiting an upload
  • 422 Unprocessable Content — Index outside the chunk plan or wrong body size

Get upload progress

GET /v1/transcriptions/{id}/upload

Lists 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 credential
  • 403 Forbidden — Insufficient scope or a single-member team plan
  • 404 Not Found — Transcription or upload not found
  • 429 Too Many Requests — Too many requests. Wait for the number of seconds in Retry-After.

Complete a chunked upload

POST /v1/transcriptions/{id}/upload/complete

Checks 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 credential
  • 402 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 plan
  • 404 Not Found — Transcription or upload not found
  • 409 Conflict — upload_incomplete means chunks are missing. missing_chunks lists them. invalid_state means 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 in Retry-After.

Account

Your plan, remaining minutes and rate limits.

Get your account

GET /v1/account

Returns 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 credential
  • 403 Forbidden — Insufficient scope or a single-member team plan
  • 404 Not Found — No account found
  • 429 Too Many Requests — Too many requests. Wait for the number of seconds in Retry-After.

Objeto Account

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 admitidosaccount

  • userobjectobligatorio
    Ver campos anidados
    • idstringobligatorio
    • emailstringobligatorio
  • planobjectobligatorioacepta null
    Ver campos anidados
    • idstringobligatorio
    • namestringobligatorio
  • minutesobjectobligatorio
    Ver campos anidados
    • remainingnumberobligatorio
    • cyclenumberobligatorio
    • topupnumberobligatorio
    • topup_usablebooleanobligatorio
    • resets_atstring · date-timeobligatorioacepta null
  • renews_atstring · date-timeobligatorioacepta null
  • limitsobjectobligatorio
    Ver 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));
};
transcription.completed
{
  "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

typeCuándo lo recibes
transcription.completedCuando la transcripción pasa a completed.
transcription.failedCuando 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 campo transcript.

Para obtener el texto, toma el id de data y consulta GET /v1/transcriptions/:id.

El request lleva estos headers:

  • Content-Type: application/json
  • User-Agent: transcript1-webhooks/1.0
  • X-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>.

  • t indica cuándo se firmó el request, en segundos Unix.
  • v1 contiene el HMAC-SHA256 en hexadecimal de "<t>.<raw body>". Para calcularlo, concatena el valor de t, un punto y el cuerpo exacto que recibiste. Usa tu secreto de firma completo como clave, sin quitar el prefijo whsec_.

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 t se 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 a raw_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 2xx para 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 3xx tambié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.id y type para detectar e ignorar duplicados.