> ## Documentation Index
> Fetch the complete documentation index at: https://developers.phrase.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Update Subtitle

> Replace the subtitle segments of a recording's language track. The request body must contain **every** segment in the final state — the endpoint performs a bulk replace, not a partial patch. Any segment you omit is removed from the track. Use this to push subtitle edits made externally (for example, by AI editing tools) back into Studio.

## `words` is the source of truth — never send empty `words`

Each segment's `words` array (word-level text and timings) is authoritative. Subtitle rendering **and** dubbing text-to-speech are generated from `words`; the segment-level `text` field is only a human-readable mirror and is **ignored** when rendering subtitles or synthesizing speech.

A segment whose `words` array is empty is treated as having no content: it is **silently dropped the next time the track is read**, and for dubbing nothing is synthesized for it. This is the most common cause of a track that “comes back empty” after an update. Never submit `"words": []` unless you intend to remove that segment's content.

To change what a segment says — for example, shortening a dubbing line — rewrite its `words` array, not just `text`. Mirror the change in `text` for readability, but the `words` entries are what actually change the rendered subtitle and the generated audio.

## Recommended workflow

1. `GET` the track to fetch the current segments with their word-level timings.
2. Edit the `words` array of the segment(s) you want to change (and mirror the new text in `text`). Leave every other segment untouched.
3. `PUT` the **full** segment array back. For a dubbing redub, list the changed segment ids in `segmentsToRedub` and keep `enqueueTtsUpdate=true` so their audio is regenerated from the new `words`.

To edit text without triggering re-synthesis, set `enqueueTtsUpdate` to `false` — but still send the full `words` array for every segment.

## Language scoping

The `language` value must match the requested task type for the recording:
* When `source=true` and `dubbing=false` (transcription), `language` must equal the recording source language.
* When `source=false` and `dubbing=false` (translation), `language` must be one of the recording translation languages.
* When `source=false` and `dubbing=true` (dubbing), `language` must be one of the recording translation languages.
* `source=true` and `dubbing=true` together is invalid.

Mismatched combinations return `400` with a descriptive message rather than reaching the underlying file lookup.



## OpenAPI

````yaml /openapi/phrase-studio.json put /v1/projects/{projectId}/recordings/{recordingId}/subtitles
openapi: 3.0.3
info:
  title: Studio API
  description: >-
    API for creating projects and reading transcription, translation, and
    dubbing results.
  version: 1.0.0
servers:
  - url: https://api.studio.us.phrase.com
    description: Production US Environment
  - url: https://api.studio.eu.phrase.com
    description: Production EU Environment
security: []
tags:
  - name: Projects
    description: Manage transcription, translation, and dubbing projects
  - name: Recordings
    description: Access recording status and output data
  - name: Subtitle Profiles
    description: Manage subtitle formatting profiles
  - name: Pronunciations
    description: Manage custom pronunciation rules
  - name: Glossaries
    description: Manage translation glossaries
  - name: Insights
    description: Manage AI-powered insight prompts
  - name: Safe Communications
    description: Access safe communication evaluation templates
  - name: Subtitles
    description: Write subtitle segments for a recording
paths:
  /v1/projects/{projectId}/recordings/{recordingId}/subtitles:
    put:
      tags:
        - Subtitles
      summary: Update Subtitle
      description: >-
        Replace the subtitle segments of a recording's language track. The
        request body must contain **every** segment in the final state — the
        endpoint performs a bulk replace, not a partial patch. Any segment you
        omit is removed from the track. Use this to push subtitle edits made
        externally (for example, by AI editing tools) back into Studio.


        ## `words` is the source of truth — never send empty `words`


        Each segment's `words` array (word-level text and timings) is
        authoritative. Subtitle rendering **and** dubbing text-to-speech are
        generated from `words`; the segment-level `text` field is only a
        human-readable mirror and is **ignored** when rendering subtitles or
        synthesizing speech.


        A segment whose `words` array is empty is treated as having no content:
        it is **silently dropped the next time the track is read**, and for
        dubbing nothing is synthesized for it. This is the most common cause of
        a track that “comes back empty” after an update. Never submit `"words":
        []` unless you intend to remove that segment's content.


        To change what a segment says — for example, shortening a dubbing line —
        rewrite its `words` array, not just `text`. Mirror the change in `text`
        for readability, but the `words` entries are what actually change the
        rendered subtitle and the generated audio.


        ## Recommended workflow


        1. `GET` the track to fetch the current segments with their word-level
        timings.

        2. Edit the `words` array of the segment(s) you want to change (and
        mirror the new text in `text`). Leave every other segment untouched.

        3. `PUT` the **full** segment array back. For a dubbing redub, list the
        changed segment ids in `segmentsToRedub` and keep
        `enqueueTtsUpdate=true` so their audio is regenerated from the new
        `words`.


        To edit text without triggering re-synthesis, set `enqueueTtsUpdate` to
        `false` — but still send the full `words` array for every segment.


        ## Language scoping


        The `language` value must match the requested task type for the
        recording:

        * When `source=true` and `dubbing=false` (transcription), `language`
        must equal the recording source language.

        * When `source=false` and `dubbing=false` (translation), `language` must
        be one of the recording translation languages.

        * When `source=false` and `dubbing=true` (dubbing), `language` must be
        one of the recording translation languages.

        * `source=true` and `dubbing=true` together is invalid.


        Mismatched combinations return `400` with a descriptive message rather
        than reaching the underlying file lookup.
      operationId: updateSubtitles
      parameters:
        - name: projectId
          in: path
          required: true
          description: Project ID
          schema:
            type: string
            format: uuid
        - name: recordingId
          in: path
          required: true
          description: Recording ID
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSubtitleRequest'
            examples:
              source_text_edit:
                summary: >-
                  Replace a source-language transcript segment without TTS
                  regeneration
                value:
                  language: en
                  source: true
                  dubbing: false
                  enqueueTtsUpdate: false
                  subtitles:
                    - id: seg-1
                      key: seg-1_en_true_false
                      time_begin: 0
                      time_end: 1.8
                      speaker: speaker_1
                      language: en
                      text: Hello and welcome.
                      words:
                        - word: Hello
                          time_begin: 0
                          time_end: 0.5
                        - word: and
                          time_begin: 0.5
                          time_end: 0.8
                        - word: welcome.
                          time_begin: 0.8
                          time_end: 1.8
                          is_eos: true
              dubbing_redub:
                summary: >-
                  Update a dubbing track and queue specific segments for
                  re-synthesis
                value:
                  language: tr
                  source: false
                  dubbing: true
                  enqueueTtsUpdate: true
                  segmentsToRedub:
                    - seg-1
                  subtitles:
                    - id: seg-1
                      key: seg-1_tr_false_true
                      time_begin: 0
                      time_end: 1.8
                      speaker: voice-uid-123
                      language: tr
                      text: Merhaba ve hoş geldiniz.
                      words:
                        - word: Merhaba
                          time_begin: 0
                          time_end: 0.6
                        - word: ve
                          time_begin: 0.6
                          time_end: 0.8
                        - word: hoş
                          time_begin: 0.8
                          time_end: 1.2
                        - word: geldiniz.
                          time_begin: 1.2
                          time_end: 1.8
                          is_eos: true
      responses:
        '200':
          description: Subtitles updated successfully
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                bad_request:
                  summary: Bad Request
                  value:
                    success: false
                    statusCode: 400
                    message: Bad Request
                    timestamp: '2025-01-01T00:00:00.000Z'
                language_not_available:
                  summary: Language not available for the requested task
                  value:
                    success: false
                    statusCode: 400
                    message: >-
                      Language 'fr' is not available for translation on this
                      recording. Available languages: tr, es
                    timestamp: '2025-01-01T00:00:00.000Z'
                source_dubbing_conflict:
                  summary: source and dubbing both true
                  value:
                    success: false
                    statusCode: 400
                    message: source and dubbing cannot both be true
                    timestamp: '2025-01-01T00:00:00.000Z'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                unauthorized:
                  summary: Unauthorized
                  value:
                    success: false
                    statusCode: 401
                    message: Unauthorized
                    timestamp: '2025-01-01T00:00:00.000Z'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                forbidden:
                  summary: Forbidden
                  value:
                    success: false
                    statusCode: 403
                    message: Access denied
                    timestamp: '2025-01-01T00:00:00.000Z'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                not_found:
                  summary: Not Found
                  value:
                    success: false
                    statusCode: 404
                    message: Project or recording not found
                    timestamp: '2025-01-01T00:00:00.000Z'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                internal_server_error:
                  summary: Internal server error
                  value:
                    success: false
                    statusCode: 500
                    message: Internal server error
                    timestamp: '2025-01-01T00:00:00.000Z'
      security:
        - apiKeyAuth: []
components:
  schemas:
    UpdateSubtitleRequest:
      type: object
      required:
        - subtitles
        - language
      properties:
        language:
          type: string
          description: BCP-47 language code of the track to write
        source:
          type: boolean
          default: false
          description: True when writing the source (transcript) track
        dubbing:
          type: boolean
          default: false
          description: True when writing a dubbing track
        bgVolume:
          type: number
          description: Dubbing background-audio volume (only honored for dubbing tracks)
        speakerVolumes:
          type: object
          additionalProperties:
            type: number
          description: Per-speaker volume overrides (dubbing only)
        speakerVoiceSettings:
          type: object
          additionalProperties:
            type: object
            additionalProperties: true
          description: Per-speaker TTS voice settings (dubbing only)
        segmentsToRedub:
          type: array
          items:
            type: string
          description: Segment ids that should be re-synthesized after this update
        enqueueTtsUpdate:
          type: boolean
          default: true
          description: >-
            When true (default), the dubbing segments listed in
            `segmentsToRedub` are queued for TTS regeneration from their
            `words`. Set to false to save edits without re-synthesis (for
            example, text-only fixes) — you must still send the full `words`
            array for every segment.
        subtitles:
          type: array
          description: >-
            Full segment list for the target language track. The endpoint
            performs a bulk replace, so the array must contain every segment in
            the final state, not just the edited ones. Every segment must
            include a non-empty `words` array; omitting a segment deletes it,
            and an empty `words` array drops the segment on the next read.
          items:
            $ref: '#/components/schemas/SubtitleSegment'
    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          example: false
        statusCode:
          type: integer
          example: 400
        message:
          type: string
          example: Bad Request
        timestamp:
          type: string
          format: date-time
          example: '2025-01-01T00:00:00.000Z'
      required:
        - success
        - statusCode
        - message
        - timestamp
    SubtitleSegment:
      type: object
      required:
        - id
        - key
        - time_begin
        - time_end
        - words
        - speaker
        - language
      properties:
        id:
          type: string
          description: Segment identifier (stable across edits)
        key:
          type: string
          description: >-
            Composite key in the form `${id}_${language}_${source}_${dubbing}`.
            Must match the request-level language/source/dubbing flags.
        time_begin:
          type: number
          minimum: 0
          description: Segment start time in seconds
        time_end:
          type: number
          minimum: 0
          description: Segment end time in seconds
        words:
          type: array
          description: >-
            Word-level text and timings. **Source of truth** for both subtitle
            rendering and dubbing text-to-speech. Must be non-empty: a segment
            submitted with an empty `words` array is dropped the next time the
            track is read (and produces no dubbing audio). To change a segment's
            content, edit these entries — not `text`.
          items:
            $ref: '#/components/schemas/SubtitleWord'
        speaker:
          type: string
          description: Speaker identifier (voice id or label)
        language:
          type: string
          description: BCP-47 language code for the segment
        text:
          type: string
          description: >-
            Human-readable mirror of the segment text. **Not authoritative** —
            it is ignored when rendering subtitles or synthesizing dubbing
            audio, both of which are derived from `words`. Editing `text` alone
            has no effect on the output; change `words` instead (and keep `text`
            in sync for readability).
        metadata:
          type: object
          description: Optional editorial metadata (review state, tags, etc.)
          additionalProperties: true
        cue_settings:
          type: object
          description: Optional VTT cue rendering settings
          additionalProperties: true
    SubtitleWord:
      type: object
      required:
        - word
        - time_begin
        - time_end
      properties:
        word:
          type: string
          description: Word text
        time_begin:
          type: number
          minimum: 0
          description: Start time in seconds
        time_end:
          type: number
          minimum: 0
          description: End time in seconds
        is_eos:
          type: boolean
          description: Whether this word ends a sentence
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key

````