{"openapi":"3.0.0","info":{"title":"Xora Transcoding API","version":"1.0.0","description":"High-performance programmatic video transcoding, resizing, audio extraction, and clipping powered by AWS Lambda and Cloudflare Workers."},"servers":[{"url":"https://api.xora.sh/v1","description":"Production"}],"components":{"schemas":{"JobOutput":{"type":"object","properties":{"id":{"type":"string","description":"Platform job ID","example":"01KSD388ERH85FGR79JCW0SZZ0"},"state":{"type":"string","description":"Current state of the job","example":"queued"}},"required":["id","state"]},"ErrorResponse":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message"]}},"required":["error"]},"ProbeJobInput":{"type":"object","properties":{"mode":{"type":"string","enum":["probe"],"description":"Probe-only job. Runs ffprobe and returns enriched metadata on the job record. No output file is written.","example":"probe"},"input":{"type":"object","properties":{"url":{"type":"string","format":"uri"}},"required":["url"],"description":"HTTPS or S3 URL of the media file to inspect"},"webhookUrl":{"type":"string","format":"uri"},"stageInput":{"type":"boolean","description":"When false, skip copying inputs to engine storage if the origin supports HTTP byte-range requests. ffmpeg reads your URLs directly (faster for extractAudio, thumbnails, and long transcodes). Defaults to true; staging still runs when ranges are unsupported.","example":false}},"required":["mode","input"]},"CreateJobBody":{"anyOf":[{"$ref":"#/components/schemas/ProbeJobInput"},{"type":"object","properties":{"mode":{"type":"string","enum":["recipe"],"example":"recipe"},"input":{"type":"object","properties":{"url":{"type":"string","format":"uri"}},"required":["url"],"description":"HTTPS or S3 URL of the input media file"},"input_files":{"type":"object","additionalProperties":{"type":"string","format":"uri"}},"output":{"type":"object","properties":{"format":{"type":"string","enum":["mp4","mp3","jpg","png","gif","webm","mov","aac","wav"],"description":"Target output format. For most recipes this is the delivered container/codec. For extractAudio, this is the transcode fallback (mp3, aac, or wav) used only when the source audio cannot be stream-copied; the actual file extension may be .aac, .opus, .flac, etc. when copy applies. See /docs/recipes/extract-audio.","example":"mp4"}},"required":["format"],"description":"Output settings. For extractAudio, format is the transcode fallback (mp3, aac, or wav), not necessarily the delivered extension."},"recipe":{"type":"object","properties":{"name":{"type":"string","enum":["compress","transcode","resize","trim","thumbnail","extractAudio","concat","webReady","proxy"],"description":"Recipe to run. webReady: optimize for web delivery (passthrough, faststart remux, or web encode). proxy: always re-encode for timeline editing. extractAudio: strip video, stream-copy when possible else transcode to output.format.","example":"compress"},"crf":{"type":"number","description":"compress / transcode / webReady / proxy: constant rate factor (lower = higher quality)"},"bitrateKbps":{"type":"number","description":"webReady / proxy: optional target video bitrate in Kbps; when set, overrides CRF"},"width":{"type":"number","description":"resize / webReady / proxy: output width in pixels"},"height":{"type":"number","description":"resize / webReady / proxy: output height in pixels"},"scaleDown":{"type":"boolean","description":"webReady / proxy: downscale only when true"},"gop":{"type":"string","enum":["per_second","all_intra"],"description":"proxy recipe only: per_second (1 keyframe/sec) or all_intra (every frame a keyframe, best scrubbing)","example":"per_second"},"startSeconds":{"type":"number","description":"trim / extractAudio / webReady / proxy: start offset in seconds"},"durationSeconds":{"type":"number","description":"trim / extractAudio / webReady / proxy: duration limit in seconds"},"seekSeconds":{"type":"number","description":"thumbnail: frame position in seconds"}},"required":["name"],"description":"Recipe parameters. webReady and proxy support optional scaleDown, width, height, crf, and bitrateKbps. proxy also supports gop."},"webhookUrl":{"type":"string","format":"uri"},"outputPath":{"type":"string","minLength":1,"maxLength":1024,"pattern":"^[a-zA-Z0-9_\\-/.]+$","description":"Optional custom output path relative to your storage provider prefix (e.g., exports/my-video.mp4). For extractAudio you may omit the file extension (e.g., partner/asset/audio); Xora appends the resolved extension after probing. For other recipes the extension must match output.format. Requires a default BYOB storage provider.","example":"exports/my-video.mp4"},"stageInput":{"type":"boolean","description":"When false, skip copying inputs to engine storage if the origin supports HTTP byte-range requests. ffmpeg reads your URLs directly (faster for extractAudio, thumbnails, and long transcodes). Defaults to true; staging still runs when ranges are unsupported.","example":false}},"required":["mode","output","recipe"]},{"type":"object","properties":{"mode":{"type":"string","enum":["ffmpeg"]},"input":{"type":"object","properties":{"url":{"type":"string","format":"uri"}},"required":["url"]},"output":{"type":"object","properties":{"format":{"type":"string","enum":["mp4","mp3","jpg","png","gif","webm","mov","aac","wav"],"description":"Target output format. For most recipes this is the delivered container/codec. For extractAudio, this is the transcode fallback (mp3, aac, or wav) used only when the source audio cannot be stream-copied; the actual file extension may be .aac, .opus, .flac, etc. when copy applies. See /docs/recipes/extract-audio.","example":"mp4"}},"required":["format"]},"input_files":{"type":"object","additionalProperties":{"type":"string"}},"output_files":{"type":"object","additionalProperties":{"type":"string","pattern":"^[a-zA-Z0-9_]+\\.[a-zA-Z0-9]+$"}},"ffmpeg":{"type":"object","properties":{"args":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":128}},"required":["args"]},"webhookUrl":{"type":"string","format":"uri"},"outputPath":{"type":"string","minLength":1,"maxLength":1024,"pattern":"^[a-zA-Z0-9_\\-/.]+$","description":"Optional custom output path relative to your storage provider prefix (e.g., exports/my-video.mp4). For extractAudio you may omit the file extension (e.g., partner/asset/audio); Xora appends the resolved extension after probing. For other recipes the extension must match output.format. Requires a default BYOB storage provider.","example":"exports/my-video.mp4"},"stageInput":{"type":"boolean","description":"When false, skip copying inputs to engine storage if the origin supports HTTP byte-range requests. ffmpeg reads your URLs directly (faster for extractAudio, thumbnails, and long transcodes). Defaults to true; staging still runs when ranges are unsupported.","example":false}},"required":["mode","ffmpeg"]},{"type":"object","properties":{"presetId":{"type":"string","minLength":1,"description":"Run a saved preset; provide input at job time"},"input":{"type":"object","properties":{"url":{"type":"string","format":"uri"}},"required":["url"],"description":"Input URL when preset omits input (recipe presets, legacy ffmpeg)"},"input_files":{"type":"object","additionalProperties":{"type":"string","format":"uri"},"description":"Input file URLs for multi-file ffmpeg presets"},"webhookUrl":{"type":"string","format":"uri"},"outputPath":{"type":"string","minLength":1,"maxLength":1024,"pattern":"^[a-zA-Z0-9_\\-/.]+$","description":"Optional custom output path relative to your storage provider prefix (e.g., exports/my-video.mp4). For extractAudio you may omit the file extension (e.g., partner/asset/audio); Xora appends the resolved extension after probing. For other recipes the extension must match output.format. Requires a default BYOB storage provider.","example":"exports/my-video.mp4"},"stageInput":{"type":"boolean","description":"When false, skip copying inputs to engine storage if the origin supports HTTP byte-range requests. ffmpeg reads your URLs directly (faster for extractAudio, thumbnails, and long transcodes). Defaults to true; staging still runs when ranges are unsupported.","example":false}},"required":["presetId"]}]},"JobRow":{"type":"object","properties":{"id":{"type":"string"},"user_id":{"type":"string"},"token_id":{"type":"string","nullable":true},"mode":{"type":"string"},"recipe_name":{"type":"string","nullable":true},"input_url":{"type":"string"},"created_at":{"type":"string"},"state":{"type":"string"},"progress":{"type":"number","nullable":true},"completed_at":{"type":"string","nullable":true},"duration_seconds":{"type":"number","nullable":true},"error_code":{"type":"string","nullable":true},"preset_id":{"type":"string","nullable":true},"preset_name":{"type":"string","nullable":true}},"required":["id","user_id","token_id","mode","recipe_name","input_url","created_at","state","completed_at","duration_seconds","error_code"]},"ListJobsResponse":{"type":"object","properties":{"jobs":{"type":"array","items":{"$ref":"#/components/schemas/JobRow"}},"limit":{"type":"number"},"offset":{"type":"number"}},"required":["jobs","limit","offset"]},"JobStatusResponse":{"type":"object","properties":{"id":{"type":"string"},"state":{"type":"string"},"progress":{"type":"number"},"mode":{"type":"string"},"error":{"type":"object","nullable":true,"properties":{"code":{"type":"string"},"message":{"nullable":true},"stage":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code"]},"output":{"type":"object","nullable":true,"properties":{"signedUrl":{"type":"string"}}},"outputs":{"type":"object","nullable":true,"additionalProperties":{"type":"object","properties":{"signedUrl":{"type":"string"}}}},"request":{"nullable":true},"probe":{"type":"object","additionalProperties":{"nullable":true},"description":"Enriched ffprobe metadata when mode is probe and the job has completed probing"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"preset_id":{"type":"string","nullable":true},"preset_name":{"type":"string","nullable":true}},"required":["id","state"]},"CancelResponse":{"type":"object","properties":{"cancelled":{"type":"boolean","example":true}},"required":["cancelled"]},"RetryResponse":{"type":"object","properties":{"retried":{"type":"boolean","example":true},"id":{"type":"string","example":"01KSD388ERH85FGR79JCW0SZZ0"}},"required":["retried","id"]},"DeleteOutputResponse":{"type":"object","properties":{"id":{"type":"string"},"deleted":{"type":"boolean"},"keys":{"type":"array","items":{"type":"string"}}},"required":["id","deleted"]},"FileResponse":{"type":"object","properties":{"file_id":{"type":"string"},"job_id":{"type":"string"},"alias":{"type":"string","nullable":true},"filename":{"type":"string"},"role":{"type":"string"},"size_mbytes":{"type":"number","nullable":true},"duration":{"type":"number","nullable":true},"file_type":{"type":"string","nullable":true},"file_format":{"type":"string","nullable":true},"mime_type":{"type":"string","nullable":true},"codec":{"type":"string","nullable":true},"width":{"type":"number","nullable":true},"height":{"type":"number","nullable":true},"storage_url":{"type":"string","nullable":true},"is_deleted":{"type":"boolean"},"created_at":{"type":"string"}},"required":["file_id","job_id","alias","filename","role","size_mbytes","duration","file_type","file_format","mime_type","codec","width","height","storage_url","is_deleted","created_at"]},"ListFilesResponse":{"type":"object","properties":{"files":{"type":"array","items":{"$ref":"#/components/schemas/FileResponse"}},"limit":{"type":"number"},"offset":{"type":"number"}},"required":["files","limit","offset"]},"FilesErrorResponse":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message"]}},"required":["error"]},"DeleteFileResponse":{"type":"object","properties":{"file_id":{"type":"string"},"deleted":{"type":"boolean"}},"required":["file_id","deleted"]}},"parameters":{}},"paths":{"/api/v1/jobs":{"post":{"summary":"Create Transcoding Job","description":"Submit a new job. Use `mode: \"probe\"` to inspect media metadata (no output file). Recipe mode supports compress, transcode, resize, trim, thumbnail, extractAudio (stream-copy audio when possible, else transcode to output.format), concat, webReady (web delivery: passthrough, faststart remux, or web encode), and proxy (always re-encode for timeline editing). Returns 202 immediately while processing runs asynchronously.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateJobBody"}}}},"responses":{"202":{"description":"Job accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobOutput"}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"summary":"List Jobs","description":"Fetch list of transcoding jobs for the authorized user.","parameters":[{"schema":{"type":"number","nullable":true,"default":20,"description":"Limit returned items (max 100)","example":20},"required":false,"name":"limit","in":"query"},{"schema":{"type":"number","nullable":true,"default":0,"description":"Offset returned items","example":0},"required":false,"name":"offset","in":"query"},{"schema":{"type":"string","enum":["queued","probing","planning","segmenting","transcoding","merging","completed","failed","rejected","cancelled"],"description":"Filter by job state","example":"completed"},"required":false,"name":"state","in":"query"}],"responses":{"200":{"description":"List of jobs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListJobsResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/jobs/{id}":{"get":{"summary":"Get Job Status","description":"Returns job state and outputs. Running jobs poll the engine; terminal jobs are served from platform storage.","parameters":[{"schema":{"type":"string","description":"Unique Job ID","example":"01KSD388ERH85FGR79JCW0SZZ0"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Job status retrieved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatusResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/jobs/{id}/cancel":{"post":{"summary":"Cancel Job","description":"Cancel an active or queued transcoding job in progress.","parameters":[{"schema":{"type":"string","description":"Unique Job ID","example":"01KSD388ERH85FGR79JCW0SZZ0"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Job cancelled successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/jobs/{id}/retry":{"post":{"summary":"Retry Job","description":"Retry a failed, rejected, or cancelled job using original input parameters.","parameters":[{"schema":{"type":"string","description":"Unique Job ID","example":"01KSD388ERH85FGR79JCW0SZZ0"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Job retried successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetryResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/jobs/{id}/output":{"delete":{"summary":"Delete Job Output","description":"Delete hosted output files from storage. Outputs persist until explicitly deleted.","parameters":[{"schema":{"type":"string","description":"Unique Job ID","example":"01KSD388ERH85FGR79JCW0SZZ0"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Output deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteOutputResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Job or output not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/files":{"get":{"summary":"List stored files","description":"List output files for the authenticated account. Optionally filter by job ID.","parameters":[{"schema":{"type":"number","nullable":true,"default":20,"example":20},"required":false,"name":"limit","in":"query"},{"schema":{"type":"number","nullable":true,"default":0,"example":0},"required":false,"name":"offset","in":"query"},{"schema":{"type":"string","description":"Filter by job ID"},"required":false,"name":"jobId","in":"query"}],"responses":{"200":{"description":"File list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListFilesResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FilesErrorResponse"}}}}}}},"/api/v1/files/{id}":{"get":{"summary":"Get file metadata","description":"Returns file metadata and a fresh signed download URL.","parameters":[{"schema":{"type":"string","example":"01KSD388ERH85FGR79JCW0SZZ0"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"File metadata","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FilesErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FilesErrorResponse"}}}}}},"delete":{"summary":"Delete stored file","description":"Delete a single output file from storage.","parameters":[{"schema":{"type":"string","example":"01KSD388ERH85FGR79JCW0SZZ0"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteFileResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FilesErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FilesErrorResponse"}}}}}}}}}