{"openapi":"3.1.0","info":{"title":"Reedapt Dubbing API","description":"Dub video and audio into other languages in the speaker's own voice. Authenticate with `Authorization: Bearer <key>`; make keys in Reedapt under Developers.","version":"1.0.0"},"servers":[{"url":"https://api.reedapt.com/v1"}],"paths":{"/uploads":{"post":{"tags":["Uploads"],"summary":"Create an upload","description":"Start uploading a video or audio file. Send each part with a PUT to its link\n(keep each response's ETag), ask for more links with Sign more parts, then call\nComplete an upload.","operationId":"create_upload_uploads_post","parameters":[{"name":"idempotency-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Any unique string, up to 255 characters. Sending the same request again with the same key returns the first answer instead of doing the work twice. Kept for 24 hours.","examples":["7c1f6a52-2d4e-4bb1-9e0a-5f3d8c2b1a90"],"title":"Idempotency-Key"},"description":"Any unique string, up to 255 characters. Sending the same request again with the same key returns the first answer instead of doing the work twice. Kept for 24 hours."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadIn"},"examples":{"In parts":{"value":{"filename":"interview.mp4","size":734003200}},"From a link":{"value":{"source_url":"https://example.com/media/interview.mp4"}}}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Upload"},"example":{"id":"job_3f9a2c7e1b4d","object":"upload","filename":"interview.mp4","size":734003200,"status":"uploading","error":null,"part_size":16777216,"part_count":44,"parts":[{"part_number":1,"url":"https://…"},{"part_number":2,"url":"https://…"}],"parts_expire_at":"2026-10-28T09:27:44Z"}}}},"202":{"description":"From source_url: we're downloading the file.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Upload"},"example":{"id":"job_3f9a2c7e1b4d","object":"upload","filename":"interview.mp4","size":0,"status":"fetching","error":null,"part_size":8388608,"part_count":0,"parts":[],"parts_expire_at":null}}}},"401":{"description":"The key is missing, wrong or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Too many requests, or a plan limit was reached. Wait for `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"The request conflicts with the object's state; the code says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"The request is invalid; the code and `fields` say why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"A dependency is briefly unavailable. Retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/uploads/{upload_id}/parts":{"post":{"tags":["Uploads"],"summary":"Sign more parts","description":"Fresh signed links for these parts (each lasts 15 minutes).","operationId":"sign_more_parts_uploads__upload_id__parts_post","parameters":[{"name":"upload_id","in":"path","required":true,"schema":{"type":"string","title":"Upload Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartsIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Upload"}}}},"401":{"description":"The key is missing, wrong or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Too many requests, or a plan limit was reached. Wait for `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Nothing with this id belongs to the key's account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"The request conflicts with the object's state; the code says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"The request is invalid; the code and `fields` say why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/uploads/{upload_id}/complete":{"post":{"tags":["Uploads"],"summary":"Complete an upload","description":"Join the parts into the file. It can then be dubbed.","operationId":"complete_upload_uploads__upload_id__complete_post","parameters":[{"name":"upload_id","in":"path","required":true,"schema":{"type":"string","title":"Upload Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompleteIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Upload"},"example":{"id":"job_3f9a2c7e1b4d","object":"upload","filename":"interview.mp4","size":734003200,"status":"uploaded","error":null,"part_size":16777216,"part_count":44,"parts":[],"parts_expire_at":null}}}},"401":{"description":"The key is missing, wrong or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Too many requests, or a plan limit was reached. Wait for `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Nothing with this id belongs to the key's account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"The request conflicts with the object's state; the code says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"The request is invalid; the code and `fields` say why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/uploads/{upload_id}":{"get":{"tags":["Uploads"],"summary":"Retrieve an upload","description":"An upload and its status. Poll this after sending a source_url.","operationId":"get_upload_uploads__upload_id__get","parameters":[{"name":"upload_id","in":"path","required":true,"schema":{"type":"string","title":"Upload Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Upload"},"example":{"id":"job_3f9a2c7e1b4d","object":"upload","filename":"interview.mp4","size":734003200,"status":"uploaded","error":null,"part_size":16777216,"part_count":44,"parts":[],"parts_expire_at":null}}}},"401":{"description":"The key is missing, wrong or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Too many requests, or a plan limit was reached. Wait for `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Nothing with this id belongs to the key's account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"The request is invalid; the code and `fields` say why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/dubs":{"post":{"tags":["Dubs"],"summary":"Create dubs","description":"Dub a completed upload into one or more languages. Each language is one dub and\nreserves its minutes now; they are returned if the dub fails or is cancelled.","operationId":"create_dubs_dubs_post","parameters":[{"name":"idempotency-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Any unique string, up to 255 characters. Sending the same request again with the same key returns the first answer instead of doing the work twice. Kept for 24 hours.","examples":["7c1f6a52-2d4e-4bb1-9e0a-5f3d8c2b1a90"],"title":"Idempotency-Key"},"description":"Any unique string, up to 255 characters. Sending the same request again with the same key returns the first answer instead of doing the work twice. Kept for 24 hours."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DubIn"},"examples":{"Two languages":{"value":{"upload_id":"job_3f9a2c7e1b4d","source_language":"en","target_languages":["fr","es"],"metadata":{"order_id":"1042"}}}}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DubsCreated"},"example":{"dubs":[{"id":"job_3f9a2c7e1b4d","object":"dub","status":"queued","stage":null,"progress":0,"source_language":"en","target_language":"fr","audio":false,"test":false,"preview_seconds":null,"duration_minutes":12.4,"created_at":"2026-10-28T09:12:44","started_at":null,"completed_at":null,"error":null,"metadata":{"order_id":"1042"}}],"refused":[{"target_language":"es","code":"LANGUAGE_NOT_IN_PLAN","message":"Your plan doesn't include this language."}]}}}},"401":{"description":"The key is missing, wrong or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Too many requests, or a plan limit was reached. Wait for `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Nothing with this id belongs to the key's account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"The request conflicts with the object's state; the code says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"The request is invalid; the code and `fields` say why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"A dependency is briefly unavailable. Retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}},"get":{"tags":["Dubs"],"summary":"List dubs","description":"The account's dubs, newest first.","operationId":"list_dubs_dubs_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"How many to return, up to 100.","default":20,"title":"Limit"},"description":"How many to return, up to 100."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"How many to skip, for the next page.","default":0,"title":"Offset"},"description":"How many to skip, for the next page."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DubList"},"example":{"data":[{"id":"job_3f9a2c7e1b4d","object":"dub","status":"completed","stage":null,"progress":100,"source_language":"en","target_language":"fr","audio":false,"test":false,"preview_seconds":null,"duration_minutes":12.4,"created_at":"2026-10-28T09:12:44","started_at":"2026-10-28T09:12:51","completed_at":"2026-10-28T09:31:02","error":null,"metadata":{"order_id":"1042"}}],"has_more":false}}}},"401":{"description":"The key is missing, wrong or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Too many requests, or a plan limit was reached. Wait for `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"The request is invalid; the code and `fields` say why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/dubs/{dub_id}":{"get":{"tags":["Dubs"],"summary":"Retrieve a dub","description":"A dub's status and progress. Prefer webhooks to polling; if you poll, every 30 seconds is plenty.","operationId":"get_dub_dubs__dub_id__get","parameters":[{"name":"dub_id","in":"path","required":true,"schema":{"type":"string","title":"Dub Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Dub"},"example":{"id":"job_3f9a2c7e1b4d","object":"dub","status":"completed","stage":null,"progress":100,"source_language":"en","target_language":"fr","audio":false,"test":false,"preview_seconds":null,"duration_minutes":12.4,"created_at":"2026-10-28T09:12:44","started_at":"2026-10-28T09:12:51","completed_at":"2026-10-28T09:31:02","error":null,"metadata":{"order_id":"1042"}}}}},"401":{"description":"The key is missing, wrong or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Too many requests, or a plan limit was reached. Wait for `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Nothing with this id belongs to the key's account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"The request is invalid; the code and `fields` say why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/dubs/{dub_id}/cancel":{"post":{"tags":["Dubs"],"summary":"Cancel a dub","description":"Stop a dub and delete it. Its minutes are returned.","operationId":"cancel_dub_dubs__dub_id__cancel_post","parameters":[{"name":"dub_id","in":"path","required":true,"schema":{"type":"string","title":"Dub Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"The key is missing, wrong or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Too many requests, or a plan limit was reached. Wait for `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Nothing with this id belongs to the key's account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"The request conflicts with the object's state; the code says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"The request is invalid; the code and `fields` say why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/dubs/{dub_id}/files":{"get":{"tags":["Dubs"],"summary":"Download a dub's files","description":"Links to a completed dub's files. Each link lasts an hour; ask again for fresh ones.","operationId":"dub_files_dubs__dub_id__files_get","parameters":[{"name":"dub_id","in":"path","required":true,"schema":{"type":"string","title":"Dub Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DubFiles"},"example":{"files":[{"kind":"dub","filename":"interview_dubbed_french.mp4","url":"https://…"}],"expires_at":"2026-10-28T10:31:02Z"}}}},"401":{"description":"The key is missing, wrong or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Too many requests, or a plan limit was reached. Wait for `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Nothing with this id belongs to the key's account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"The request conflicts with the object's state; the code says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"The request is invalid; the code and `fields` say why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/languages":{"get":{"tags":["Account"],"summary":"List languages","description":"The languages this key's plan can dub into.","operationId":"languages_languages_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Languages"}}}},"401":{"description":"The key is missing, wrong or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Too many requests, or a plan limit was reached. Wait for `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/usage":{"get":{"tags":["Account"],"summary":"Get usage","description":"Minutes left on the account, and how a test key's dubs are limited.","operationId":"usage_usage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Usage"}}}},"401":{"description":"The key is missing, wrong or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Too many requests, or a plan limit was reached. Wait for `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/webhook_endpoints":{"get":{"tags":["Webhooks"],"summary":"List webhook endpoints","description":"The account's endpoints. Secrets are never shown again.","operationId":"list_webhook_endpoints_webhook_endpoints_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointList"}}}},"401":{"description":"The key is missing, wrong or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Too many requests, or a plan limit was reached. Wait for `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}},"post":{"tags":["Webhooks"],"summary":"Create a webhook endpoint","description":"Send this account's dub events (`dub.completed`, `dub.failed`) to a URL. Each\ndelivery is signed: `Reedapt-Signature: t=<unix>,v1=<HMAC-SHA256 of \"<t>.<body>\">`\nkeyed with the endpoint's secret.","operationId":"create_webhook_endpoint_webhook_endpoints_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointIn"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointOut"}}}},"401":{"description":"The key is missing, wrong or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Too many requests, or a plan limit was reached. Wait for `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"The request is invalid; the code and `fields` say why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/webhook_endpoints/{endpoint_id}":{"delete":{"tags":["Webhooks"],"summary":"Delete a webhook endpoint","description":"Stop sending events here. Deliveries still pending are dropped.","operationId":"delete_webhook_endpoint_webhook_endpoints__endpoint_id__delete","parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"string","title":"Endpoint Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"The key is missing, wrong or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Too many requests, or a plan limit was reached. Wait for `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Nothing with this id belongs to the key's account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"The request is invalid; the code and `fields` say why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/webhook_endpoints/{endpoint_id}/test":{"post":{"tags":["Webhooks"],"summary":"Send a test event","description":"Queue a `webhook.test` event to this endpoint; watch it under /deliveries.","operationId":"test_webhook_endpoint_webhook_endpoints__endpoint_id__test_post","parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"string","title":"Endpoint Id"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Queued"}}}},"401":{"description":"The key is missing, wrong or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Too many requests, or a plan limit was reached. Wait for `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Nothing with this id belongs to the key's account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"The request is invalid; the code and `fields` say why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/webhook_endpoints/{endpoint_id}/deliveries":{"get":{"tags":["Webhooks"],"summary":"List deliveries","description":"The latest deliveries to this endpoint, newest first.","operationId":"webhook_deliveries_webhook_endpoints__endpoint_id__deliveries_get","parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"string","title":"Endpoint Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"How many to return, up to 100.","default":50,"title":"Limit"},"description":"How many to return, up to 100."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryList"}}}},"401":{"description":"The key is missing, wrong or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Too many requests, or a plan limit was reached. Wait for `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Nothing with this id belongs to the key's account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"The request is invalid; the code and `fields` say why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}}},"webhooks":{"dub.completed":{"post":{"tags":["Webhooks"],"summary":"Dub completed","description":"A dub finished, and its files are ready to download.","operationId":"_eventdub_completed_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DubEvent"},"examples":{"dub.completed":{"value":{"id":"evt_9a3e1c7b5d2f8e0a4c6b1d3f","type":"dub.completed","created_at":"2026-10-28T09:31:03Z","data":{"dub":{"id":"job_3f9a2c7e1b4d","object":"dub","status":"completed","stage":null,"progress":100,"source_language":"en","target_language":"fr","audio":false,"test":false,"preview_seconds":null,"duration_minutes":12.4,"created_at":"2026-10-28T09:12:44","started_at":"2026-10-28T09:12:51","completed_at":"2026-10-28T09:31:02","error":null,"metadata":{"order_id":"1042"}}}}}}}},"required":true},"responses":{"200":{"description":"Any 2xx within 10 seconds."}},"security":[],"parameters":[{"name":"Reedapt-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`, keyed with the endpoint's secret. Refuse a timestamp more than 5 minutes old.","schema":{"type":"string"}}]}},"dub.failed":{"post":{"tags":["Webhooks"],"summary":"Dub failed","description":"A dub failed or was cancelled; `data.dub.error` says why. Its minutes were returned.","operationId":"_eventdub_failed_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DubEvent"},"examples":{"dub.failed":{"value":{"id":"evt_2c7d9e1f3a5b8c0d4e6f1a2b","type":"dub.failed","created_at":"2026-10-28T09:14:11Z","data":{"dub":{"id":"job_3f9a2c7e1b4d","object":"dub","status":"failed","stage":null,"progress":20,"source_language":"en","target_language":"fr","audio":false,"test":false,"preview_seconds":null,"duration_minutes":null,"created_at":"2026-10-28T09:12:44","started_at":"2026-10-28T09:12:51","completed_at":"2026-10-28T09:14:10","error":{"code":"NO_SPEECH","message":"We couldn't find any speech in this file, so there was nothing to dub."},"metadata":{"order_id":"1042"}}}}}}}},"required":true},"responses":{"200":{"description":"Any 2xx within 10 seconds."}},"security":[],"parameters":[{"name":"Reedapt-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`, keyed with the endpoint's secret. Refuse a timestamp more than 5 minutes old.","schema":{"type":"string"}}]}},"batch.completed":{"post":{"tags":["Webhooks"],"summary":"Batch completed","description":"Every dub of a bulk upload made in the Reedapt app has finished.","operationId":"_eventbatch_completed_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchEvent"},"examples":{"batch.completed":{"value":{"id":"evt_5e8f1a3c7b9d2e4f6a0c1b3d","type":"batch.completed","created_at":"2026-10-28T11:02:40Z","data":{"batch":{"batch_id":"bat_7d2e9f1a3c5b","status":"completed","video_count":2,"jobs_total":2,"jobs_done":2,"jobs_failed":0,"dubs":[{"id":"job_3f9a2c7e1b4d","object":"dub","status":"completed","stage":null,"progress":100,"source_language":"en","target_language":"fr","audio":false,"test":false,"preview_seconds":null,"duration_minutes":12.4,"created_at":"2026-10-28T09:12:44","started_at":"2026-10-28T09:12:51","completed_at":"2026-10-28T09:31:02","error":null,"metadata":{"order_id":"1042"}}]}}}}}}},"required":true},"responses":{"200":{"description":"Any 2xx within 10 seconds."}},"security":[],"parameters":[{"name":"Reedapt-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`, keyed with the endpoint's secret. Refuse a timestamp more than 5 minutes old.","schema":{"type":"string"}}]}}},"components":{"schemas":{"BatchEvent":{"properties":{"id":{"type":"string","title":"Id","description":"The same on every retry and every endpoint. Use it to skip repeats.","examples":["evt_9a3e1c7b5d2f8e0a4c6b1d3f"]},"type":{"type":"string","const":"batch.completed","title":"Type"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"data":{"$ref":"#/components/schemas/BatchEventData"}},"type":"object","required":["id","type","created_at","data"],"title":"BatchEvent"},"BatchEventData":{"properties":{"batch":{"$ref":"#/components/schemas/BatchSummary"}},"type":"object","required":["batch"],"title":"BatchEventData"},"BatchSummary":{"properties":{"batch_id":{"type":"string","title":"Batch Id"},"status":{"type":"string","title":"Status"},"video_count":{"type":"integer","title":"Video Count"},"jobs_total":{"type":"integer","title":"Jobs Total"},"jobs_done":{"type":"integer","title":"Jobs Done"},"jobs_failed":{"type":"integer","title":"Jobs Failed"},"dubs":{"items":{"$ref":"#/components/schemas/Dub"},"type":"array","title":"Dubs"}},"type":"object","required":["batch_id","status","video_count","jobs_total","jobs_done","jobs_failed","dubs"],"title":"BatchSummary"},"CompleteIn":{"properties":{"parts":{"items":{"$ref":"#/components/schemas/UploadedPart"},"type":"array","maxItems":10000,"minItems":1,"title":"Parts","description":"Every part, once each."}},"type":"object","required":["parts"],"title":"CompleteIn"},"Dub":{"properties":{"id":{"type":"string","title":"Id","examples":["job_3f9a2c7e1b4d"]},"object":{"type":"string","const":"dub","title":"Object","default":"dub"},"status":{"type":"string","enum":["queued","processing","completed","failed"],"title":"Status","description":"`completed` and `failed` are final.","examples":["processing"]},"stage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage","description":"What a processing dub is doing now. For display only; the values may change.","examples":["transcribing"]},"progress":{"type":"integer","title":"Progress","description":"0 to 100.","examples":[35]},"source_language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Language","examples":["en"]},"target_language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Language","examples":["fr"]},"audio":{"type":"boolean","title":"Audio","description":"Whether the dub is audio (made from an audio file) rather than video."},"test":{"type":"boolean","title":"Test","description":"Made with a test key: only the first seconds are dubbed, with a watermark."},"preview_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Preview Seconds","description":"For a test dub, how many seconds were dubbed.","examples":[60]},"duration_minutes":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Duration Minutes","description":"The media's length, which is what the dub costs.","examples":[12.4]},"created_at":{"type":"string","format":"date-time","title":"Created At"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"error":{"anyOf":[{"$ref":"#/components/schemas/DubError"},{"type":"null"}],"description":"Why a failed dub failed."},"metadata":{"additionalProperties":{"type":"string"},"type":"object","title":"Metadata","description":"What you sent when creating it.","examples":[{"order_id":"1042"}]}},"type":"object","required":["id","status","progress","source_language","target_language","audio","test","created_at"],"title":"Dub","example":{"audio":false,"completed_at":"2026-10-28T09:31:02","created_at":"2026-10-28T09:12:44","duration_minutes":12.4,"id":"job_3f9a2c7e1b4d","metadata":{"order_id":"1042"},"object":"dub","progress":100,"source_language":"en","started_at":"2026-10-28T09:12:51","status":"completed","target_language":"fr","test":false}},"DubError":{"properties":{"code":{"type":"string","title":"Code","examples":["NO_SPEECH"]},"message":{"type":"string","title":"Message","examples":["We couldn't hear anyone speaking in this file."]}},"type":"object","required":["code","message"],"title":"DubError"},"DubEvent":{"properties":{"id":{"type":"string","title":"Id","description":"The same on every retry and every endpoint. Use it to skip repeats.","examples":["evt_9a3e1c7b5d2f8e0a4c6b1d3f"]},"type":{"type":"string","enum":["dub.completed","dub.failed"],"title":"Type"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"data":{"$ref":"#/components/schemas/DubEventData"}},"type":"object","required":["id","type","created_at","data"],"title":"DubEvent"},"DubEventData":{"properties":{"dub":{"$ref":"#/components/schemas/Dub"}},"type":"object","required":["dub"],"title":"DubEventData"},"DubFiles":{"properties":{"files":{"items":{"$ref":"#/components/schemas/FileLink"},"type":"array","title":"Files"},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"When the links stop working."}},"type":"object","required":["files","expires_at"],"title":"DubFiles"},"DubIn":{"properties":{"upload_id":{"type":"string","title":"Upload Id","description":"An upload whose status is `uploaded`.","examples":["job_3f9a2c7e1b4d"]},"source_language":{"type":"string","maxLength":10,"minLength":2,"title":"Source Language","description":"The language spoken in the file, as a code like `en`.","examples":["en"]},"target_languages":{"items":{"type":"string"},"type":"array","maxItems":30,"minItems":1,"title":"Target Languages","description":"One dub is made per language. GET /languages lists the codes your plan has.","examples":[["fr","es"]]},"voice_cloning":{"type":"boolean","title":"Voice Cloning","description":"Dub in each speaker's own voice.","default":true},"num_speakers":{"anyOf":[{"type":"integer","maximum":20.0,"minimum":1.0},{"type":"null"}],"title":"Num Speakers","description":"How many people speak, if you know. Left out, we work it out.","examples":[2]},"media_category":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Media Category","description":"What the media is, e.g. `film` or `sermon`. Optional."},"metadata":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Metadata","description":"Your own keys and values, up to 2 KB, returned on the dub and its webhooks.","examples":[{"order_id":"1042"}]}},"type":"object","required":["upload_id","source_language","target_languages"],"title":"DubIn"},"DubList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Dub"},"type":"array","title":"Data"},"has_more":{"type":"boolean","title":"Has More","description":"Whether another page follows; ask with a larger offset."}},"type":"object","required":["data","has_more"],"title":"DubList"},"DubsCreated":{"properties":{"dubs":{"items":{"$ref":"#/components/schemas/Dub"},"type":"array","title":"Dubs","description":"One per language that started."},"refused":{"items":{"$ref":"#/components/schemas/RefusedLanguage"},"type":"array","title":"Refused","description":"Languages that could not be started. Nothing is reserved for them."}},"type":"object","required":["dubs"],"title":"DubsCreated"},"ErrorBody":{"properties":{"error":{"$ref":"#/components/schemas/ErrorDetail"}},"type":"object","required":["error"],"title":"ErrorBody"},"ErrorDetail":{"properties":{"code":{"type":"string","title":"Code","description":"Stable and machine-readable; branch on this, not on the message.","examples":["UPLOAD_INCOMPLETE"]},"message":{"type":"string","title":"Message","description":"What went wrong and what to do, in plain English.","examples":["Complete the upload before dubbing it."]},"request_id":{"type":"string","title":"Request Id","description":"Quote it when you contact us about this request.","examples":["req_8f2c1e0a9b7d6c5e4f3a"]},"fields":{"anyOf":[{"items":{"additionalProperties":{"type":"string"},"type":"object"},"type":"array"},{"type":"null"}],"title":"Fields","description":"On INVALID_REQUEST from validation: each field that is missing or invalid.","examples":[[{"field":"target_languages","message":"Field required"}]]}},"type":"object","required":["code","message","request_id"],"title":"ErrorDetail"},"FileLink":{"properties":{"kind":{"type":"string","enum":["dub","dubbed_audio_wav","voice","background"],"title":"Kind","description":"`dub`: the dubbed video (or audio). For audio dubs made with a live key, also `dubbed_audio_wav`, and the separated `voice` and `background` tracks.","examples":["dub"]},"filename":{"type":"string","title":"Filename","examples":["interview_dubbed_french.mp4"]},"url":{"type":"string","title":"Url","description":"A download link that lasts an hour."}},"type":"object","required":["kind","filename","url"],"title":"FileLink"},"Languages":{"properties":{"target_languages":{"items":{"type":"string"},"type":"array","title":"Target Languages","description":"Codes this key's plan can dub into right now.","examples":[["de","en","es","fr","it","ja","ko","pt","ru","zh"]]}},"type":"object","required":["target_languages"],"title":"Languages"},"PartLink":{"properties":{"part_number":{"type":"integer","title":"Part Number","description":"Counts from 1.","examples":[1]},"url":{"type":"string","title":"Url","description":"PUT this part's bytes here, with no extra headers. Keep the response's ETag.","examples":["https://…"]}},"type":"object","required":["part_number","url"],"title":"PartLink"},"PartsIn":{"properties":{"part_numbers":{"items":{"type":"integer"},"type":"array","maxItems":100,"minItems":1,"title":"Part Numbers","description":"Up to 100 part numbers per request.","examples":[[11,12,13]]}},"type":"object","required":["part_numbers"],"title":"PartsIn"},"Queued":{"properties":{"queued":{"type":"boolean","title":"Queued","default":true}},"type":"object","title":"Queued"},"RefusedLanguage":{"properties":{"target_language":{"type":"string","title":"Target Language","examples":["sw"]},"code":{"type":"string","title":"Code","examples":["LANGUAGE_NOT_IN_PLAN"]},"message":{"type":"string","title":"Message","examples":["Your plan doesn't include this language."]}},"type":"object","required":["target_language","code","message"],"title":"RefusedLanguage"},"Upload":{"properties":{"id":{"type":"string","title":"Id","description":"The upload's id. Its dubs are created from it.","examples":["job_3f9a2c7e1b4d"]},"object":{"type":"string","const":"upload","title":"Object","default":"upload"},"filename":{"type":"string","title":"Filename","examples":["interview.mp4"]},"size":{"type":"integer","title":"Size","description":"In bytes. 0 while a source_url is still downloading.","examples":[734003200]},"status":{"type":"string","enum":["uploading","fetching","uploaded","used","failed"],"title":"Status","description":"`uploading`: waiting for its parts. `fetching`: we're downloading source_url. `uploaded`: ready to dub. `used`: already dubbed. `failed`: see `error`.","examples":["uploading"]},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why a fetch from source_url failed."},"part_size":{"type":"integer","title":"Part Size","description":"Every part but the last is exactly this many bytes.","examples":[16777216]},"part_count":{"type":"integer","title":"Part Count","examples":[44]},"parts":{"items":{"$ref":"#/components/schemas/PartLink"},"type":"array","title":"Parts","description":"Signed links for the first parts. Ask for the rest with /parts."},"parts_expire_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Parts Expire At","description":"When the links in `parts` stop working (15 minutes)."}},"type":"object","required":["id","filename","size","status","part_size","part_count"],"title":"Upload"},"UploadIn":{"properties":{"filename":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Filename","description":"The file's name, with its extension. With source_url, only if the link doesn't end in one.","examples":["interview.mp4"]},"size":{"anyOf":[{"type":"integer","exclusiveMinimum":0.0},{"type":"null"}],"title":"Size","description":"The file's size in bytes (not with source_url).","examples":[734003200]},"source_url":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"title":"Source Url","description":"A public http(s) link to the file. We download it ourselves; poll the upload until it is `uploaded`.","examples":["https://example.com/media/interview.mp4"]}},"type":"object","title":"UploadIn"},"UploadedPart":{"properties":{"part_number":{"type":"integer","minimum":1.0,"title":"Part Number","examples":[1]},"etag":{"type":"string","maxLength":128,"minLength":1,"title":"Etag","description":"The ETag header from that part's PUT response, as sent.","examples":["\"9b2cf535f27731c974343645a3985328\""]}},"type":"object","required":["part_number","etag"],"title":"UploadedPart"},"Usage":{"properties":{"plan":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plan","examples":["pro"]},"key_mode":{"type":"string","enum":["live","test"],"title":"Key Mode"},"minutes_remaining":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Minutes Remaining","description":"Minutes left to dub with. Null when unlimited.","examples":[184.5]},"unlimited":{"type":"boolean","title":"Unlimited"},"test_preview_seconds":{"type":"integer","title":"Test Preview Seconds","description":"How many seconds a test key dubs.","examples":[60]}},"type":"object","required":["key_mode","unlimited","test_preview_seconds"],"title":"Usage"},"WebhookDeliveryList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/WebhookDeliveryOut"},"type":"array","title":"Data"}},"type":"object","required":["data"],"title":"WebhookDeliveryList"},"WebhookDeliveryOut":{"properties":{"id":{"type":"string","title":"Id","examples":["whd_0b8e3f1a6c2d9e4f7a5b1c3d"]},"event_id":{"type":"string","title":"Event Id","description":"The same for every endpoint the event went to. Use it to skip repeats.","examples":["evt_9a3e1c7b5d2f8e0a4c6b1d3f"]},"event_type":{"type":"string","title":"Event Type","examples":["dub.completed"]},"status":{"type":"string","enum":["pending","delivered","failed"],"title":"Status","description":"`pending` is still being tried; `failed` means every retry was used."},"attempts":{"type":"integer","title":"Attempts","examples":[1]},"last_status_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Status Code","description":"What your endpoint answered last time.","examples":[200]},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error"},"next_attempt_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Attempt At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"delivered_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Delivered At"}},"type":"object","required":["id","event_id","event_type","status","attempts","created_at"],"title":"WebhookDeliveryOut"},"WebhookEndpointIn":{"properties":{"url":{"type":"string","maxLength":2048,"title":"Url","description":"A public https:// address.","examples":["https://example.com/reedapt/webhooks"]},"description":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Description","examples":["Production"]}},"type":"object","required":["url"],"title":"WebhookEndpointIn"},"WebhookEndpointList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/WebhookEndpointOut"},"type":"array","title":"Data"}},"type":"object","required":["data"],"title":"WebhookEndpointList"},"WebhookEndpointOut":{"properties":{"id":{"type":"string","title":"Id","examples":["we_5d1c9a2f7b3e8a40"]},"object":{"type":"string","const":"webhook_endpoint","title":"Object","default":"webhook_endpoint"},"url":{"type":"string","title":"Url","examples":["https://example.com/reedapt/webhooks"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"events":{"items":{"type":"string"},"type":"array","title":"Events","description":"Every endpoint gets every event.","examples":[["dub.completed","dub.failed","batch.completed"]]},"created_at":{"type":"string","format":"date-time","title":"Created At"},"secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Secret","description":"Only in the answer that makes the endpoint: keep it to check signatures.","examples":["whsec_…"]}},"type":"object","required":["id","url","events","created_at"],"title":"WebhookEndpointOut"}},"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"`rk_live_…` or `rk_test_…`, made in Reedapt under Developers."}}},"security":[{"apiKey":[]}]}