# Perso AI API Documentation API Base URL: https://api.perso.ai Service/File URL: https://portal-media.perso.ai Authentication: Include `XP-API-KEY` header in every request. API Key format: pk_live_xxxxxxxxxxxxxxxxxxxx Example: ``` curl -X GET https://api.perso.ai/video-translator/api/v1/languages \ -H "XP-API-KEY: pk_live_xxxxxxxxxxxxxxxxxxxx" ``` --- ## CRITICAL RULES (read before writing any code) These are the most common failure points. Apply them by default. 1. **Authentication header is `XP-API-KEY`, not `Authorization: Bearer`.** - Every request to https://api.perso.ai must include `XP-API-KEY: `. - Bearer JWT is never used for this API. 2. **Multi-tenancy via `spaceSeq`.** - Most domain endpoints require a `spaceSeq` (Space ID) in the request body or query. - Missing `spaceSeq` is the #1 cause of empty lists or 404s — when in doubt, pass it explicitly. 3. **File upload is a 3-step flow, not a single call.** Do NOT try to attach binary files to Dubbing/STT/Lip-Sync requests directly. First produce a `mediaSeq`: ``` Step 1: GET /file/api/upload/sas-token?fileName= -> { blobSasUrl } (valid 30 minutes) Step 2: PUT {blobSasUrl} (direct to Azure Blob, NOT https://api.perso.ai) Headers: x-ms-blob-type: BlockBlob Body: raw file bytes DO NOT send XP-API-KEY on this request. 201 on success. 403 = SAS expired, go back to Step 1. Step 3: POST /file/api/upload/video (or /audio) Body: { "fileUrl": "", ... } -> { "seq": } // use this as mediaSeq in downstream APIs ``` For YouTube/TikTok/Drive URLs, use the External flow instead: `external/metadata` → `media/validate` → `upload/video/external`. 4. **Long-running jobs return an ID — poll it.** - Dubbing, STT, Lip-Sync, TTS create a project and return immediately with a `projectId` (status `PENDING`). - Poll `GET /.../projects/{projectId}` every 2–5 seconds (exponential backoff recommended) until status is `COMPLETED` or `FAILED`. - Never block an HTTP handler on the polling loop — use a background worker/queue. 5. **Response file paths resolve against `https://portal-media.perso.ai`, not the API base.** - Fields like `videoFilePath`, `audioFilePath`, `thumbnailFilePath` contain relative paths under `/perso-storage/...`. - Prepend `https://portal-media.perso.ai` (not `https://api.perso.ai`) to download the file. 6. **HTTP error codes follow a predictable map.** - 401 = `XP-API-KEY` header missing, misspelled, or key is Expired/Revoked. - 403 = key is valid but lacks permission on this Space. - 404 = usually `spaceSeq` or resource ID wrong. Verify the IDs before assuming the endpoint is broken. - 429 = rate limited. Back off exponentially. - 5xx = retry with backoff; include the Request ID from Usage logs when escalating. 7. **`fileName` for SAS token must be URL-encoded.** Spaces, Korean/Japanese characters, parentheses all need encoding. --- ## Space API Retrieve space banner information. ### GET /portal/api/v1/spaces **List Space Banners** Retrieve the list of space banners for all spaces the authenticated user belongs to. Response (200): ```json { "result": [ { "spaceSeq": 1, "spaceName": "My Workspace", "planName": "Pro", "tier": "team", "logo": "https://...", "memberCount": 5, "seat": 10, "isDefaultSpaceOwned": true, "memberRole": "space_owner", "useVideoTranslatorEdit": true } ] } ``` Errors: - 404 PT0026: Space subscription info not found --- ### GET /portal/api/v1/spaces/{spaceSeq} **Get Space Banner** Retrieve the banner information for a specific space. Path parameters: - spaceSeq (integer, required): The unique identifier of the space. Response (200): ```json { "result": { "spaceSeq": 1, "spaceName": "My Workspace", "planName": "Pro", "tier": "team", "logo": "https://...", "memberCount": 5, "seat": 10, "isDefaultSpaceOwned": true, "memberRole": "space_owner", "useVideoTranslatorEdit": true } } ``` Errors: - 404 PT0026: Space subscription info not found - 401 PT0027: Not an approved member --- ## Media API Upload video and audio files, validate media, manage external media imports, and download project output files. ### Direct File Upload (Video / Audio) Uploading a file requires a multi-step process: obtain a temporary SAS token, upload the binary to Azure Blob Storage, then register the uploaded file with the server. **Step 1: Get SAS Token** [GET] Auth: XP-API-KEY `/file/api/upload/sas-token?fileName={fileName}` Call the Get SAS Token endpoint to obtain a blobSasUrl. The token is valid for 30 minutes. > The fileName must be URL-encoded. **Step 2: Upload Binary to Azure Blob Storage** [PUT] Auth: None `{blobSasUrl}` Upload the file binary directly to the blobSasUrl via a PUT request. The SAS URL already contains authentication, so no Authorization header is required. This request goes directly to Azure — not to the Perso API server. Required headers: x-ms-blob-type: BlockBlob Content-Type: application/octet-stream ``` curl -X PUT \ -H "x-ms-blob-type: BlockBlob" \ -H "Content-Type: application/octet-stream" \ --data-binary @"/path/to/file.mp4" \ "{blobSasUrl}" ``` > Returns 201 Created on success (empty body). A 403 means the SAS token has expired — call Get SAS Token again. **Step 3: Register Uploaded File** [PUT] Auth: XP-API-KEY `/file/api/upload/video (or /audio)` Call Upload Video or Upload Audio to register the file. Pass the blob URL (the path portion of blobSasUrl before the '?') as fileUrl. > The response includes a seq (media sequence) — use this as mediaSeq when requesting a translation via the Dubbing API. **Step 4: Validate Media (Optional)** [POST] Auth: XP-API-KEY `/file/api/v1/media/validate` Call Validate Media to pre-check file constraints (extension, size, duration, resolution) before upload. This is optional but recommended to fail fast instead of waiting for a long upload to fail. > Can be called before Step 2 to avoid uploading an invalid file. ### External Platform Upload (YouTube, TikTok, Google Drive) Import videos from external platforms. The server downloads the video on your behalf. **Step 1: Get External Metadata** [POST] Auth: XP-API-KEY `/file/api/v1/video-translator/external/metadata` Preview the media info (duration, resolution, size) before uploading. **Step 2: Validate Media** [POST] Auth: XP-API-KEY `/file/api/v1/media/validate` Check constraints (size, duration, resolution) against your plan limits. > Validates in 1-2 seconds. Skipping this step may result in a 10+ second wait before an error on invalid media. **Step 3: Upload External Video** [PUT] Auth: XP-API-KEY `/file/api/upload/video/external` Start the import. This is synchronous — the server downloads the file before responding (may take up to 10 minutes). > The response includes a seq for use as mediaSeq in the Dubbing API. ### PUT /file/api/upload/video **Upload Video** Upload a video file via URL. The server downloads the file from the given URL and stores it. Before calling this endpoint, you must first obtain a SAS token via the Get SAS Token endpoint and upload the file to the returned blobSasUrl. Pass the blob URL as the fileUrl parameter. The response includes a seq (media sequence) which is used as mediaSeq when requesting a translation. Note: videoFilePath and thumbnailFilePath in the response are relative paths containing perso-storage. To access the actual file, prepend https://portal-media.perso.ai (e.g. https://portal-media.perso.ai/perso-storage/.../video.mp4). Request body: - spaceSeq (integer, required): The unique identifier of the space. - fileUrl (string, required): Direct access URL of the video file. - fileName (string, required): File name. URL-encoded names are automatically decoded. Request example: ```json { "spaceSeq": 1, "fileUrl": "https://example.com/video.mp4", "fileName": "my_video.mp4" } ``` Response (200): ```json { "seq": 456, "originalName": "my_video", "videoFilePath": "/container/directory/uuid_20260219.mp4", "thumbnailFilePath": "/container/directory/uuid_20260219.webp", "size": 52428800, "durationMs": 30000 } ``` Errors: - 400 F4003: Missing required parameter - 400 F4004: File size limit exceeded - 400 F4007: Invalid video type - 401 F4001: Unauthorized --- ### PUT /file/api/upload/audio **Upload Audio** Upload an audio file via URL. The server downloads the file from the given URL and stores it. Before calling this endpoint, you must first obtain a SAS token via the Get SAS Token endpoint and upload the file to the returned blobSasUrl. Pass the blob URL as the fileUrl parameter. The response includes a seq (media sequence) which is used as mediaSeq when requesting a translation. Note: audioFilePath and thumbnailFilePath in the response are relative paths containing perso-storage. To access the actual file, prepend https://portal-media.perso.ai (e.g. https://portal-media.perso.ai/perso-storage/.../audio.mp3). Request body: - spaceSeq (integer, required): The unique identifier of the space. - fileUrl (string, required): Direct access URL of the audio file. - fileName (string, required): File name. URL-encoded names are automatically decoded. Request example: ```json { "spaceSeq": 1, "fileUrl": "https://portal-media.perso.ai/perso-storage/...", "fileName": "my_audio.mp3" } ``` Response (200): ```json { "seq": 789, "originalName": "my_audio", "audioFilePath": "/container/directory/uuid_20260219.mp3", "thumbnailFilePath": "audio_thumb.png", "size": 5242880, "durationMs": 180000 } ``` Errors: - 400 F4003: Missing required parameter - 400 F4004: File size limit exceeded - 400 F4007: Invalid audio type - 401 F4001: Unauthorized --- ### PUT /file/api/upload/video/external **Upload External Video** Upload a video from an external platform (YouTube, TikTok, Google Drive). This is a synchronous operation — the server waits for the download to complete before responding. Timeout may take up to 10 minutes. Note: videoFilePath, audioFilePath, and thumbnailFilePath in the response are relative paths containing perso-storage. To access the actual file, prepend https://portal-media.perso.ai (e.g. https://portal-media.perso.ai/perso-storage/.../video.mp4). Request body: - space_seq (integer, required): The unique identifier of the space. - url (string, required): External video URL (YouTube, TikTok, Google Drive). - lang (string, optional): Language code. (default: en) Request example: ```json { "space_seq": 1, "url": "https://www.youtube.com/watch?v=xxxxx", "lang": "ko" } ``` Response (200): ```json { "seq": 456, "originalName": "Video Title", "videoFilePath": "/container/directory/uuid_20260219.mp4", "thumbnailFilePath": "/container/directory/uuid_20260219.webp", "size": 52428800, "durationMs": 30000 } ``` Errors: - 400 F4003: Missing required parameter - 400 F4006: Not a valid external link - 400 F40012: Region unavailable external video - 400 F40014: Members only content - 400 F40015: Payment required content - 400 F40016: Invalid YouTube URL - 401 F4001: Unauthorized - 403 F4031: Unaccessible Google Drive link - 403 F4032: Geo-restricted YouTube video - 403 F4033: Age-restricted YouTube video - 403 F4035: Unaccessible external media link --- ### GET /file/api/upload/sas-token **Get SAS Token** Issue an Azure Blob Storage SAS token for direct file upload. This is the first step for uploading video or audio files. The token expires 30 minutes after issuance. Upload your file to the returned blobSasUrl via a PUT request before the expiration time, then call the Upload Video or Upload Audio endpoint with the blob URL. Query parameters: - fileName (string, required): File name (URL encoding required). Response (200): ```json { "blobSasUrl": "https://portal-media.perso.ai/{container}/{path}/uuid.mp4?sv=2024-11-04&se=2026-02-19T13%3A00%3A00Z&sr=b&sp=rw&sig=...", "expirationDatetime": "2026-02-19T13:00:00" } ``` Errors: - 400 F4003: Missing required parameter - 401 F4001: Unauthorized --- ### POST /file/api/v1/media/validate **Validate Media** Pre-validate media file metadata before upload. Checks extension, file size, duration, and resolution constraints without transferring the actual file. Request body: - spaceSeq (integer, required): The unique identifier of the space. - durationMs (integer, required): Media duration in milliseconds. - originalName (string, required): Original file name. - mediaType (string, required): Type of the media file. [video, audio] - extension (string, required): File extension. [.mp4, .webm, .mov, .mp3, .wav] - size (integer, optional): File size in bytes (max 2GB). - width (integer, optional): Video width in pixels. Required when mediaType is 'video'. Min 201, max 7999. - height (integer, optional): Video height in pixels. Required when mediaType is 'video'. Min 201, max 7999. - thumbnailFilePath (string, optional): Thumbnail file path. Request example: ```json { "spaceSeq": 1, "durationMs": 30000, "originalName": "video.mp4", "mediaType": "video", "extension": ".mp4", "size": 52428800, "width": 1920, "height": 1080, "thumbnailFilePath": null } ``` Response (200): ```json { "status": true } ``` Errors: - 400 F4003: Missing required parameter - 400 F4004: File size limit exceeded - 400 F4007: Invalid video/audio type - 400 F4008: Video duration limit exceeded - 400 F4009: Video duration too short (min 5s) - 400 F40010: Video resolution limit exceeded - 400 F40011: Video resolution too low - 401 F4001: Unauthorized - 403 F4005: Plan usage limit exceeded - 422 F4220: Validation failed --- ### POST /file/api/v1/video-translator/external/metadata **Get External Metadata** Retrieve metadata from an external platform video (YouTube, TikTok, Google Drive) without downloading the file. Use this to preview media info before uploading. Request body: - space_seq (integer, required): The unique identifier of the space. - url (string, required): External video URL (YouTube, TikTok, Google Drive). - lang (string, optional): Language code. (default: en) Request example: ```json { "space_seq": 1, "url": "https://www.youtube.com/watch?v=xxxxx", "lang": "ko" } ``` Response (200): ```json { "durationMs": 215000, "originalName": "Video Title", "thumbnailFilePath": "https://i.ytimg.com/vi/xxxxx/maxresdefault.jpg", "mediaType": "video", "size": 52428800, "extension": ".mp4", "width": 1920, "height": 1080 } ``` Errors: - 400 F4003: Missing required parameter - 400 F4006: Not a valid external link - 400 F40016: Invalid YouTube URL - 400 F40017: Invalid media URL - 401 F4001: Unauthorized - 403 F4031: Unaccessible Google Drive link - 403 F4035: Unaccessible external media link --- ### GET /video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq}/download-info **Check Download Availability** Before downloading files, check which files are currently available for download in your project. Each field returns true/false to indicate availability. Some fields may return null depending on the project type (video vs audio). Use the corresponding download target value for any field that returned true when calling the download endpoint. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Response (200): ```json { "hasPreviousProjectVideo": true, "hasTranslatedVideo": true, "hasLipSyncVideo": false, "hasOriginalSubtitle": true, "hasTranslatedSubtitle": true, "hasOriginalVoiceOnly": true, "hasTranslatedVoice": true, "hasOriginalBackground": true, "hasTranslatedBackground": true, "hasTranslateAudio": null, "hasTranslatedVoiceWithBackground": null, "hasZipDownload": true, "hasOriginalSpeakerAudioCollection": false, "hasSpeakerSegmentExcel": true, "hasSpeakerSegmentWithTranslationExcel": true } ``` --- ### GET /video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq}/download **Download Files** Download project output files. Use the download-info endpoint first to check which files are available, then pass the corresponding target value. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Query parameters: - target (string, required): The type of file to download. Use the download-info endpoint to check which targets are available. [video, dubbingVideo, lipSyncVideo, originalSubtitle, translatedSubtitle, originalVoiceAudio, voiceAudio, backgroundAudio, voicewithBackgroundAudio, translatedAudio, all, originalVoiceSpeakers, speakerSegmentExcel, speakerSegmentWithTranslationExcel] Response (200): ```json { "result": { "videoFile": { "videoDownloadLink": "https://..." }, "audioFile": { "voiceAudioDownloadLink": "https://...", "backgroundAudioDownloadLink": "https://...", "voiceWithBackgroundAudioDownloadLink": "https://..." }, "srtFile": { "originalSubtitleDownloadLink": "https://...", "translatedSubtitleDownloadLink": "https://..." }, "zippedFileDownloadLink": "https://..." } } ``` --- ## Dubbing API Core endpoints for video translation, project management, and file downloads. ### End-to-End Dubbing Workflow Complete workflow from file upload to downloading the translated result. **Step 1: Upload Media** Upload your video or audio file using the File API. See the File API page for the detailed upload flow (SAS token → Azure Blob upload → register). The response seq value is your mediaSeq for the next step. > Refer to the File API 'Direct File Upload' or 'External Platform Upload' guide. **Step 2: Initialize Queue** [PUT] Auth: XP-API-KEY `/video-translator/api/v1/projects/spaces/{spaceSeq}/queue` Call the Usage API's Get User Queue endpoint to ensure the space has a translation queue. Without this, the first translation request in a new space will fail with 'space queue not found'. > Only required once per space. Subsequent translations do not need this step. **Step 3: Request Translation** [POST] Auth: XP-API-KEY `/video-translator/api/v1/projects/spaces/{spaceSeq}/translate` Submit the translation request with the mediaSeq from Step 1. Use sourceLanguageCode 'auto' for automatic detection, or a specific code from the Language API. **Step 4: Poll Progress** [GET] Auth: XP-API-KEY `/video-translator/api/v1/projects/{projectSeq}/space/{spaceSeq}/progress` Poll the project status every 5 seconds. progressReason values: Enqueue Pending | Slow Mode Pending | Uploading | Transcribing | Translating | Generating Voice | Analyzing Lip Sync | Applying Lip Sync | Completed | Failed. > Do not poll more frequently than every 5 seconds. **Step 5: Download Result** [GET] Auth: XP-API-KEY `/video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq}/download?target=video` Once complete, download the output files. Use the download-info endpoint first to check available file types. > Available targets: video, dubbingVideo, lipSyncVideo, originalSubtitle, translatedSubtitle, voiceAudio, backgroundAudio, all, etc. ### POST /video-translator/api/v1/projects/spaces/{spaceSeq}/translate **Request Translation** Submit a video or audio translation request based on uploaded media files. The mediaSeq is the seq value returned from the Upload Video or Upload Audio endpoint in the File API. New integrations should use `targetLanguages` (per-language TTS model selection). The legacy `targetLanguageCodes` + `ttsModel` pair is still accepted for backward compatibility, but is deprecated. If you receive a 'space queue not found' error, you must first call the PUT /video-translator/api/v1/projects/spaces/{spaceSeq}/queue endpoint (Usage API) to initialize the queue before retrying. Path parameters: - spaceSeq (integer, required): The unique identifier of the space. Request body: - mediaSeq (integer, required): The media sequence (seq) returned from the Upload Video or Upload Audio API response. - isVideoProject (boolean, required): Whether this is a video project (true) or audio project (false). - sourceLanguageCode (string, required): Source language code. Use 'auto' for automatic language detection, or a specific code from the Language API (e.g. 'en', 'ko'). Do not send an empty string — use 'auto' instead. - targetLanguageCodes (string[], optional): (Deprecated since 2026-05-14) Array of target language codes to translate into. New integrations should use `targetLanguages` instead. If both fields are provided, `targetLanguages` takes precedence. Either `targetLanguageCodes` or `targetLanguages` must be provided. - targetLanguages (object[], optional): Recommended. List of target language and TTS model pairs. When provided, this field takes precedence over `targetLanguageCodes` and the top-level `ttsModel`. Either `targetLanguageCodes` or `targetLanguages` must be provided. - numberOfSpeakers (integer, optional): Number of speakers in the video for multi-speaker detection. (default: 1) - preferredSpeedType (string, required): Processing speed preference. [GREEN, RED] - withLipSync (boolean, optional): Whether to include lip sync processing. - customDictionaryBlobPath (string, optional): Storage path to a custom dictionary file. - srtBlobPath (string, optional): Storage path to an SRT subtitle file. - ttsModel (string, optional): (Deprecated since 2026-05-14) Single TTS model applied to all target languages. New integrations should specify `ttsModel` per language inside `targetLanguages`. ELEVEN_V2 (natural) or ELEVEN_V3 (emotional). [ELEVEN_V2, ELEVEN_V3] - title (string, optional): Project title. If omitted, the media file name is used. Request example: ```json { "mediaSeq": 12345, "isVideoProject": true, "sourceLanguageCode": "en", "targetLanguages": [ { "languageCode": "ko", "ttsModel": "ELEVEN_V3" }, { "languageCode": "ja", "ttsModel": "ELEVEN_V2" } ], "numberOfSpeakers": 2, "withLipSync": false, "preferredSpeedType": "GREEN", "title": "My Translation Project" } ``` Response (201): ```json { "result": { "startGenerateProjectIdList": [101, 102] } } ``` Errors: - 404 VT4043: Source language not found - 404 VT4044: Target language not found - 404 VT4042: Video not found - 503 VT5034: Queue full - 402 VT4021: Insufficient credits - 400 VT4009: Target language and TTS model pair is not supported --- ### GET /video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq} **Get Project** Retrieve detailed information about a specific translation project. The progressReason field indicates the current status: Enqueue Pending | Slow Mode Pending | Uploading | Transcribing | Translating | Generating Voice | Analyzing Lip Sync | Applying Lip Sync | Completed | Failed. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Response (200): ```json { "result": { "seq": 101, "projectType": "VIDEO", "title": "My Translation Project", "isEditable": true, "durationMs": 120000, "sourceLanguage": { "code": "en", "name": "English" }, "targetLanguage": { "code": "ko", "name": "Korean" }, "progress": 100, "progressReason": "Completed", "hasFailed": false, "isLipSync": false, "isLinkShared": false, "projectGenerationType": "DUBBING", "thumbnailUrl": "https://...", "createDate": "2026-01-15T10:30:00Z", "updateDate": "2026-01-15T11:00:00Z" } } ``` Errors: - 404 VT4041: Project not found - 404 VT4045: Project deleted - 403 VT4031: Access denied - 409 VT4091: Video generation failed --- ### GET /video-translator/api/v1/projects/spaces/{spaceSeq} **List Projects** Retrieve a list of active projects within a space. Path parameters: - spaceSeq (integer, required): The unique identifier of the space. Query parameters: - memberRole (string, required): The role of the member requesting the list. [enterprise_owner, space_owner, space_manager, space_member, individual, developer] - size (integer, required): Number of items per page. - offset (integer, required): Starting position for pagination. - sortType (string, optional): Sort field. (default: update_date) [update_date, title] - sortDirection (string, required): Sort direction. [asc, desc] - type (string, optional): Filter by project generation type. [DUBBING, LIP_SYNC] Response (200): ```json { "result": { "totalCount": 100, "hasNext": true, "pageSize": 10, "nextVtOffset": 10, "content": [ { "seq": 1, "title": "My Project", "projectType": "VIDEO", "durationMs": 120000, "sourceLanguage": { "code": "en", "name": "English" }, "targetLanguage": { "code": "ko", "name": "Korean" }, "progress": 100, "hasFailed": false, "status": "created", "projectGenerationType": "DUBBING", "createDate": "2026-01-15T10:00:00Z" } ] } } ``` --- ### GET /video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq}/script **Get Script** Retrieve the project script with sentence-level translations, matching rates, and speaker information. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Query parameters: - cursorId (integer, optional): Cursor-based pagination ID. Omit for the first request. - size (integer, optional): Number of sentences per page. (default: 10000) Response (200): ```json { "result": { "hasNext": false, "retranslateAvailable": true, "sentences": [ { "seq": 1, "speakerOrderIndex": 0, "offsetMs": 0, "durationMs": 3500, "originalText": "Hello, welcome.", "translatedText": "...", "audioUrl": "https://...", "matchingRate": { "level": 3, "levelType": "GOOD" }, "rewrite": { "speed": "normal", "current": 120 } } ], "speakers": [ { "speakerOrderIndex": 0, "externalSpeakerSeq": "spk_001" } ] } } ``` --- ### GET /video-translator/api/v1/projects/{projectSeq}/space/{spaceSeq}/progress **Poll Progress** Poll the current progress of a translation project. Recommended polling interval: every 5 seconds. Completion: progressReason === 'Completed'. Failure: progressReason === 'Failed'. Do not poll more frequently than every 5 seconds to avoid rate limiting. progressReason values: Enqueue Pending | Slow Mode Pending | Uploading | Transcribing | Translating | Generating Voice | Analyzing Lip Sync | Applying Lip Sync | Completed | Failed. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Response (200): ```json { "result": { "projectSeq": 101, "progress": 65, "progressReason": "Transcribing", "hasFailed": false, "speedType": "fast", "expectedRemainingTimeMinutes": 3, "isCancelable": true } } ``` --- ### POST /video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq}/cancel **Cancel Project** Cancel a pending project. Only available for GREEN zone projects in PENDING initial export state. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Response (201): ```json { "result": {} } ``` Errors: - 400 VT4004: Cancellation not allowed - 403 VT4033: No access to the space --- ### GET /video-translator/api/v1/projects/{projectSeq}/share **Get Share Link** Retrieve the encrypted share query string for sharing a project externally. Path parameters: - projectSeq (integer, required): The unique identifier of the project. Response (200): ```json { "result": { "shareQuery": "eyJhbGciOiJIUzI1NiJ9..." } } ``` --- ### PATCH /video-translator/api/v1/projects/{projectSeq}/share **Toggle Share** Enable or disable the share URL for a project. Path parameters: - projectSeq (integer, required): The unique identifier of the project. Query parameters: - sharedStatus (boolean, required): Whether to enable (true) or disable (false) sharing. Response (200): ```json { "result": { "projectSeq": 1, "sharedStatus": true } } ``` --- ### GET /video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq}/video-info **Get Video Info** Retrieve metadata for an individual video including title, duration, resolution, and status. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Response (200): ```json { "result": { "title": "My Video", "thumbnailUrl": "https://...", "type": "VIDEO", "durationMs": 120000, "videoStatus": "COMPLETED", "language": "ko", "aspectRatio": "16:9", "resolution": "1920x1080", "sizeByte": 52428800 } } ``` Errors: - 403 VT4033: No access to the space - 404 VT4041: Project not found - 404 VT4042: Video not found --- ### GET /video-translator/api/v1/projects/{projectSeq}/space/{spaceSeq}/export-history **Get Export History** Retrieve the export and upload history for a project with pagination. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Query parameters: - page (integer, optional): Page number (zero-based). (default: 0) - size (integer, optional): Number of items per page. - sort (string, optional): Sort criteria. Response (200): ```json { "result": { "page": 1, "totalPages": 10, "hasNext": true, "currentPageSize": 100, "contents": [ { "exportDate": "2026-01-15T10:00:00Z", "projectTitle": "My Project", "isLipSync": true, "exportZipDownloadPath": "/export.zip" } ] } } ``` --- ### GET /video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq}/retranslation/status **Check Retranslation Status** Check whether retranslation is available for a project. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Response (200): ```json { "result": { "retranslateAvailable": true } } ``` --- ### GET /video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq}/used-features **Get Used Features** Check which optional features were used when creating the project, such as custom dictionary or SRT upload. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Response (200): ```json { "result": { "usedCustomDictionary": false, "usedSrtUpload": false } } ``` --- ## Editing API Edit, translate, and manage individual audio sentences within a project. ### PATCH /video-translator/api/v1/project/{projectSeq}/audio-sentence/{sentenceSeq} **Translate Sentence** Request translation or retranslation for a specific sentence within a project. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - sentenceSeq (integer, required): The unique identifier of the sentence. Request body: - targetText (string, required): The text to translate or the updated translation. Request example: ```json { "targetText": "Updated translation text" } ``` Response (200): ```json { "result": { "scriptSeq": 1, "translatedText": "Updated translation text", "matchingRate": { "level": 4, "levelType": "EXCELLENT" }, "rewrite": { "speed": "normal", "current": 115, "optimal": { "min": 80, "max": 150 } } } } ``` --- ### PATCH /video-translator/api/v1/project/{projectSeq}/audio-sentence/{audioSentenceSeq}/generate-audio **Generate Audio** Generate a translated audio file for a specific sentence. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - audioSentenceSeq (integer, required): The unique identifier of the audio sentence. Request body: - targetText (string, required): The text to generate audio for. Request example: ```json { "targetText": "Text to generate audio for" } ``` Response (200): ```json { "result": { "scriptSeq": 1, "translatedText": "Text to generate audio for", "generateAudioFilePath": "/audio/1.mp3", "matchingRate": { "level": 3, "levelType": "Low" }, "rewrite": { "speed": "fast", "current": 3, "optimal": { "min": 101, "max": 120 } } } } ``` --- ### PUT /video-translator/api/v1/project/{projectSeq}/audio-sentence/{audioSentenceSeq}/reset **Reset Translation** Reset a translation back to its original proofread state. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - audioSentenceSeq (integer, required): The unique identifier of the audio sentence. Response (200): ```json { "result": { "speakerSeq": "pvsp-1351dafa5135", "proofreadOriginalText": "Hello", "proofreadTranslatedText": "Hello" } } ``` --- ### PUT /video-translator/api/v1/project/{projectSeq}/audio-sentence/{audioSentenceSeq}/cancel **Cancel Translation** Cancel an in-progress translation for a specific sentence. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - audioSentenceSeq (integer, required): The unique identifier of the audio sentence. Response (200): ```json { "result": {} } ``` --- ### POST /video-translator/api/v1/project/{projectSeq}/audio-sentence/{audioSentenceSeq}/temp-save **Temp Save Draft** Temporarily save a translation draft for a paragraph without triggering full processing. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - audioSentenceSeq (integer, required): The unique identifier of the audio sentence. Request body: - originalDraftText (string, required): The draft original text to temporarily save. - speakerSeq (string, required): The speaker identifier to temporarily assign. Request example: ```json { "originalDraftText": "Draft translation text", "speakerSeq": "spk_001" } ``` Response (200): ```json { "result": {} } ``` --- ### POST /video-translator/api/v1/project/{projectSeq}/audio-sentence/{audioSentenceSeq}/match-rewrite **Get Match Rate & Rewrite** Query the matching rate and rewrite information for a translated sentence to evaluate translation quality. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - audioSentenceSeq (integer, required): The unique identifier of the audio sentence. Request body: - targetText (string, required): The translated text to evaluate. Request example: ```json { "targetText": "Translation text to evaluate" } ``` Response (200): ```json { "result": { "matchingRate": { "level": 3, "levelType": "Low" }, "rewrite": { "speed": "fast", "current": 3, "optimal": { "min": 101, "max": 120 } } } } ``` --- ### POST /video-translator/api/v1/project/{projectSeq}/space/{spaceSeq}/proofread **Request Proofread** Submit a proofread request for the project's translations. This re-processes all translations with quality improvements. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Request body: - isLipSync (boolean, optional): Whether to enable lip sync for the proofread output. - experimentKey (string, optional): Experiment key for A/B testing configurations. - preferredSpeedType (string, required): Processing speed preference. [GREEN, RED] Request example: ```json { "isLipSync": false, "preferredSpeedType": "GREEN" } ``` Response (200): ```json { "result": {} } ``` --- ### PATCH /video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq}/title **Update Title** Update the title of a project. If the title is empty or null, it defaults to 'Untitled'. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Request body: - newTitle (string, optional): The new project title. Defaults to 'Untitled' if empty. Request example: ```json { "newTitle": "Updated Project Name" } ``` Response (200): ```json { "result": {} } ``` --- ### PATCH /video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq}/access **Update Access** Modify the access permissions for a project. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Query parameters: - permission (string, required): The access permission type. [individual, all] Response (200): ```json { "result": {} } ``` Errors: - 403 VT4033: No access to the space - 404 VT4046: Project space not found --- ### DELETE /video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq} **Delete Project** Permanently delete a project. Returns 204 No Content on success. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Response (204): ```json // No content returned ``` Errors: - 404 VT4041: Project not found - 404 VT4045: Project deleted - 403 VT4031: Access denied --- ## Usage API Monitor quota consumption, queue status, and estimated credit usage. ### GET /video-translator/api/v1/projects/spaces/{spaceSeq}/plan/status **Get User Quota** Retrieve the quota information and plan status for a user within a space. Path parameters: - spaceSeq (integer, required): The unique identifier of the space. Response (200): ```json { "result": { "spaceSeq": 12345, "planTier": "creator", "remainingQuota": { "remainingQuota": 500000 }, "resetDateTime": "2026-02-28T23:59:59Z", "isCancellationScheduled": false } } ``` --- ### GET /video-translator/api/v1/projects/spaces/{spaceSeq}/media/quota **Estimate Quota Usage** Calculate the estimated quota that will be consumed for a given media file based on its type, duration, and translation settings. Path parameters: - spaceSeq (integer, required): The unique identifier of the space. Query parameters: - mediaType (string, required): The type of media. [VIDEO, AUDIO] - lipSync (boolean, required): Whether lip sync is included. - durationMs (integer, required): Media duration in milliseconds. - width (integer, optional): Video width in pixels. - height (integer, optional): Video height in pixels. - targetLanguageSize (integer, optional): Number of target languages. (default: 1) Response (200): ```json { "result": { "expectedUsedQuota": 5000 } } ``` --- ### PUT /video-translator/api/v1/projects/spaces/{spaceSeq}/queue **Get User Queue** Retrieve or initialize the queue for a user within a space. If no queue exists, a new one is automatically created and returned. This endpoint must be called before requesting a translation if the space does not yet have an initialized queue — otherwise the translation request will fail with a 'space queue not found' error. Both GET and PUT methods are supported. Internally, the service fetches the user's plan options from the Credit service, retrieves quota information, and looks up the TranslateQueue — creating a new one if it does not exist. Path parameters: - spaceSeq (integer, required): The unique identifier of the space. Response (200): ```json { "success": true, "data": { "userSeq": 12345, "planName": "Free", "usedQueueCount": 5, "maxQueueCount": 10, "redZoneQueueCount": 0 } } ``` --- ## Lip Sync API Request lip sync video generation and retrieve generation history. ### POST /video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq}/lip-sync **Request Lip Sync** Submit a lip sync video generation request for a translated project. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Request body: - preferredSpeedType (string, required): Processing speed preference. [GREEN, RED] - title (string, optional): Project title. If omitted, the parent project title is used. Request example: ```json { "preferredSpeedType": "GREEN", "title": "My Lip Sync Project" } ``` Response (200): ```json { "result": { "startGenerateProjectIdList": [1, 2, 3] } } ``` --- ### GET /video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq}/lip-sync/generated **Get Generation History** Retrieve a paginated list of lip sync generation history for a project. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Query parameters: - page (integer, optional): Page number. (default: 1) - pageSize (integer, optional): Number of items per page. (default: 10) Response (200): ```json { "result": { "page": 1, "totalPages": 10, "hasNext": true, "contents": [ { "type": "VIEW", "projectTitle": "Lip Sync Project 1", "lipSyncProjectSeq": 123, "status": "COMPLETED", "createDate": "2026-01-15T04:29:37.432Z" }, { "type": "DOWNLOAD", "projectTitle": "Lip Sync Project 2", "exportZipDownloadPath": "/export.zip", "createDate": "2026-01-15T04:29:37.432Z" } ] } } ``` --- ## STT API Create Speech-to-Text projects and retrieve generated STT scripts. ### POST /video-translator/api/v1/projects/spaces/{spaceSeq}/stt **Create STT Project** Create a Speech-to-Text (STT) project from an uploaded media file. The mediaSeq is the seq value returned from the Upload Video or Upload Audio endpoint in the File API. Path parameters: - spaceSeq (integer, required): The unique identifier of the space. Request body: - mediaSeq (integer, required): The media sequence (seq) returned from the Upload Video or Upload Audio API response. - isVideoProject (boolean, required): Whether this is a video project (true) or audio project (false). - title (string, optional): Project title. If omitted, the media file name is used. Request example: ```json { "mediaSeq": 12345, "isVideoProject": true, "title": "My STT Project" } ``` Response (201): ```json { "result": { "startGenerateProjectIdList": [101] } } ``` --- ### POST /video-translator/api/v1/project/{projectSeq}/space/{spaceSeq}/stt/apply-changes **Regenerate STT** Request regeneration of the STT (Speech-to-Text) results for a project after applying script edits. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Response (200): ```json { "result": null } ``` --- ### GET /video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq}/stt/script **Get STT Project Script** Retrieve the script for a completed STT project. Uses cursor-based pagination — omit cursorId on the first request. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Query parameters: - cursorId (integer, optional): Cursor-based pagination ID. Omit on the first request; pass the last item's cursor value on subsequent requests. - size (integer, optional): Page size. (default: 10000) Response (200): ```json { "hasNext": true, "nextCursorId": 123456789, "sentences": [ { "seq": 1, "externalScriptSeq": "pvtv-e1045225d769af98305367b722c1adfb", "speakerOrderIndex": 1, "offsetMs": 0, "durationMs": 1000, "originalDraftText": "Hello, world!", "originalText": "Hello, world!" } ], "speakers": [ { "speakerOrderIndex": 1, "externalSpeakerSeq": "pvtv-e1045225d769af98305367b722c1adfb" } ] } ``` --- ## Audio Separation API Create Audio Separation projects that split media into voice and background audio tracks, and retrieve the generated script. ### POST /video-translator/api/v1/projects/spaces/{spaceSeq}/audio-separation **Create Audio Separation Project** Create an Audio Separation project that splits a media file into voice and background audio tracks. Path parameters: - spaceSeq (integer, required): The unique identifier of the space. Request body: - mediaSeq (integer, required): The media sequence (seq) returned from the Upload Video or Upload Audio API response. - isVideoProject (boolean, required): Whether this is a video project (true) or audio project (false). - title (string, optional): Project title. If omitted, the media file name is used. Request example: ```json { "mediaSeq": 12345, "isVideoProject": false, "title": "My Audio Separation Project" } ``` Response (201): ```json { "result": { "startGenerateProjectIdList": [102] } } ``` --- ### GET /video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq}/audio-separation/script **Get Audio Separation Project Script** Retrieve the script for a completed Audio Separation project. Uses cursor-based pagination — omit cursorId on the first request. Path parameters: - projectSeq (integer, required): The unique identifier of the project. - spaceSeq (integer, required): The unique identifier of the space. Query parameters: - cursorId (integer, optional): Cursor-based pagination ID. Omit on the first request; pass the last item's cursor value on subsequent requests. - size (integer, optional): Page size. (default: 10000) Response (200): ```json { "hasNext": true, "nextCursorId": 123456789, "sentences": [ { "seq": 1, "externalScriptSeq": "pvtv-e1045225d769af98305367b722c1adfb", "speakerOrderIndex": 1, "offsetMs": 0, "durationMs": 1000, "originalDraftText": "Hello, world!", "originalText": "Hello, world!", "audioUrl": "/perso-storage/audio.mp3" } ], "speakers": [ { "speakerOrderIndex": 1, "externalSpeakerSeq": "pvtv-e1045225d769af98305367b722c1adfb" } ] } ``` --- ## Language API Retrieve the list of languages available for dubbing and translation. ### GET /video-translator/api/v1/languages **List Languages** Returns a list of all supported languages including their language codes and names. Experimental languages are flagged accordingly. Response (200): ```json { "result": { "languages": [ { "code": "en", "name": "English", "experiment": false }, { "code": "ko", "name": "Korean", "experiment": false }, { "code": "ja", "name": "Japanese", "experiment": true } ] } } ``` --- ## Feedback API Submit and retrieve feedback ratings for translated projects. ### POST /video-translator/api/v1/projects/feedbacks **Submit Feedback** Submit a feedback rating for a specific project. Ratings must be between 1 and 5. Request body: - projectSeq (integer, required): The unique identifier of the project. - rating (integer, required): Feedback score from 1 to 5. Request example: ```json { "projectSeq": 101, "rating": 4 } ``` Response (200): ```json { "result": { "averageRating": 4.5, "count": 10 } } ``` Errors: - 404 VT4041: Project not found - 404 VT4045: Project has been deleted --- ### GET /video-translator/api/v1/projects/feedbacks **Get Feedback** Retrieve the feedback rating you submitted for a specific project. Returns 204 No Content if no feedback has been submitted. Query parameters: - projectSeq (integer, required): The unique identifier of the project. Response (200): ```json { "result": { "rating": 4 } } ``` Errors: - 404 VT4041: Project not found - 404 VT4045: Project has been deleted --- ## Community Spotlight API Browse featured public projects and shared translations. ### GET /video-translator/api/v1/projects/recommended **List Featured Projects** Retrieve a paginated list of projects featured in the Community Spotlight. Query parameters: - page (integer, optional): Page number (zero-based). (default: 0) - size (integer, optional): Number of items per page. - languageCode (string, optional): Filter by target language code (e.g. ko, en, ja). Response (200): ```json { "result": { "totalCount": 100, "totalPages": 10, "page": 1, "size": 10, "isLast": false, "contents": [ { "seq": 1, "title": "How to build with Framer", "mediaType": "VIDEO", "userName": "oh****on", "thumbnailUrl": "/thumbnail.jpg", "durationMs": 10000, "sourceLanguage": { "code": "en", "name": "English" }, "targetLanguage": { "code": "ko", "name": "Korean" }, "isLipSync": true, "feedbackAverage": { "averageRating": 4.5, "count": 10 } } ] } } ``` --- ### GET /video-translator/api/v1/projects/recommended/{projectSeq} **Get Featured Project** Retrieve detailed information about a specific featured project. Path parameters: - projectSeq (integer, required): The unique identifier of the project. Response (200): ```json { "result": { "seq": 1, "title": "How to build with Framer", "mediaType": "VIDEO", "userName": "oh****on", "durationMs": 10000, "sourceLanguage": { "code": "en", "name": "English" }, "targetLanguage": { "code": "ko", "name": "Korean" }, "originalFileUrl": "/original.mp4", "translatedFileUrl": "/translated.mp4", "lipSyncFileUrl": "/lip-sync.mp4", "isLipSync": true, "feedbackAverage": { "averageRating": 4.5, "count": 10 } } } ``` Errors: - 404 VT4041: Project not found --- ### GET /video-translator/api/v1/projects/shared/{sharedQuery} **Get Shared Project** Retrieve project information using an encrypted share query string. Path parameters: - sharedQuery (string, required): Encrypted share query string. Response (200): ```json { "result": { "seq": 1, "title": "How to build with Framer", "projectType": "VIDEO", "userName": "oh****on", "durationMs": 10000, "sourceLanguage": { "code": "en", "name": "English" }, "targetLanguage": { "code": "ko", "name": "Korean" }, "originalFileUrl": "/original.mp4", "translatedFileUrl": "/translated.mp4", "isLipSync": true } } ``` ---