`): Returns completion status for processing steps of a specific language. Only completed steps are included in the response.
# Get Safe Communications
Source: https://developers.phrase.com/en/api/studio/safe-communications/get-safe-communications
/openapi/phrase-studio.json get /v1/safe-communications
Retrieve available safe communications for the authenticated account
# Get Subtitle Profiles
Source: https://developers.phrase.com/en/api/studio/subtitle-profiles/get-subtitle-profiles
/openapi/phrase-studio.json get /v1/subtitle-profiles
Retrieve available subtitle profiles for the authenticated account
# Get Subtitle
Source: https://developers.phrase.com/en/api/studio/subtitles/get-subtitle
/openapi/phrase-studio.json get /v1/projects/{projectId}/recordings/{recordingId}/subtitles
Retrieve the structured subtitle segments of a recording's language track in JSON form (with word-level timings). The response shape mirrors the PUT request body — use this before `PUT` to fetch the current segments, edit the `words` array of the segment(s) you want to change (word-level text and timings are the source of truth; editing `text` alone has no effect), and send the full array back. The `source` and `dubbing` query flags pick which track to read: source transcription, target translation, or dubbing.
## 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.
# Update Subtitle
Source: https://developers.phrase.com/en/api/studio/subtitles/update-subtitle
/openapi/phrase-studio.json put /v1/projects/{projectId}/recordings/{recordingId}/subtitles
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.
# Authentication
Source: https://developers.phrase.com/en/api/connectors/authentication
## Phrase Platform API tokens
The Phrase Connectors API uses API key authentication. Generate a Phrase Platform JWT token as described [here](/en/api/platform/authentication)
### Using Your API Key
Include your API key in the `Authentication` header with every API request:
```bash theme={null}
curl -X GET "https://eu.phrase.com/connectors/connectors/v1" \
-H "Authentication: Bearer GENERATED_JWT_TOKEN"
```
# Convert stored raw Braze content to XLIFF asynchronously
Source: https://developers.phrase.com/en/api/connectors/braze/convert-stored-raw-braze-content-to-xliff-asynchronously
/openapi/phrase-connectors.json post /braze_multilang/v1/async/convert-to-xliff
Convert raw Braze content already in Phrase file storage to XLIFF 2.0, without
re-downloading from Braze. Each entry in `rawFiles` references a stored file
(`storageId`) plus the metadata the connector needs to segment correctly.
Use this when you have a snapshot from `download-raw-file` and want to defer the
conversion step (e.g. to apply custom pre-processing first).
**Async variant.** Returns `{ requestId, webHookUrl }` immediately; the XLIFF (or its `storageId`) is POSTed to `X-Webhook`.
# Convert stored raw Braze content to XLIFF synchronously
Source: https://developers.phrase.com/en/api/connectors/braze/convert-stored-raw-braze-content-to-xliff-synchronously
/openapi/phrase-connectors.json post /braze_multilang/v1/sync/convert-to-xliff
Convert raw Braze content already in Phrase file storage to XLIFF 2.0, without
re-downloading from Braze. Each entry in `rawFiles` references a stored file
(`storageId`) plus the metadata the connector needs to segment correctly.
Use this when you have a snapshot from `download-raw-file` and want to defer the
conversion step (e.g. to apply custom pre-processing first).
# Convert stored XLIFF back to raw Braze content asynchronously
Source: https://developers.phrase.com/en/api/connectors/braze/convert-stored-xliff-back-to-raw-braze-content-asynchronously
/openapi/phrase-connectors.json post /braze_multilang/v1/async/convert-to-raw
Convert a translated XLIFF (referenced by `storageId`) back into Braze's raw content
format without writing it to Braze. The result is stored in Phrase file storage and
returned as a `storageId` you can then push with `upload-raw-file`.
Use this when the upload step is decoupled from the conversion step (e.g. for a
preview/review gate).
**Async variant.** Returns `{ requestId, webHookUrl }` immediately; the raw file (or its `storageId`) is POSTed to `X-Webhook`.
# Convert stored XLIFF back to raw Braze content synchronously
Source: https://developers.phrase.com/en/api/connectors/braze/convert-stored-xliff-back-to-raw-braze-content-synchronously
/openapi/phrase-connectors.json post /braze_multilang/v1/sync/convert-to-raw
Convert a translated XLIFF (referenced by `storageId`) back into Braze's raw content
format without writing it to Braze. The result is stored in Phrase file storage and
returned as a `storageId` you can then push with `upload-raw-file`.
Use this when the upload step is decoupled from the conversion step (e.g. for a
preview/review gate).
# Download Braze multilingual content as XLIFF asynchronously
Source: https://developers.phrase.com/en/api/connectors/braze/download-braze-multilingual-content-as-xliff-asynchronously
/openapi/phrase-connectors.json post /braze_multilang/v1/async/download-xliff-file
Download Braze multilingual content as XLIFF 2.0, ready for translation. The connector
applies its segmentation and serialization rules, including locale fallbacks across the
source and target Braze locale codes.
Pair with `upload-xliff-file` once translated.
**Async variant.** This endpoint returns immediately with `{ requestId, webHookUrl }`. The XLIFF is POSTed to the URL supplied in `X-Webhook`. `X-Webhook` is required.
# Download Braze multilingual content as XLIFF synchronously
Source: https://developers.phrase.com/en/api/connectors/braze/download-braze-multilingual-content-as-xliff-synchronously
/openapi/phrase-connectors.json post /braze_multilang/v1/sync/download-xliff-file
Download Braze multilingual content as XLIFF 2.0, ready for translation. The connector
applies its segmentation and serialization rules, including locale fallbacks across the
source and target Braze locale codes.
Pair with `upload-xliff-file` once translated.
# Download raw Braze multilingual content asynchronously
Source: https://developers.phrase.com/en/api/connectors/braze/download-raw-braze-multilingual-content-asynchronously
/openapi/phrase-connectors.json post /braze_multilang/v1/async/download-raw-file
Download raw multilingual content from Braze without any connector-side conversion. The
returned payload is Braze's native JSON representation of the content blocks / campaigns
selected by `path` and `configuration`.
Use this when you want the original, untranslated content (e.g. to build a custom
preview, snapshot a Braze workspace, or feed an analytics pipeline). For
translation-ready content, use `download-xliff-file` instead.
**Choosing sync vs async**
- `/sync/download-raw-file` blocks until the connector finishes. Use for small selections
and interactive callers. Subject to gateway timeouts (~60 s).
- `/async/download-raw-file` returns immediately with a `requestId` and posts the
result to the URL in `X-Webhook` when ready. Required for large selections.
**Response shape (sync only)** is controlled by `X-ResponseType`:
- `ID` (default) — JSON `{ "storageId": "..." }`. The caller fetches the content from
Phrase file storage later. Recommended for any non-trivial payload.
- `OBJECT` — raw `application/octet-stream`. Faster for tiny payloads, but holds the
file in memory.
**Async variant.** This endpoint returns immediately with `{ requestId, webHookUrl }`. The actual download result is POSTed to the URL supplied in `X-Webhook`. `X-Webhook` is required.
# Download raw Braze multilingual content synchronously
Source: https://developers.phrase.com/en/api/connectors/braze/download-raw-braze-multilingual-content-synchronously
/openapi/phrase-connectors.json post /braze_multilang/v1/sync/download-raw-file
Download raw multilingual content from Braze without any connector-side conversion. The
returned payload is Braze's native JSON representation of the content blocks / campaigns
selected by `path` and `configuration`.
Use this when you want the original, untranslated content (e.g. to build a custom
preview, snapshot a Braze workspace, or feed an analytics pipeline). For
translation-ready content, use `download-xliff-file` instead.
**Choosing sync vs async**
- `/sync/download-raw-file` blocks until the connector finishes. Use for small selections
and interactive callers. Subject to gateway timeouts (~60 s).
- `/async/download-raw-file` returns immediately with a `requestId` and posts the
result to the URL in `X-Webhook` when ready. Required for large selections.
**Response shape (sync only)** is controlled by `X-ResponseType`:
- `ID` (default) — JSON `{ "storageId": "..." }`. The caller fetches the content from
Phrase file storage later. Recommended for any non-trivial payload.
- `OBJECT` — raw `application/octet-stream`. Faster for tiny payloads, but holds the
file in memory.
# List Braze multilingual content asynchronously
Source: https://developers.phrase.com/en/api/connectors/braze/list-braze-multilingual-content-asynchronously
/openapi/phrase-connectors.json post /braze_multilang/v1/async/list-files
List Braze multilingual content reachable from `path` under the supplied
`configuration`. Use this for project setup, change detection, or to drive a UI picker.
The response is a list of `remoteResources`, each carrying a `path` you can feed
back into download / convert endpoints.
**Async variant.** Returns immediately with `{ requestId, webHookUrl }`. The listing is POSTed to the URL supplied in `X-Webhook`.
# List Braze multilingual content synchronously
Source: https://developers.phrase.com/en/api/connectors/braze/list-braze-multilingual-content-synchronously
/openapi/phrase-connectors.json post /braze_multilang/v1/sync/list-files
List Braze multilingual content reachable from `path` under the supplied
`configuration`. Use this for project setup, change detection, or to drive a UI picker.
The response is a list of `remoteResources`, each carrying a `path` you can feed
back into download / convert endpoints.
# Upload raw Braze multilingual content asynchronously
Source: https://developers.phrase.com/en/api/connectors/braze/upload-raw-braze-multilingual-content-asynchronously
/openapi/phrase-connectors.json post /braze_multilang/v1/async/upload-raw-file
Upload raw multilingual content back to Braze. The request body must reference an
already-stored file by `storageId` — upload the file to Phrase file storage first.
Use this when you have non-XLIFF content (e.g. a manually edited content-block export).
For translated XLIFF, use `upload-xliff-file`.
**Async variant.** This endpoint returns immediately with `{ requestId, webHookUrl }`. The actual upload result is POSTed to the URL supplied in `X-Webhook`. `X-Webhook` is required.
# Upload raw Braze multilingual content synchronously
Source: https://developers.phrase.com/en/api/connectors/braze/upload-raw-braze-multilingual-content-synchronously
/openapi/phrase-connectors.json post /braze_multilang/v1/sync/upload-raw-file
Upload raw multilingual content back to Braze. The request body must reference an
already-stored file by `storageId` — upload the file to Phrase file storage first.
Use this when you have non-XLIFF content (e.g. a manually edited content-block export).
For translated XLIFF, use `upload-xliff-file`.
# Upload translated XLIFF asynchronously
Source: https://developers.phrase.com/en/api/connectors/braze/upload-translated-xliff-asynchronously
/openapi/phrase-connectors.json post /braze_multilang/v1/async/upload-xliff-file
Upload translated XLIFF back to Braze. The connector parses the XLIFF, maps
target-language segments back onto the original Braze content, and writes them to the
target locale in the configured workspace.
**Async variant.** Returns immediately with `{ requestId, webHookUrl }`. The upload result is POSTed to the URL supplied in `X-Webhook`. `X-Webhook` is required.
# Upload translated XLIFF synchronously
Source: https://developers.phrase.com/en/api/connectors/braze/upload-translated-xliff-synchronously
/openapi/phrase-connectors.json post /braze_multilang/v1/sync/upload-xliff-file
Upload translated XLIFF back to Braze. The connector parses the XLIFF, maps
target-language segments back onto the original Braze content, and writes them to the
target locale in the configured workspace.
# List available connectors
Source: https://developers.phrase.com/en/api/connectors/connectors/list-available-connectors
/openapi/phrase-connectors.json get /connectors/v1
Returns all Bifrost-compatible connectors you have configured.
# Convert an XLIFF file from the connector to a raw file used by Contentful asynchronously
Source: https://developers.phrase.com/en/api/connectors/contentful/convert-an-xliff-file-from-the-connector-to-a-raw-file-used-by-contentful-asynchronously
/openapi/phrase-connectors.json post /contentful2/v1/async/convert-to-raw
Convert a translated XLIFF (referenced by `storageId`) back into Contentful's raw
entry format without writing it to Contentful. The result is stored in Phrase file
storage and returned as a `storageId` you can then push with `upload-raw-file`.
Use this when the upload step is decoupled from the conversion step (e.g. for a
preview/review gate).
**Async variant.** Returns `{ requestId, webHookUrl }` immediately; the raw file (or its `storageId`) is POSTed to `X-Webhook`.
# Convert an XLIFF file from the connector to a raw file used by Contentful synchronously
Source: https://developers.phrase.com/en/api/connectors/contentful/convert-an-xliff-file-from-the-connector-to-a-raw-file-used-by-contentful-synchronously
/openapi/phrase-connectors.json post /contentful2/v1/sync/convert-to-raw
Convert a translated XLIFF (referenced by `storageId`) back into Contentful's raw
entry format without writing it to Contentful. The result is stored in Phrase file
storage and returned as a `storageId` you can then push with `upload-raw-file`.
Use this when the upload step is decoupled from the conversion step (e.g. for a
preview/review gate).
# Convert raw content from Contentful to XLIFF asynchronously
Source: https://developers.phrase.com/en/api/connectors/contentful/convert-raw-content-from-contentful-to-xliff-asynchronously
/openapi/phrase-connectors.json post /contentful2/v1/async/convert-to-xliff
Convert raw Contentful content already in Phrase file storage to XLIFF 2.0, without
re-downloading from Contentful. Each entry in `rawFiles` references a stored file
(`storageId`) plus the metadata the connector needs to segment correctly.
Use this when you have a snapshot from `download-raw-file` and want to defer the
conversion step (e.g. to apply custom pre-processing first).
**Async variant.** Returns `{ requestId, webHookUrl }` immediately; the XLIFF (or its `storageId`) is POSTed to `X-Webhook`.
# Convert raw content from Contentful to XLIFF synchronously
Source: https://developers.phrase.com/en/api/connectors/contentful/convert-raw-content-from-contentful-to-xliff-synchronously
/openapi/phrase-connectors.json post /contentful2/v1/sync/convert-to-xliff
Convert raw Contentful content already in Phrase file storage to XLIFF 2.0, without
re-downloading from Contentful. Each entry in `rawFiles` references a stored file
(`storageId`) plus the metadata the connector needs to segment correctly.
Use this when you have a snapshot from `download-raw-file` and want to defer the
conversion step (e.g. to apply custom pre-processing first).
# Download raw content asynchronously
Source: https://developers.phrase.com/en/api/connectors/contentful/download-raw-content-asynchronously
/openapi/phrase-connectors.json post /contentful2/v1/async/download-raw-file
Download raw entry content from Contentful without any connector-side conversion. The
returned payload is Contentful's native JSON representation of the entries selected
by `path` and `configuration`.
Use this when you want the original, untranslated content (e.g. to build a custom
preview, snapshot a space, or feed an analytics pipeline). For translation-ready
content, use `download-xliff-file` instead.
**Choosing sync vs async**
- `/sync/download-raw-file` blocks until the connector finishes. Use for small spaces
and interactive callers. Subject to gateway timeouts (~60 s).
- `/async/download-raw-file` returns immediately with a `requestId` and posts the
result to the URL in `X-Webhook` when ready. Required for large spaces.
**Response shape (sync only)** is controlled by `X-ResponseType`:
- `ID` (default) — JSON `{ "storageId": "..." }`. The caller fetches the content from
Phrase file storage later. Recommended for any non-trivial payload.
- `OBJECT` — raw `application/octet-stream`. Faster for tiny payloads, but holds the
file in memory.
**Async variant.** This endpoint returns immediately with `{ requestId, webHookUrl }`. The actual download result is POSTed to the URL supplied in `X-Webhook`. `X-Webhook` is required.
# Download raw content synchronously
Source: https://developers.phrase.com/en/api/connectors/contentful/download-raw-content-synchronously
/openapi/phrase-connectors.json post /contentful2/v1/sync/download-raw-file
Download raw entry content from Contentful without any connector-side conversion. The
returned payload is Contentful's native JSON representation of the entries selected
by `path` and `configuration`.
Use this when you want the original, untranslated content (e.g. to build a custom
preview, snapshot a space, or feed an analytics pipeline). For translation-ready
content, use `download-xliff-file` instead.
**Choosing sync vs async**
- `/sync/download-raw-file` blocks until the connector finishes. Use for small spaces
and interactive callers. Subject to gateway timeouts (~60 s).
- `/async/download-raw-file` returns immediately with a `requestId` and posts the
result to the URL in `X-Webhook` when ready. Required for large spaces.
**Response shape (sync only)** is controlled by `X-ResponseType`:
- `ID` (default) — JSON `{ "storageId": "..." }`. The caller fetches the content from
Phrase file storage later. Recommended for any non-trivial payload.
- `OBJECT` — raw `application/octet-stream`. Faster for tiny payloads, but holds the
file in memory.
# Download xliff content asynchronously
Source: https://developers.phrase.com/en/api/connectors/contentful/download-xliff-content-asynchronously
/openapi/phrase-connectors.json post /contentful2/v1/async/download-xliff-file
Download Contentful entries as XLIFF 2.0, ready for translation. The connector
applies its segmentation and serialization rules (rich-text handling, reference
inlining, locale fallbacks).
Pair with `upload-xliff-file` once translated.
**Async variant.** This endpoint returns immediately with `{ requestId, webHookUrl }`. The XLIFF is POSTed to the URL supplied in `X-Webhook`. `X-Webhook` is required.
# Download xliff content synchronously
Source: https://developers.phrase.com/en/api/connectors/contentful/download-xliff-content-synchronously
/openapi/phrase-connectors.json post /contentful2/v1/sync/download-xliff-file
Download Contentful entries as XLIFF 2.0, ready for translation. The connector
applies its segmentation and serialization rules (rich-text handling, reference
inlining, locale fallbacks).
Pair with `upload-xliff-file` once translated.
# List content asynchronously
Source: https://developers.phrase.com/en/api/connectors/contentful/list-content-asynchronously
/openapi/phrase-connectors.json post /contentful2/v1/async/list-files
List Contentful entries (and optionally referenced assets) reachable from `path`
under the supplied `configuration`. Use this for project setup, change detection, or
to drive a UI picker.
The response is a list of `remoteResources`, each carrying a `path` you can feed
back into download / convert endpoints.
**Async variant.** Returns immediately with `{ requestId, webHookUrl }`. The listing is POSTed to the URL supplied in `X-Webhook`.
# List content synchronously
Source: https://developers.phrase.com/en/api/connectors/contentful/list-content-synchronously
/openapi/phrase-connectors.json post /contentful2/v1/sync/list-files
List Contentful entries (and optionally referenced assets) reachable from `path`
under the supplied `configuration`. Use this for project setup, change detection, or
to drive a UI picker.
The response is a list of `remoteResources`, each carrying a `path` you can feed
back into download / convert endpoints.
# Upload raw content asynchronously
Source: https://developers.phrase.com/en/api/connectors/contentful/upload-raw-content-asynchronously
/openapi/phrase-connectors.json post /contentful2/v1/async/upload-raw-file
Upload raw entry content back to Contentful for a given target locale. The request
body must reference an already-stored file by `storageId` — upload the file to Phrase
file storage first.
Use this when you have non-XLIFF content (e.g. a manually edited entry export). For
translated XLIFF, use `upload-xliff-file`.
**Async variant.** This endpoint returns immediately with `{ requestId, webHookUrl }`. The actual upload result is POSTed to the URL supplied in `X-Webhook`. `X-Webhook` is required.
# Upload raw content synchronously
Source: https://developers.phrase.com/en/api/connectors/contentful/upload-raw-content-synchronously
/openapi/phrase-connectors.json post /contentful2/v1/sync/upload-raw-file
Upload raw entry content back to Contentful for a given target locale. The request
body must reference an already-stored file by `storageId` — upload the file to Phrase
file storage first.
Use this when you have non-XLIFF content (e.g. a manually edited entry export). For
translated XLIFF, use `upload-xliff-file`.
# Upload xliff content asynchronously
Source: https://developers.phrase.com/en/api/connectors/contentful/upload-xliff-content-asynchronously
/openapi/phrase-connectors.json post /contentful2/v1/async/upload-xliff-file
Upload translated XLIFF back to Contentful. The connector parses the XLIFF, maps
target-language segments back onto the original entries, and writes them to the
target locale in the configured space.
**Async variant.** Returns immediately with `{ requestId, webHookUrl }`. The upload result is POSTed to the URL supplied in `X-Webhook`. `X-Webhook` is required.
# Upload xliff content synchronously
Source: https://developers.phrase.com/en/api/connectors/contentful/upload-xliff-content-synchronously
/openapi/phrase-connectors.json post /contentful2/v1/sync/upload-xliff-file
Upload translated XLIFF back to Contentful. The connector parses the XLIFF, maps
target-language segments back onto the original entries, and writes them to the
target locale in the configured space.
# Download file from file storage synchronously
Source: https://developers.phrase.com/en/api/connectors/files/download-file-from-file-storage-synchronously
/openapi/phrase-connectors.json get /files/v1/download-file/{uid}
Download file synchronously from the file storage.
# Upload file to file storage asynchronously
Source: https://developers.phrase.com/en/api/connectors/files/upload-file-to-file-storage-asynchronously
/openapi/phrase-connectors.json post /files/v1/async/upload-file
Upload file asynchronously to the file storage. The file is saved and can be retrieved by sending a GET request to this endpoint.
The returned ID can be used as a parameter for uploading files to third party systems or for file conversion.
The file will be deleted after 1 day.
# Upload file to file storage synchronously
Source: https://developers.phrase.com/en/api/connectors/files/upload-file-to-file-storage-synchronously
/openapi/phrase-connectors.json post /files/v1/sync/upload-file
Upload file synchronously to the file storage. The file is saved and can be retrieved by sending a GET request to this endpoint.
The returned ID can be used as a parameter for uploading files to third party systems or for file conversion.
The file will be deleted after 1 day.
# Batch upload raw files asynchronously — single commit via Git Blob/Tree API
Source: https://developers.phrase.com/en/api/connectors/github/batch-upload-raw-files-asynchronously-—-single-commit-via-git-blobtree-api
/openapi/phrase-connectors.json post /github2/v1/async/upload-batch-raw-files
# Batch upload raw files synchronously — single commit via Git Blob/Tree API
Source: https://developers.phrase.com/en/api/connectors/github/batch-upload-raw-files-synchronously-—-single-commit-via-git-blobtree-api
/openapi/phrase-connectors.json post /github2/v1/sync/upload-batch-raw-files
# Download raw content asynchronously
Source: https://developers.phrase.com/en/api/connectors/github/download-raw-content-asynchronously
/openapi/phrase-connectors.json post /github2/v1/async/download-raw-file
# Download raw content synchronously
Source: https://developers.phrase.com/en/api/connectors/github/download-raw-content-synchronously
/openapi/phrase-connectors.json post /github2/v1/sync/download-raw-file
# List content asynchronously
Source: https://developers.phrase.com/en/api/connectors/github/list-content-asynchronously
/openapi/phrase-connectors.json post /github2/v1/async/list-files
# List content synchronously
Source: https://developers.phrase.com/en/api/connectors/github/list-content-synchronously
/openapi/phrase-connectors.json post /github2/v1/sync/list-files
# Upload raw content asynchronously
Source: https://developers.phrase.com/en/api/connectors/github/upload-raw-content-asynchronously
/openapi/phrase-connectors.json post /github2/v1/async/upload-raw-file
# Upload raw content synchronously
Source: https://developers.phrase.com/en/api/connectors/github/upload-raw-content-synchronously
/openapi/phrase-connectors.json post /github2/v1/sync/upload-raw-file
# Download raw content from Google Drive asynchronously
Source: https://developers.phrase.com/en/api/connectors/google-drive/download-raw-content-from-google-drive-asynchronously
/openapi/phrase-connectors.json post /google-drive/v1/async/download-raw-file
Download a raw binary file from Google Drive without any connector-side conversion. The
returned payload is the original file bytes as stored in Drive (Docs/Sheets/Slides are
exported as their native binary equivalents when applicable).
Use this when you need the source artefact intact (e.g. PDF, DOCX, image, video). Google
Drive is a binary-file connector — there is no XLIFF or raw-to-XLIFF conversion endpoint.
**Choosing sync vs async**
- `/sync/download-raw-file` blocks until the connector finishes. Use for small files and
interactive callers. Subject to gateway timeouts (~60 s).
- `/async/download-raw-file` returns immediately with a `requestId` and posts the result
to the URL in `X-Webhook` when ready. Required for large files.
**Response shape (sync only)** is controlled by `X-ResponseType`:
- `ID` (default) — JSON `{ "storageId": "..." }`. The caller fetches the content from
Phrase file storage later. Recommended for any non-trivial payload.
- `OBJECT` — raw `application/octet-stream`. Faster for small payloads; the stream may
be proxied directly from Drive with upstream `Content-Type`, `Content-Disposition`,
and `Content-Length` headers preserved when available.
**Async variant.** Returns immediately with `{ requestId, webHookUrl }`. The download result is POSTed to the URL supplied in `X-Webhook`.
# Download raw content from Google Drive synchronously
Source: https://developers.phrase.com/en/api/connectors/google-drive/download-raw-content-from-google-drive-synchronously
/openapi/phrase-connectors.json post /google-drive/v1/sync/download-raw-file
Download a raw binary file from Google Drive without any connector-side conversion. The
returned payload is the original file bytes as stored in Drive (Docs/Sheets/Slides are
exported as their native binary equivalents when applicable).
Use this when you need the source artefact intact (e.g. PDF, DOCX, image, video). Google
Drive is a binary-file connector — there is no XLIFF or raw-to-XLIFF conversion endpoint.
**Choosing sync vs async**
- `/sync/download-raw-file` blocks until the connector finishes. Use for small files and
interactive callers. Subject to gateway timeouts (~60 s).
- `/async/download-raw-file` returns immediately with a `requestId` and posts the result
to the URL in `X-Webhook` when ready. Required for large files.
**Response shape (sync only)** is controlled by `X-ResponseType`:
- `ID` (default) — JSON `{ "storageId": "..." }`. The caller fetches the content from
Phrase file storage later. Recommended for any non-trivial payload.
- `OBJECT` — raw `application/octet-stream`. Faster for small payloads; the stream may
be proxied directly from Drive with upstream `Content-Type`, `Content-Disposition`,
and `Content-Length` headers preserved when available.
**Response shape (this endpoint)** is controlled by the `X-ResponseType` request header:
- `ID`: returns JSON with a `storageId` referencing file storage (`application/json`)
- `OBJECT`: returns a binary stream (`application/octet-stream`). Two variants:
- Streaming: proxied stream from the connector; status and headers (`Content-Type`, `Content-Disposition`, `Content-Length`) may come from upstream when present.
- File storage: Bifrost downloads from storage (single file or zip of multiple files) and returns `application/octet-stream` with `Content-Disposition: attachment; filename=""`.
# List Google Drive content asynchronously
Source: https://developers.phrase.com/en/api/connectors/google-drive/list-google-drive-content-asynchronously
/openapi/phrase-connectors.json post /google-drive/v1/async/list-files
List folders and files reachable from `path` in Google Drive. Use this for project
setup, change detection, or to drive a UI picker.
The `path.pathType` selects the scope:
- `ROOT` — list available top-level spaces ("My Drive" and "Shared drives")
- `SHARED_DRIVES_ROOT` — list all shared drives accessible to the credentials
- `FOLDER` — list children of the given folder
The response is a list of `remoteResources`. Each entry carries a resolved `path` you
can feed back into `download-raw-file` or `upload-raw-file`.
**Async variant.** Returns immediately with `{ requestId, webHookUrl }`. The listing is POSTed to the URL supplied in `X-Webhook`.
# List Google Drive content synchronously
Source: https://developers.phrase.com/en/api/connectors/google-drive/list-google-drive-content-synchronously
/openapi/phrase-connectors.json post /google-drive/v1/sync/list-files
List folders and files reachable from `path` in Google Drive. Use this for project
setup, change detection, or to drive a UI picker.
The `path.pathType` selects the scope:
- `ROOT` — list available top-level spaces ("My Drive" and "Shared drives")
- `SHARED_DRIVES_ROOT` — list all shared drives accessible to the credentials
- `FOLDER` — list children of the given folder
The response is a list of `remoteResources`. Each entry carries a resolved `path` you
can feed back into `download-raw-file` or `upload-raw-file`.
# Upload raw content to Google Drive asynchronously
Source: https://developers.phrase.com/en/api/connectors/google-drive/upload-raw-content-to-google-drive-asynchronously
/openapi/phrase-connectors.json post /google-drive/v1/async/upload-raw-file
Upload a raw binary file to Google Drive. Two delivery modes are supported via
`X-Upload-Mode`:
- **STORAGE** (default) — the request body references a file already in Phrase file
storage by `storageId`. The connector pulls the bytes itself and writes them to the
target Drive location. Returns the standard `UploadResponse` envelope.
- **STREAM** — initialize a two-phase streaming upload (recommended for large files).
`storageId` must be omitted. The response carries `{ uploadUrl, streamToken,
expiresAt, maxBytes }`. The caller then PUTs the raw bytes to `uploadUrl` with the
matching `X-Stream-Token` header — see `PUT /google-drive/v1/sync/upload-raw-file/stream/{streamId}`.
For async uploads, only STORAGE mode is supported and `storageId` is required.
**Async variant.** Only `X-Upload-Mode: STORAGE` is supported — `storageId` is required. The endpoint returns immediately with `{ requestId, webHookUrl }` and the actual upload result is POSTed to the URL supplied in `X-Webhook`.
# Upload raw content to Google Drive synchronously
Source: https://developers.phrase.com/en/api/connectors/google-drive/upload-raw-content-to-google-drive-synchronously
/openapi/phrase-connectors.json post /google-drive/v1/sync/upload-raw-file
Upload a raw binary file to Google Drive. Two delivery modes are supported via
`X-Upload-Mode`:
- **STORAGE** (default) — the request body references a file already in Phrase file
storage by `storageId`. The connector pulls the bytes itself and writes them to the
target Drive location. Returns the standard `UploadResponse` envelope.
- **STREAM** — initialize a two-phase streaming upload (recommended for large files).
`storageId` must be omitted. The response carries `{ uploadUrl, streamToken,
expiresAt, maxBytes }`. The caller then PUTs the raw bytes to `uploadUrl` with the
matching `X-Stream-Token` header — see `PUT /google-drive/v1/sync/upload-raw-file/stream/{streamId}`.
For async uploads, only STORAGE mode is supported and `storageId` is required.
# Upload raw file bytes to Google Drive (streaming, phase 2)
Source: https://developers.phrase.com/en/api/connectors/google-drive/upload-raw-file-bytes-to-google-drive-streaming-phase-2
/openapi/phrase-connectors.json put /google-drive/v1/sync/upload-raw-file/stream/{streamId}
Phase 2 of the streaming upload flow. The caller PUTs raw bytes to this endpoint after
initializing a streaming upload via `POST /google-drive/v1/sync/upload-raw-file` with
`X-Upload-Mode: STREAM`.
The `{streamId}` path segment and `X-Stream-Token` header value both come from the
phase-1 response (`uploadUrl` already embeds the `streamId`; `X-Stream-Token` is the
`streamToken` field). The token is single-use and expires at `expiresAt`. The body must
be the raw file bytes; total size must not exceed the `maxBytes` returned by phase 1.
On success, the endpoint returns `204 No Content`.
# Introduction
Source: https://developers.phrase.com/en/api/connectors/introduction
## Phrase Connectors API Reference
A tool for building custom flows using Phrase Integrations. Easily connect localization processes with external systems and extend built-in integrations with your own functionality.
# Convert stored raw Optimizely content to XLIFF asynchronously
Source: https://developers.phrase.com/en/api/connectors/optimizely/convert-stored-raw-optimizely-content-to-xliff-asynchronously
/openapi/phrase-connectors.json post /optimizely/v1/async/convert-to-xliff
Convert raw Optimizely content already in Phrase file storage to XLIFF 2.0, without
re-downloading from Optimizely. Each entry in `rawFiles` references a stored file
(`storageId`) plus the metadata the connector needs to segment correctly.
Use this when you have a snapshot from `download-raw-file` and want to defer the
conversion step (e.g. to apply custom pre-processing first).
**Async variant.** Returns `{ requestId, webHookUrl }` immediately; the XLIFF (or its `storageId`) is POSTed to `X-Webhook`.
# Convert stored raw Optimizely content to XLIFF synchronously
Source: https://developers.phrase.com/en/api/connectors/optimizely/convert-stored-raw-optimizely-content-to-xliff-synchronously
/openapi/phrase-connectors.json post /optimizely/v1/sync/convert-to-xliff
Convert raw Optimizely content already in Phrase file storage to XLIFF 2.0, without
re-downloading from Optimizely. Each entry in `rawFiles` references a stored file
(`storageId`) plus the metadata the connector needs to segment correctly.
Use this when you have a snapshot from `download-raw-file` and want to defer the
conversion step (e.g. to apply custom pre-processing first).
# Convert stored XLIFF back to raw Optimizely content asynchronously
Source: https://developers.phrase.com/en/api/connectors/optimizely/convert-stored-xliff-back-to-raw-optimizely-content-asynchronously
/openapi/phrase-connectors.json post /optimizely/v1/async/convert-to-raw
Convert a translated XLIFF (referenced by `storageId`) back into Optimizely's raw
content format without writing it to Optimizely. The result is stored in Phrase file
storage and returned as a `storageId` you can then push with `upload-raw-file`.
Use this when the upload step is decoupled from the conversion step (e.g. for a
preview/review gate).
**Async variant.** Returns `{ requestId, webHookUrl }` immediately; the raw file (or its `storageId`) is POSTed to `X-Webhook`.
# Convert stored XLIFF back to raw Optimizely content synchronously
Source: https://developers.phrase.com/en/api/connectors/optimizely/convert-stored-xliff-back-to-raw-optimizely-content-synchronously
/openapi/phrase-connectors.json post /optimizely/v1/sync/convert-to-raw
Convert a translated XLIFF (referenced by `storageId`) back into Optimizely's raw
content format without writing it to Optimizely. The result is stored in Phrase file
storage and returned as a `storageId` you can then push with `upload-raw-file`.
Use this when the upload step is decoupled from the conversion step (e.g. for a
preview/review gate).
# Download Optimizely content as XLIFF asynchronously
Source: https://developers.phrase.com/en/api/connectors/optimizely/download-optimizely-content-as-xliff-asynchronously
/openapi/phrase-connectors.json post /optimizely/v1/async/download-xliff-file
Download Optimizely content as XLIFF 2.0, ready for translation. The connector applies
its segmentation and serialization rules to produce a translation-ready file targeting
the requested locale pair.
Pair with `upload-xliff-file` once translated.
**Async variant.** This endpoint returns immediately with `{ requestId, webHookUrl }`. The XLIFF is POSTed to the URL supplied in `X-Webhook`. `X-Webhook` is required.
# Download Optimizely content as XLIFF synchronously
Source: https://developers.phrase.com/en/api/connectors/optimizely/download-optimizely-content-as-xliff-synchronously
/openapi/phrase-connectors.json post /optimizely/v1/sync/download-xliff-file
Download Optimizely content as XLIFF 2.0, ready for translation. The connector applies
its segmentation and serialization rules to produce a translation-ready file targeting
the requested locale pair.
Pair with `upload-xliff-file` once translated.
# Download raw Optimizely content asynchronously
Source: https://developers.phrase.com/en/api/connectors/optimizely/download-raw-optimizely-content-asynchronously
/openapi/phrase-connectors.json post /optimizely/v1/async/download-raw-file
Download raw content from Optimizely without any connector-side conversion. The returned
payload is Optimizely's native representation of the content items selected by `path` and
`configuration`.
Use this when you want the original, untranslated content (e.g. to build a custom
preview, snapshot a project, or feed an analytics pipeline). For translation-ready
content, use `download-xliff-file` instead.
**Choosing sync vs async**
- `/sync/download-raw-file` blocks until the connector finishes. Use for small payloads
and interactive callers. Subject to gateway timeouts (~60 s).
- `/async/download-raw-file` returns immediately with a `requestId` and posts the result
to the URL in `X-Webhook` when ready. Required for large payloads.
**Response shape (sync only)** is controlled by `X-ResponseType`:
- `ID` (default) — JSON `{ "storageId": "..." }`. The caller fetches the content from
Phrase file storage later. Recommended for any non-trivial payload.
- `OBJECT` — raw `application/octet-stream`. Faster for tiny payloads, but holds the
file in memory.
**Async variant.** This endpoint returns immediately with `{ requestId, webHookUrl }`. The actual download result is POSTed to the URL supplied in `X-Webhook`. `X-Webhook` is required.
# Download raw Optimizely content synchronously
Source: https://developers.phrase.com/en/api/connectors/optimizely/download-raw-optimizely-content-synchronously
/openapi/phrase-connectors.json post /optimizely/v1/sync/download-raw-file
Download raw content from Optimizely without any connector-side conversion. The returned
payload is Optimizely's native representation of the content items selected by `path` and
`configuration`.
Use this when you want the original, untranslated content (e.g. to build a custom
preview, snapshot a project, or feed an analytics pipeline). For translation-ready
content, use `download-xliff-file` instead.
**Choosing sync vs async**
- `/sync/download-raw-file` blocks until the connector finishes. Use for small payloads
and interactive callers. Subject to gateway timeouts (~60 s).
- `/async/download-raw-file` returns immediately with a `requestId` and posts the result
to the URL in `X-Webhook` when ready. Required for large payloads.
**Response shape (sync only)** is controlled by `X-ResponseType`:
- `ID` (default) — JSON `{ "storageId": "..." }`. The caller fetches the content from
Phrase file storage later. Recommended for any non-trivial payload.
- `OBJECT` — raw `application/octet-stream`. Faster for tiny payloads, but holds the
file in memory.
# List Optimizely content items asynchronously
Source: https://developers.phrase.com/en/api/connectors/optimizely/list-optimizely-content-items-asynchronously
/openapi/phrase-connectors.json post /optimizely/v1/async/list-files
List Optimizely content items reachable from `path` under the supplied `configuration`.
Use this for project setup, change detection, or to drive a UI picker.
The response is a list of `remoteResources`, each carrying a `path` you can feed back
into download / convert endpoints.
**Async variant.** Returns immediately with `{ requestId, webHookUrl }`. The listing is POSTed to the URL supplied in `X-Webhook`.
# List Optimizely content items synchronously
Source: https://developers.phrase.com/en/api/connectors/optimizely/list-optimizely-content-items-synchronously
/openapi/phrase-connectors.json post /optimizely/v1/sync/list-files
List Optimizely content items reachable from `path` under the supplied `configuration`.
Use this for project setup, change detection, or to drive a UI picker.
The response is a list of `remoteResources`, each carrying a `path` you can feed back
into download / convert endpoints.
# Upload raw Optimizely content asynchronously
Source: https://developers.phrase.com/en/api/connectors/optimizely/upload-raw-optimizely-content-asynchronously
/openapi/phrase-connectors.json post /optimizely/v1/async/upload-raw-file
Upload raw content back to Optimizely for a given target locale. The request body must
reference an already-stored file by `storageId` — upload the file to Phrase file storage
first.
Use this when you have non-XLIFF content (e.g. a manually edited content export). For
translated XLIFF, use `upload-xliff-file`.
**Async variant.** This endpoint returns immediately with `{ requestId, webHookUrl }`. The actual upload result is POSTed to the URL supplied in `X-Webhook`. `X-Webhook` is required.
# Upload raw Optimizely content synchronously
Source: https://developers.phrase.com/en/api/connectors/optimizely/upload-raw-optimizely-content-synchronously
/openapi/phrase-connectors.json post /optimizely/v1/sync/upload-raw-file
Upload raw content back to Optimizely for a given target locale. The request body must
reference an already-stored file by `storageId` — upload the file to Phrase file storage
first.
Use this when you have non-XLIFF content (e.g. a manually edited content export). For
translated XLIFF, use `upload-xliff-file`.
# Upload translated XLIFF asynchronously
Source: https://developers.phrase.com/en/api/connectors/optimizely/upload-translated-xliff-asynchronously
/openapi/phrase-connectors.json post /optimizely/v1/async/upload-xliff-file
Upload translated XLIFF back to Optimizely. The connector parses the XLIFF, maps
target-language segments back onto the original content items, and writes them to the
target locale in the configured project.
**Async variant.** Returns immediately with `{ requestId, webHookUrl }`. The upload result is POSTed to the URL supplied in `X-Webhook`. `X-Webhook` is required.
# Upload translated XLIFF synchronously
Source: https://developers.phrase.com/en/api/connectors/optimizely/upload-translated-xliff-synchronously
/openapi/phrase-connectors.json post /optimizely/v1/sync/upload-xliff-file
Upload translated XLIFF back to Optimizely. The connector parses the XLIFF, maps
target-language segments back onto the original content items, and writes them to the
target locale in the configured project.
# Download raw Tridion content asynchronously
Source: https://developers.phrase.com/en/api/connectors/tridion-docs/download-raw-tridion-content-asynchronously
/openapi/phrase-connectors.json post /tridion/v1/async/download-raw-file
Download raw content from SDL Tridion without any connector-side conversion. The
returned payload is the native item content (typically XML) for the items selected
by `path` and `configuration`.
Use this when you want the original, untranslated content (e.g. to build a custom
preview, snapshot a folder, or feed an analytics pipeline).
**Choosing sync vs async**
- `/sync/download-raw-file` blocks until the connector finishes. Use for small
selections and interactive callers. Subject to gateway timeouts (~60 s).
- `/async/download-raw-file` returns immediately with a `requestId` and posts the
result to the URL in `X-Webhook` when ready. Required for large selections.
**Response shape (sync only)** is controlled by `X-ResponseType`:
- `ID` (default) — JSON `{ "storageId": "..." }`. The caller fetches the content from
Phrase file storage later. Recommended for any non-trivial payload.
- `OBJECT` — raw `application/octet-stream`. Faster for tiny payloads, but holds the
file in memory.
**Async variant.** This endpoint returns immediately with `{ requestId, webHookUrl }`. The actual download result is POSTed to the URL supplied in `X-Webhook`. `X-Webhook` is required.
# Download raw Tridion content synchronously
Source: https://developers.phrase.com/en/api/connectors/tridion-docs/download-raw-tridion-content-synchronously
/openapi/phrase-connectors.json post /tridion/v1/sync/download-raw-file
Download raw content from SDL Tridion without any connector-side conversion. The
returned payload is the native item content (typically XML) for the items selected
by `path` and `configuration`.
Use this when you want the original, untranslated content (e.g. to build a custom
preview, snapshot a folder, or feed an analytics pipeline).
**Choosing sync vs async**
- `/sync/download-raw-file` blocks until the connector finishes. Use for small
selections and interactive callers. Subject to gateway timeouts (~60 s).
- `/async/download-raw-file` returns immediately with a `requestId` and posts the
result to the URL in `X-Webhook` when ready. Required for large selections.
**Response shape (sync only)** is controlled by `X-ResponseType`:
- `ID` (default) — JSON `{ "storageId": "..." }`. The caller fetches the content from
Phrase file storage later. Recommended for any non-trivial payload.
- `OBJECT` — raw `application/octet-stream`. Faster for tiny payloads, but holds the
file in memory.
# List Tridion items asynchronously
Source: https://developers.phrase.com/en/api/connectors/tridion-docs/list-tridion-items-asynchronously
/openapi/phrase-connectors.json post /tridion/v1/async/list-files
List Tridion items reachable from `path` under the supplied `configuration`. Use
this for project setup, change detection, or to drive a UI picker.
The response is a list of `remoteResources`, each carrying a `path` you can feed
back into download endpoints.
**Async variant.** Returns immediately with `{ requestId, webHookUrl }`. The listing is POSTed to the URL supplied in `X-Webhook`.
# List Tridion items synchronously
Source: https://developers.phrase.com/en/api/connectors/tridion-docs/list-tridion-items-synchronously
/openapi/phrase-connectors.json post /tridion/v1/sync/list-files
List Tridion items reachable from `path` under the supplied `configuration`. Use
this for project setup, change detection, or to drive a UI picker.
The response is a list of `remoteResources`, each carrying a `path` you can feed
back into download endpoints.
# Upload raw Tridion content asynchronously
Source: https://developers.phrase.com/en/api/connectors/tridion-docs/upload-raw-tridion-content-asynchronously
/openapi/phrase-connectors.json post /tridion/v1/async/upload-raw-file
Upload raw content back to SDL Tridion for a given target locale. The request body
must reference an already-stored file by `storageId` — upload the file to Phrase
file storage first.
Use this when you have non-XLIFF content (e.g. a manually edited item export).
**Async variant.** This endpoint returns immediately with `{ requestId, webHookUrl }`. The actual upload result is POSTed to the URL supplied in `X-Webhook`. `X-Webhook` is required.
# Upload raw Tridion content synchronously
Source: https://developers.phrase.com/en/api/connectors/tridion-docs/upload-raw-tridion-content-synchronously
/openapi/phrase-connectors.json post /tridion/v1/sync/upload-raw-file
Upload raw content back to SDL Tridion for a given target locale. The request body
must reference an already-stored file by `storageId` — upload the file to Phrase
file storage first.
Use this when you have non-XLIFF content (e.g. a manually edited item export).
# Get analytics
Source: https://developers.phrase.com/en/api/quality-evaluator/latest/analytics/get-analytics
/openapi/phrase-quality-evaluator-latest.json get /v1/analytics
Returns aggregated daily evaluation rollup data for the caller's organization. Filtered by optional quality profile and date range.
# Authentication
Source: https://developers.phrase.com/en/api/quality-evaluator/latest/authentication
The Quality Evaluator API uses Phrase Platform authentication.
Generate an API token for **Quality Evaluator** in the [Phrase Platform Settings](https://eu.phrase.com/idm-ui/settings/access-tokens), then exchange it for a JWT as described in the [Platform Authentication guide](/en/api/platform/authentication).
Include the JWT in the `Authorization` header with every request:
```bash theme={null}
curl -X GET "https://eu.phrase.com/quality-evaluator/v3/qualityProfiles/cg-abc123" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
### Required roles
Most endpoints only require an authenticated caller. Analytics is restricted further:
| Access level | Endpoints |
| --------------------------- | -------------------------------- |
| Any authenticated user | Quality Profile by Content Group |
| **ADMIN** or **OWNER** role | Evaluate segments, Analytics |
Requests to restricted endpoints made by users without the required role will receive a `403 Forbidden` response.
# Evaluate segments
Source: https://developers.phrase.com/en/api/quality-evaluator/latest/evaluation/evaluate-segments
/openapi/phrase-quality-evaluator-latest.json post /v3/evaluation
Evaluates all segments against the AI Checks resolved from the given Content Group.
When no checks are associated with the Content Group, all segments are returned with an empty `results` array — no error is raised.
Requires **ADMIN** or **OWNER** IDM role.
# Introduction
Source: https://developers.phrase.com/en/api/quality-evaluator/latest/introduction
## Quality Evaluator API Reference 1.0.0
The Quality Evaluator API uses LLMs to automatically assess translation quality against
requirements you define — in plain language, not fixed rule sets. It scales the kind of
review a human linguist would do: check tone, terminology, formatting, and custom
requirements across any volume of segments.
Quality requirements are managed as Style Guide Rules attached to a **Content Group**.
Link evaluation to the Content Group and the API resolves its active Rules into AI Checks
automatically — no separate profile to create or keep in sync.
### Key Features
* **Content Group integration**: Quality requirements stay in sync with Style Guide Rules
without manual profile maintenance
* **AI Checks**: Rules are resolved into checks defined in natural language (e.g.,
"Translation must not use Yoda-style speech")
* **Analytics**: Track evaluation metrics and AI unit consumption over time
### Base URLs
The Quality Evaluator API is available in multiple regions:
| Region | Base URL |
| ------ | ----------------------------------------- |
| EU | `https://eu.phrase.com/quality-evaluator` |
| US | `https://us.phrase.com/quality-evaluator` |
### Quick Start
1. **Attach Style Guide Rules** to your Content Group (with AI Check support enabled)
2. **Evaluate segments** by referencing the Content Group — checks are resolved automatically
```bash theme={null}
# Example: Evaluate segments against a Content Group's resolved AI Checks
curl -X POST "https://eu.phrase.com/quality-evaluator/v3/evaluation" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"contentGroupId": "cg-abc123",
"segments": [
{
"id": "seg-1",
"source": "Hello, world!",
"target": "Hallo, Welt!"
}
],
"sourceLocaleCode": "en_us",
"targetLocaleCode": "de_de"
}'
```
# Get Quality Profile by Content Group
Source: https://developers.phrase.com/en/api/quality-evaluator/latest/quality-profiles/get-quality-profile-by-content-group
/openapi/phrase-quality-evaluator-latest.json get /v3/qualityProfiles/{contentGroupId}
Returns the Quality Profile for the given Content Group, listing all active rules that have AI check support enabled. The profile is resolved dynamically to reflect the current state of the linked Content Group.
Optionally filter by locale to retrieve only rules that apply to a specific language.
# Authentication
Source: https://developers.phrase.com/en/api/style-guides/authentication
The Style Guide API uses Phrase Platform authentication.
Generate an API token for **Phrase Style Guides** in the [Phrase Platform Settings](https://eu.phrase.com/idm-ui/settings/access-tokens), then exchange it for a JWT as described in the [Platform Authentication guide](/en/api/platform/authentication).
Include the JWT in the `Authorization` header on every request:
```bash theme={null}
curl -X GET "https://eu.phrase.com/styleguide/api/v1/styleguides/018e1234-5678-7890-abcd-ef1234567890" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
JWTs expire after a few hours. Re-issue a new JWT from your long-lived API token when the previous one expires; do not store JWTs at rest.
# Core Concepts
Source: https://developers.phrase.com/en/api/style-guides/concepts
This page explains the data model behind the Style Guide API and how Style Guides, Rules, and Content Groups relate
to each other and to TMS projects.
**New integrations should use the v2 endpoints** (`POST /api/v2/styleguides`, `PUT /api/v2/styleguides/{id}`). They
accept a `contentGroupId` parameter that links the Style Guide to a Content Group and unlocks automatic rule
application in TMS projects.
## Style Guides
A Style Guide is a Markdown document that captures writing guidelines for a specific language — tone, grammar rules,
terminology, and formatting conventions. An organization can have one Style Guide per Content Group and language.
Key properties:
* **Language** — BCP-47 locale code (e.g., `en-GB`, `sv-SE`). Each Style Guide is scoped to exactly one language.
* **Name and description** — human-readable metadata updated on every revision.
* **Content Group** — link to a Content Group (see below). Required when using v2 endpoints.
## Versions
Every time a new Markdown file is uploaded to a Style Guide, a new version is created. Versions are immutable
snapshots — you cannot edit a version's file after creation, only supersede it with a new upload.
Each version records:
| Field | Description |
| --------------------------------------- | --------------------------------------------------------------------------------- |
| `versionNumber` | Monotonically increasing integer |
| `name` / `description` | Metadata at the time of upload |
| `changeNotes` | Optional free-text summary of what changed (`versionChangeReason` on the request) |
| `fileName`, `fileSizeBytes`, `fileType` | File metadata |
The Style Guide's top-level `name`, `description`, and `lastModifiedAt` always reflect the latest version.
## Rules
A Rule is a single writing instruction — for example, "Avoid passive voice in all user-facing content.". Rules are
used by AI Translation Agent and MT Optimize evaluate to improve translation quality.
### Origins
Rules come from two sources:
| Origin | How it works |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Extracted** | Automatically generated when a Style Guide is created or updated. Each distinct guideline in the Markdown file becomes a separate Rule, linked to the Style Guide. |
| **Manual** | Created directly via `POST /api/v1/rules`. Useful for rules that don't belong to any Style Guide, or for adding guidelines incrementally. |
### Scope
Each Rule carries two independent scope dimensions:
**Content Group scope** — which Content Groups this rule applies to:
* `allContentGroups: true` — rule applies across every Content Group in the organization.
* `allContentGroups: false` + non-empty `contentGroupIds` — rule applies only to the listed Content Groups.
**Language scope** — which languages this rule applies to:
* `allLanguages: true` — rule applies to translations in every language.
* `allLanguages: false` + non-empty `languages` — rule applies only to the listed locale codes.
### Lifecycle
Extracted Rules start out linked to their source Style Guide (`styleGuide` field is populated). A Rule becomes
**unlinked** (standalone) when a `PATCH /api/v1/rules/{id}` request does either of the following:
* Edits the rule text.
* **Changes** the content group or language scope in any way — including narrowing it, broadening it, or switching
to `allContentGroups: true` / `allLanguages: true`. Resubmitting the exact same scope the rule already has is
not a change and does not unlink it.
Because unlinking is permanent, `PATCH /api/v1/rules/{id}` requires you to confirm it explicitly: if the update
would unlink the rule and the request does not set `forceUnlink: true`, the API returns `409 Conflict`
(`RULE_UNLINK_REQUIRES_CONFIRMATION`) and applies none of the requested changes. Retry the same request with
`forceUnlink: true` to proceed. See [Working with Rules](/en/api/style-guides/rules) for an example.
Unlinking turns the rule into a manual rule. The original Style Guide is unchanged.
**When a new Style Guide version is uploaded**, every rule still linked to that Style Guide is deleted and replaced
by the freshly extracted set. Only rules that were already **unlinked** beforehand (via a prior `forceUnlink: true`
edit) survive a new version upload as standalone rules. If the updated Style Guide file still contains an
equivalent guideline, the newly extracted rule can duplicate one of these surviving standalone rules. Review
standalone rules that originated from a Style Guide after uploading a new version, and remove any that now
duplicate a freshly extracted rule.
### Active and AI check flags
Each Rule has two toggles:
* `active` — disabled rules are excluded from all checks and are not used by AI Translation Agent or MT Optimize.
* `aiCheckEnabled` — when `false`, the rule is excluded from AI-powered Quality Assurance checks but is still used
by AI Translation Agent and MT Optimize. A rule cannot have `aiCheckEnabled: true` unless it is also active.
Both flags are subject to the active-rule and AI-check limits described in [Limits](#limits) below:
* Explicitly requesting `active: true` (or `aiCheckEnabled: true`) fails with `409 Conflict` if any (Content Group,
Language) pair the rule applies to is already at its limit.
* If the flag is omitted on create or update, the service decides it automatically: `true` if every applicable
(Content Group, Language) pair still has room, `false` otherwise — no error is returned in this case.
## Content Groups
A Content Group is an identifier managed by the **Content Group API** (a separate service). It is a broader Platform
concept that is used to organize Assets and Projects across logical groups. In case of Style Guides, a content
group acts as a shared reference point that connects Style Guides and their Rules to TMS projects.
Content Groups are created and managed outside this API. The Style Guide API accepts a Content Group ID as an opaque
string — use the Content Group API to create and list groups.
## How Everything Connects
```
Style Guide (language: en-GB)
├── linked to → Content Group "marketing"
└── source of → Rules (each scoped to Content Group "marketing", language en-GB)
TMS Project
└── linked to → Content Group "marketing"
└── at translation time, applies all active Rules where:
contentGroupIds contains "marketing" (or allContentGroups = true)
AND languages contains job language (or allLanguages = true)
```
### TMS project integration
In previous versions of the API, a TMS project had to be linked to a Style Guide per target language. With Content
Groups, the workflow changes:
1. Create a Style Guide with a `contentGroupId` (v2 endpoint).
2. Link the TMS project to the same Content Group (via TMS project settings).
3. When a translation job runs, the AI Translation Agent and MT Optimize automatically pick up all active Rules
whose content group and language scope match the job's Content Group and target language.
This means a single Content Group can aggregate Rules from multiple Style Guides across languages, and one Style
Guide can serve multiple projects through a shared Content Group.
Rule changes — whether made directly via the Rules API or triggered by uploading a new Style Guide version — take
effect immediately for any running jobs in that Content Group. To avoid unintended effects on in-flight
translations, make rule changes when no jobs for that Content Group are active. You can update and review rules
before starting new jobs to verify they produce the expected results.
## Limits
| Limit | Value | Scope |
| ---------------------------------- | -------------- | --------------------------------------- |
| Active rules | 50 | Per (Content Group, Language) pair |
| Active rules with AI check enabled | 20 | Per (Content Group, Language) pair |
| Rule text length | 450 characters | Per rule |
| Content Groups per rule | 100 | Per rule (`contentGroupIds`) |
| Languages per rule | 100 | Per rule (`languages`) |
| Content Group / language filters | 50 | Per `POST /api/v1/rules/search` request |
**Active-rule limit** — no more than 50 rules may be active at once for any single (Content Group, Language) pair.
A rule counts toward a pair's limit if it's active and linked to that Content Group (directly, or via
`allContentGroups: true`) and that Language (directly, or via `allLanguages: true`). A rule linked to multiple
Content Groups or Languages is checked against every pair it applies to — the request is rejected if any one of
them is full.
**AI-check limit** — no more than 20 active rules may have `aiCheckEnabled: true` at once for any single
(Content Group, Language) pair, using the same direct-or-global linkage rule. A rule cannot have AI check enabled
unless it is also active.
Requests that would explicitly push a pair over either limit are rejected with `409 Conflict`
(`ACTIVE_RULE_LIMIT_REACHED` or `AI_CHECK_LIMIT_REACHED`), identifying the limit and the affected Content Group
and Language. See [Working with Rules](/en/api/style-guides/rules) for an example.
**Style Guide extraction and the limits** — extracting rules from a Style Guide never touches the active or
AI-check status of rules that were already active for that Content Group and Language. If a batch of newly
extracted rules would exceed the remaining room, only as many as fit — taken in the order they appear in the
Markdown file — are kept active (and AI-check-enabled); the rest are still saved, just inactive. The
`extractedRulesCount` in the create/update response always reflects the full extracted count, regardless of how
many ended up active.
## Summary
| Resource | Scope | Created by |
| ------------- | ---------------------------------------------------- | --------------------------------------- |
| Style Guide | One per Content Group & language per organization | API upload (v1 or v2) |
| Version | One per file upload | Automatic on Style Guide create/update |
| Rule | Many per Style Guide; standalone rules also possible | Automatic extraction or manual creation |
| Content Group | Organization-wide | Content Group API |
For step-by-step API usage, see [Working with Rules](/en/api/style-guides/rules) and the **API Documentation** section in the left navigation.
# Introduction
Source: https://developers.phrase.com/en/api/style-guides/introduction
## Style Guide API Reference
The Style Guide API lets you manage organization-wide writing guidelines that Phrase applies across translations and
authored content. A Style Guide is a Markdown document scoped to a single language and versioned on every revision.
Rules are automatically extracted from each Style Guide and can also be created manually — they improve the output
quality in AI Translation Agent and MT Optimize.
**New integrations should use the v2 endpoints** (`POST /api/v2/styleguides`, `PUT /api/v2/styleguides/{id}`). The
v2 endpoints require a Content Group ID, which enables automatic rule application in TMS projects. The v1 create
and update endpoints are deprecated.
### Key Features
* **Markdown-first authoring** — upload Markdown files; the active version is always the latest revision
* **Multi-language** — manage one Style Guide per language
* **Versioning** — every revision is stored with optional change notes; previous versions stay accessible
* **Rules** — rules extracted automatically from each Style Guide, or created manually; improve AI Translation
Agent and MT Optimize output quality
* **Content Group integration** — link a Style Guide to a Content Group so TMS projects inherit its rules automatically
* **Search and filter** — find Style Guides by name, description, language, or last-modified date
* **Bulk delete** — remove multiple Style Guides in a single call, with per-item success / failure reporting
Active rules are capped at 50, and active rules with AI check enabled are capped at 20, per (Content Group,
Language) pair. See [Limits](/en/api/style-guides/concepts#limits) for details.
### Base URLs
The Style Guide API is available in multiple regions:
| Region | Base URL |
| ------ | ---------------------------------- |
| EU | `https://eu.phrase.com/styleguide` |
| US | `https://us.phrase.com/styleguide` |
### Quick Start
1. **Exchange your API token** for a JWT via the [Platform Authentication guide](/en/api/platform/authentication)
2. **Create a Style Guide** by uploading a Markdown file with a Content Group ID
3. **Poll the job status** endpoint until the job completes
4. **Reference the Style Guide** by ID in subsequent calls
```bash theme={null}
curl -X POST "https://eu.phrase.com/styleguide/api/v2/styleguides" \
-H "Authorization: Bearer $JWT" \
-F language=en-GB \
-F name="English Style Guide" \
-F description="Brand voice and grammar rules" \
-F contentGroupId="my-content-group" \
-F file=@./style-guide.md
```
The response returns a `jobId`. Poll **Get create job status** until `status` is `COMPLETED`:
```bash theme={null}
curl "https://eu.phrase.com/styleguide/api/v1/styleguides/async/create-jobs/$JOB_ID" \
-H "Authorization: Bearer $JWT"
```
Browse the full operation set, request and response schemas, and per-endpoint examples in the **API Documentation**
section of the left navigation. For a conceptual overview of Style Guides, Rules, and Content Groups, see [Core Concepts](/en/api/style-guides/concepts).
# Working with Rules
Source: https://developers.phrase.com/en/api/style-guides/rules
Rules are the writing instructions that Phrase uses to improve the translation quality for AI Translation
Agent and MT Optimize. Rules are also used for AI-powered quality checks. This guide walks through the full
lifecycle: from uploading a Style Guide and inspecting the extracted rules, to creating manual rules and updating
them over time.
For a conceptual overview of how Rules relate to Style Guides and Content Groups, see [Core Concepts](/en/api/style-guides/concepts).
## Extracted rules vs manual rules
| | Extracted | Manual |
| ------------------------- | --------------------------------------------------------------------------------------------------------- | ----------------------------- |
| **Created by** | Automatic, on Style Guide create or update | `POST /api/v1/rules` |
| **Linked to Style Guide** | Yes (`styleGuide` field populated) | No |
| **Scope on creation** | Inherits Style Guide's Content Group and language | Set explicitly in the request |
| **Unlinked when** | Rule text is edited, or content group/language scope is changed in any way (requires `forceUnlink: true`) | Never linked to begin with |
Use extracted rules when your team maintains a Style Guide Markdown file as the source of truth. Use manual rules
for guidelines that live outside any Style Guide, or when you want to add or override individual rules without
uploading a new file.
## End-to-end walkthrough
### 1. Create a Style Guide
Upload a Markdown file using the v2 endpoint with a Content Group ID. The API returns a `jobId` immediately and
processes the file asynchronously.
```bash theme={null}
curl -X POST "https://eu.phrase.com/styleguide/api/v2/styleguides" \
-H "Authorization: Bearer $JWT" \
-F language=en-GB \
-F name="English Style Guide" \
-F contentGroupId="6a428641ba54ab39ae56da64" \
-F file=@./style-guide.md
```
Response:
```json theme={null}
{ "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }
```
### 2. Poll the create job
```bash theme={null}
curl "https://eu.phrase.com/styleguide/api/v1/styleguides/async/create-jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer $JWT"
```
Keep polling until `status` is `COMPLETED`. The `result` field contains the created Style Guide.
```json theme={null}
{
"jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "COMPLETED",
"result": {
"id": "018e1234-5678-7890-abcd-ef1234567890",
"name": "English Style Guide",
"language": { "bcpCode": "en-GB", "description": "English (United Kingdom)", "language": "en" },
"contentGroup": { "id": "6a428641ba54ab39ae56da64" }
}
}
```
### 3. List extracted rules
After the job completes, list the rules that were extracted from the Style Guide. Filter by `styleGuideIds` to
retrieve only rules from this guide.
```bash theme={null}
curl -X POST "https://eu.phrase.com/styleguide/api/v1/rules/search" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"styleGuideIds": ["018e1234-5678-7890-abcd-ef1234567890"],
"pageNumber": 0,
"pageSize": 20
}'
```
Each rule in the response includes its extracted text, scope, and a `styleGuide` reference pointing back to the source:
```json theme={null}
{
"content": [
{
"id": "01900000-0000-7000-8000-000000000001",
"rule": "Avoid passive voice in all user-facing content.",
"active": true,
"aiCheckEnabled": true,
"allContentGroups": false,
"allLanguages": false,
"contentGroups": [{ "id": "6a428641ba54ab39ae56da64" }],
"languages": [{ "bcpCode": "en-GB", "description": "English (United Kingdom)", "language": "en" }],
"styleGuide": { "id": "018e1234-5678-7890-abcd-ef1234567890" }
}
],
"totalElements": 12,
"totalPages": 1,
"pageNumber": 0,
"pageSize": 20,
"numberOfElements": 12
}
```
### 4. Edit a rule
Use `PATCH /api/v1/rules/{id}` to update a rule. Only fields present (non-null) in the request body are applied —
omit a field to leave it unchanged.
Editing a rule's text or scope **unlinks it from its source Style Guide**. The `styleGuide` field becomes `null`
and the rule becomes standalone. This does not affect the Style Guide itself or its other extracted rules.
Because this is permanent, the API will not perform an unlinking update unless the request explicitly sets
`forceUnlink: true`. Without it, the request fails with `409 Conflict`
(`RULE_UNLINK_REQUIRES_CONFIRMATION`) and none of the requested changes are applied.
```bash theme={null}
curl -X PATCH "https://eu.phrase.com/styleguide/api/v1/rules/01900000-0000-7000-8000-000000000001" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"rule": "Use active voice in all user-facing content."
}'
```
Since this rule is linked to a Style Guide, the request above fails:
```json theme={null}
{
"code": "RULE_UNLINK_REQUIRES_CONFIRMATION",
"message": "This update would unlink the rule from its Style Guide. Set forceUnlink=true to confirm.",
"detail": "Updating rule 01900000-0000-7000-8000-000000000001 would unlink it from its Style Guide"
}
```
Retry with `forceUnlink: true` to confirm the unlink and apply the change:
```bash theme={null}
curl -X PATCH "https://eu.phrase.com/styleguide/api/v1/rules/01900000-0000-7000-8000-000000000001" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"rule": "Use active voice in all user-facing content.",
"forceUnlink": true
}'
```
Updates that do not change the rule text, content group scope, or language scope at all (for example, only
toggling `active` or `aiCheckEnabled`, or resubmitting the same scope the rule already has) never unlink the rule,
so `forceUnlink` is not required for them.
Rule changes take effect immediately for any running jobs in the affected Content Group. Make rule edits when no
jobs for that Content Group are active, and verify your rules before starting new jobs.
## Creating a manual rule
```bash theme={null}
curl -X POST "https://eu.phrase.com/styleguide/api/v1/rules" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"rule": "Use the Oxford comma in all list items.",
"allContentGroups": false,
"contentGroupIds": ["6a428641ba54ab39ae56da64", "6a2eb0d1ed7a69bbb1b53d06"],
"allLanguages": false,
"languages": ["en-GB", "en-US"],
"active": true,
"aiCheckEnabled": true
}'
```
To create a rule that applies everywhere, set both `allContentGroups` and `allLanguages` to `true` and omit
`contentGroupIds` and `languages`:
```bash theme={null}
curl -X POST "https://eu.phrase.com/styleguide/api/v1/rules" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"rule": "Do not use exclamation marks in formal content.",
"allContentGroups": true,
"allLanguages": true
}'
```
## Active-rule and AI-check limits
Each (Content Group, Language) pair may have at most 50 active rules and 20 active rules with AI check enabled. See
[Limits](/en/api/style-guides/concepts#limits) for the full definition. If you explicitly request `active: true`
(or `aiCheckEnabled: true`) and the affected pair is already at its limit, the request is rejected and no rule is
created or changed:
```bash theme={null}
curl -X POST "https://eu.phrase.com/styleguide/api/v1/rules" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"rule": "Use the Oxford comma in all list items.",
"allContentGroups": false,
"contentGroupIds": ["6a428641ba54ab39ae56da64"],
"allLanguages": false,
"languages": ["en-GB"],
"active": true
}'
```
```json theme={null}
{
"code": "ACTIVE_RULE_LIMIT_REACHED",
"message": "The active rule limit (50) has been reached for this Content Group and Language.",
"detail": "Content Group 6a428641ba54ab39ae56da64, Language en-GB already has 50 active rules"
}
```
`aiCheckEnabled: true` requests fail the same way with `AI_CHECK_LIMIT_REACHED` once a pair already has 20
active, AI-check-enabled rules. Requesting `aiCheckEnabled: true` without the rule being (or becoming) active is
rejected separately as an invalid request, before the AI-check limit is evaluated.
If you omit `active` or `aiCheckEnabled` instead of setting them explicitly, the API never returns a limit error
for that flag — it sets the flag to `true` only if there's room, and to `false` otherwise:
```bash theme={null}
curl -X POST "https://eu.phrase.com/styleguide/api/v1/rules" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"rule": "Use the Oxford comma in all list items.",
"allContentGroups": false,
"contentGroupIds": ["6a428641ba54ab39ae56da64"],
"allLanguages": false,
"languages": ["en-GB"]
}'
```
## Filtering rules
The `POST /api/v1/rules/search` endpoint supports several filters that can be combined:
| Filter | Description |
| ----------------- | ----------------------------------------------------- |
| `contentGroupIds` | Return rules scoped to any of these Content Group IDs |
| `languages` | Return rules scoped to any of these locale codes |
| `styleGuideIds` | Return only rules extracted from these Style Guides |
| `active` | `true` / `false` — filter by active status |
| `aiCheckEnabled` | `true` / `false` — filter by AI check flag |
| `ruleText` | Case-insensitive substring match against rule text |
| `lastModifiedBy` | Filter by user account IDs |
Example — find all active rules for the `marketing` Content Group in English:
```bash theme={null}
curl -X POST "https://eu.phrase.com/styleguide/api/v1/rules/search" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"contentGroupIds": ["6a428641ba54ab39ae56da64"],
"languages": ["en-GB"],
"active": true,
"pageNumber": 0,
"pageSize": 50
}'
```
## Disabling a rule for AI checks
Set `aiCheckEnabled: false` to keep a rule in the Style Guide record without using it for AI-powered Quality checks.
This is useful for rules that are aspirational or under review.
```bash theme={null}
curl -X PATCH "https://eu.phrase.com/styleguide/api/v1/rules/01900000-0000-7000-8000-000000000001" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{ "aiCheckEnabled": false }'
```
To re-enable, patch with `"aiCheckEnabled": true`.
## Deleting a rule
`DELETE /api/v1/rules/{id}` permanently removes a rule. This does not affect the source Style Guide or any other
extracted rules.
```bash theme={null}
curl -X DELETE "https://eu.phrase.com/styleguide/api/v1/rules/01900000-0000-7000-8000-000000000001" \
-H "Authorization: Bearer $JWT"
```
A successful deletion returns `204 No Content`.
# Create a Style Guide
Source: https://developers.phrase.com/en/api/style-guides/style-guide/create-a-style-guide
/openapi/phrase-style-guides.json post /api/v1/styleguides
**Deprecated** — use `POST /api/v2/styleguides` for new integrations. Creates a Style Guide from a Markdown file. Returns 202 with a jobId; poll the **Get create job status** API for the final result.
# Create a Style Guide (v2)
Source: https://developers.phrase.com/en/api/style-guides/style-guide/create-a-style-guide-v2
/openapi/phrase-style-guides.json post /api/v2/styleguides
Creates a Style Guide from a Markdown file with a Content Group. Returns 202 with a jobId; poll the **Get create job status** API for the final result.
# Get a Style Guide
Source: https://developers.phrase.com/en/api/style-guides/style-guide/get-a-style-guide
/openapi/phrase-style-guides.json get /api/v1/styleguides/{id}
Returns a Style Guide.
# Update a Style Guide
Source: https://developers.phrase.com/en/api/style-guides/style-guide/update-a-style-guide
/openapi/phrase-style-guides.json put /api/v1/styleguides/{id}
**Deprecated** — use `PUT /api/v2/styleguides/{id}` for new integrations. Updates a Style Guide. When a new file is uploaded, returns 202 with a jobId; poll the **Get update job status** API for the final result. Metadata-only updates return 200 with the updated Style Guide. Content-Type can be omitted for metadata-only updates; use multipart/form-data when uploading a file.
# Update a Style Guide (v2)
Source: https://developers.phrase.com/en/api/style-guides/style-guide/update-a-style-guide-v2
/openapi/phrase-style-guides.json put /api/v2/styleguides/{id}
Updates a Style Guide with a Content Group. When a new file is uploaded, returns 202 with a jobId; poll the **Get update job status** API for the final result. Metadata-only updates return 200 with the updated Style Guide. Content-Type can be omitted for metadata-only updates; use multipart/form-data when uploading a file.
# Create a Rule
Source: https://developers.phrase.com/en/api/style-guides/rule/create-a-rule
/openapi/phrase-style-guides.json post /api/v1/rules
Creates a Rule for the authenticated organization. When omitted, active and aiCheckEnabled each default to true, but only if doing so would not exceed the active-rule limit (50) or AI-check limit (20) for every (Content Group, Language) pair the rule applies to; otherwise they default to false. Explicit true values that would exceed a limit are rejected with 409 CONFLICT.
# Delete a Rule
Source: https://developers.phrase.com/en/api/style-guides/rule/delete-a-rule
/openapi/phrase-style-guides.json delete /api/v1/rules/{id}
Permanently deletes a Rule.
# Get a Rule
Source: https://developers.phrase.com/en/api/style-guides/rule/get-a-rule
/openapi/phrase-style-guides.json get /api/v1/rules/{id}
Returns a Rule by ID.
# List Rules
Source: https://developers.phrase.com/en/api/style-guides/rule/list-rules
/openapi/phrase-style-guides.json post /api/v1/rules/search
Returns a paginated, sorted list of Rules. Defaults to creation date descending.
# Update a Rule
Source: https://developers.phrase.com/en/api/style-guides/rule/update-a-rule
/openapi/phrase-style-guides.json patch /api/v1/rules/{id}
Updates a Rule. Only fields present (non-null) in the request body are applied. Editing the text, or removing the linked Style Guide's content group or language, would unlink the rule from its Style Guide; set forceUnlink=true to confirm, otherwise the request fails with 409 CONFLICT and no changes are applied.
# Delete Style Guides
Source: https://developers.phrase.com/en/api/style-guides/style-guide/delete-style-guides
/openapi/phrase-style-guides.json delete /api/v1/styleguides
Deletes the listed Style Guides and all of their versions. Partial success is possible; the response reports the deleted count and per-item failures.
# Get create job status
Source: https://developers.phrase.com/en/api/style-guides/style-guide/get-create-job-status
/openapi/phrase-style-guides.json get /api/v1/styleguides/async/create-jobs/{jobId}
Returns the current state of a Style Guide create job. HTTP status is always 200; switch on the body's status field.
# Get update job status
Source: https://developers.phrase.com/en/api/style-guides/style-guide/get-update-job-status
/openapi/phrase-style-guides.json get /api/v1/styleguides/async/update-jobs/{jobId}
Returns the current state of a Style Guide update job. HTTP status is always 200; switch on the body's status field.
# List Style Guides
Source: https://developers.phrase.com/en/api/style-guides/style-guide/list-style-guides
/openapi/phrase-style-guides.json post /api/v1/styleguides/search
Returns a paginated list of Style Guides. Filter by name, language, last-modified range, or last-modified user; sort by name, language, or last-modified date.
# Changelog
Source: https://developers.phrase.com/en/changelog
Stay up to date with the latest changes to Phrase APIs, SDKs, and developer tools.
For full product release notes, see the [Phrase Help Center](https://support.phrase.com/hc/en-us/categories/4930539748124-Release-notes).
## Service Accounts: Machine-to-Machine authentication
Server-to-server integrations can now authenticate without a human user in the loop. Create a Service Account in **Organization Settings → Service Accounts** to get a `client_id`/`client_secret` pair and exchange them directly for an access token via the standard OAuth 2.0 Client Credentials flow — no API token or token-exchange step needed.
* Each Service Account provisions a bot user scoped to one or more Phrase products.
* Bot users can read/write content, run analyses, and work with translation memories, but can't create or modify human users, or be assigned as project owners. They don't count toward seat quotas.
See the [Platform authentication guide](/en/api/platform/authentication#machine-to-machine-authentication-service-accounts).
## Content Groups API: SYSTEM content group
Every organization now has one platform-managed **SYSTEM** content group ("All Groups"), representing "applies to all content groups." `Group` objects now carry a `groupType` field (`SYSTEM` or `USER`), and references now carry a `referenceType` field (`SYSTEM` or `USER`).
* Use the literal string `SYSTEM` as an alias for the group id on `GET /groups/{groupId}` and `GET /groups/{groupId}/references`. It is not accepted on `PUT`/`DELETE /groups/{groupId}` — the SYSTEM group is immutable and those calls return `403`.
* `listGroups` and `listGroupReferences` accept a new `includeSystem` query parameter (default `true`) to include/exclude the SYSTEM group and its references.
* `PLATFORM_STYLE_GUIDE`, `TMS_PROJECT`, and the newly added `TMS_PROJECT_TEMPLATE` object type are never linked to the SYSTEM group; only `PLATFORM_QA_CHECK` and `PLATFORM_STYLE_RULE` references can appear under it.
See the [Content Groups API reference](/en/api/control-hub/introduction#the-system-content-group).
## Style Guide API: active-rule and AI-check limits per Content Group and Language
Each (Content Group, Language) pair now allows at most **50 active rules** and **20 active rules with AI check
enabled**. Rules are counted whether linked directly to a Content Group/Language or via
`allContentGroups`/`allLanguages`.
* Explicitly requesting `active: true` or `aiCheckEnabled: true` when a pair is already at its limit is rejected
with `409 Conflict` (`ACTIVE_RULE_LIMIT_REACHED` / `AI_CHECK_LIMIT_REACHED`).
* When these flags are omitted, the API now decides them automatically based on remaining room, instead of
always defaulting to `true`.
* Style Guide rule extraction respects the same limits: extracted rules beyond the remaining room are saved but
left inactive, without affecting rules already active for that Content Group and Language.
See [Limits](/en/api/style-guides/concepts#limits) in the Style Guide API reference.
## Studio API: AI model value `quokka` is now `aita`
The AI model name enums for the [Update project settings](/en/api/studio/projects/update-project-settings) endpoint (`chatModelName`, `insightsModelName`, and `translationModelName`) previously exposed the internal codename `quokka`. The AI Translation Agent is now selected with the value `aita` — a meaningful external name in line with the other options (`plai`, `gpt-5-mini`, `gpt-4o-mini`).
* Use `aita` when setting one of these fields to the AI Translation Agent.
* The old `quokka` value is still accepted for backward compatibility, but is no longer documented and may be removed in a future release.
See the [Studio API reference](/en/api/studio/introduction).
## Content Groups API now available
The [Content Groups API](/en/api/control-hub/introduction) is now documented in the Developer Hub.
Use it to manage **content groups** — organization-scoped collections that link platform objects such as projects, style guides, style rules, and AI quality checks together for coordinated workflows.
## Strings API: job automations can target multiple projects
Job automations previously accepted `project_ids` as an array but used only the first entry. You can now associate a job automation with **multiple projects** in a single call. Multi-project automations require the `advanced_job_automation` plan feature — accounts without it receive a `422` with `project_ids` in the error field.
See the [Strings API reference](/en/api/strings/introduction).
## Studio API: clarified subtitle updates — `words` is the source of truth
The Update/Get Subtitle reference now spells out that a segment's `words` array (word-level text and timings) is authoritative for both subtitle rendering and dubbing text-to-speech. The `text` field is only a human-readable mirror and is ignored when rendering or synthesizing.
* To change what a segment says (for example, shortening a dubbing line before a redub), edit its `words` — not just `text`.
* Never submit a segment with an empty `words` array: it is silently dropped the next time the track is read, and produces no dubbing audio. This is the usual cause of a track that "comes back empty" after an update.
* The endpoint is a bulk replace: always send the full segment list, then list changed ids in `segmentsToRedub` to regenerate their audio.
See the [Studio API reference](/en/api/studio/introduction).
## Quality Evaluator API: v1 CRUD and v2 endpoints deprecated
All v1 AI Check CRUD endpoints (`GET/POST /v1/aiChecks`, `GET/PUT/DELETE /v1/aiChecks/{uid}`), v1 Quality Profile CRUD endpoints, `PUT /v2/aiChecks/{uid}`, and `POST /v2/evaluation` are now flagged as **deprecated** in the OpenAPI spec. Migrate to the v3 Content Group–based endpoints:
* `POST /v3/evaluation` for evaluating segments.
* `GET /v3/qualityProfiles/{contentGroupId}` for resolving a Quality Profile from a Content Group.
The deprecated endpoints continue to work for now. See the [Quality Evaluator API reference](/en/api/quality-evaluator/introduction).
## Strings API: link and unlink keys with content strategies
The Key Links endpoints now cover the full lifecycle of shared translations between a parent key and its children:
* **Link child keys** — designate a parent and attach one or more children so their translations derive from the parent.
* **List child keys** — retrieve the full link record for a parent, including all attached children.
* **Unlink a single child** — detach one child; its translations inherit the parent's current content and are marked unverified for reviewer confirmation.
* **Batch unlink** — detach several children in one request, or dissolve the entire group with `unlink_parent: true`.
* **Content strategy on unlink** — choose `keep_content` (default; copies the parent's translations into each detached child) or `remove_content` (clears them).
Key Links are available on main projects only. See the [Strings API reference](/en/api/strings/introduction).
## Strings API: document list search is now a prefix match
The `q` parameter on **List documents** now performs a case-insensitive **prefix** match on the document name instead of a substring match. For example, `q=invoice` returns documents whose names *start* with "invoice", not those that merely contain it. If you relied on the previous substring behavior, adjust your queries accordingly.
The **Delete document**, **Get screenshot**, and **Delete screenshot** endpoints also received clearer descriptions and error contracts. See the [Strings API reference](/en/api/strings/introduction).
## Strings API: assign a Language AI profile per locale
Locales now accept an optional `language_ai_profile` identifier when you create or update a locale. The selected profile drives Language AI behavior for that locale, and the assigned profile is returned on the locale resource.
See the [Strings API reference](/en/api/strings/introduction).
## Strings API: prefix-based locale downloads
Locale download requests now accept two related options in the request body:
* **`translation_key_prefix`** — strip the given prefix from key names in the exported file.
* **`filter_by_prefix`** — when `true`, only export keys that match `translation_key_prefix`, and remove the prefix from the output.
Use these to export a subset of keys with cleaner names. Note: stripping a prefix can produce duplicate key names if other keys collide once the prefix is removed.
See the [Strings API reference](/en/api/strings/introduction).
## Connectors API: full reference for all seven connectors
The Connectors API reference now documents every supported connector end-to-end, not just Contentful. Available connectors:
* **Files** — sync localizable files from arbitrary file sources.
* **GitHub** — connect repositories to TMS projects.
* **Braze** — pull content blocks and email templates for translation.
* **Contentful** — sync entries and assets.
* **Google Drive** — translate documents stored in Drive.
* **Tridion Docs** — exchange content with Tridion Docs.
* **Optimizely** — localize Optimizely content.
Each connector exposes the same request/response contract, so you can build custom flows against a consistent interface. See the [Connectors API reference](/en/api/connectors/introduction).
## Strings API: review due dates on jobs
Job responses now include a `review_due_date` field so you can see when a job's review stage is due. The field returns `null` when the project does not have the review workflow enabled.
See the [Strings API reference](/en/api/strings/introduction).
## Strings API: branch comparison response schema
The branch comparison endpoint now returns a fully described response. For each resource type (translation keys, translations, locales, tags), the response lists changes made on the base branch, changes made on the feature branch, and any conflicts — making it easier to surface branch diffs and resolve merges programmatically.
See the [Strings API reference](/en/api/strings/introduction).
## Quality Evaluator API v3: Content Group–based evaluation
Quality Evaluator now resolves AI Checks and Quality Profiles dynamically from Content Groups, so a single call applies the right checks for the target content:
* **Evaluate against a Content Group** — `POST /v3/evaluation` evaluates segments using the AI Checks resolved from the supplied `contentGroupId`. When no checks are associated, segments are returned with an empty `results` array instead of an error.
* **Resolve a Quality Profile** — `GET /v3/qualityProfiles/{contentGroupId}` returns the Quality Profile resolved for a Content Group.
* **Restrict AI Checks to specific locales** — `PUT /v2/aiChecks/{uid}` accepts an optional `locales` field so a check can be limited to certain target locales. Omit the field to preserve existing restrictions, or send an empty array to clear them.
The previous AI Check CRUD endpoints (`/v1/aiChecks*`), Quality Profile CRUD endpoints (`/v1/qualityProfiles*`), and `POST /v2/evaluation` are now deprecated. Migrate to the v3 Content Group–based endpoints. See the [Quality Evaluator API introduction](/en/api/quality-evaluator/introduction).
## Strings API: name your repo syncs
Repo syncs now support an optional `name` field (up to 100 characters) so you can give each sync a custom display name. When the name is null or blank, the sync continues to display using its associated project name. Set `name` when creating or updating a repo sync to make repository connections easier to identify in lists and dashboards.
See the [Strings API reference](/en/api/strings/introduction).
## Studio API: update projects and manage recording subtitles
Three new endpoints expand what you can do with the Studio API:
* **Update project settings** — `PATCH /v1/projects/{id}` partially updates AI model preferences, TTS provider, translation memory / MT profile assignment, and sharing visibility. Only the fields you send are changed.
* **Get recording subtitles** — `GET /v1/projects/{projectId}/recordings/{recordingId}/subtitles` returns the segmented subtitle for a recording.
* **Update recording subtitles** — `PUT /v1/projects/{projectId}/recordings/{recordingId}/subtitles` writes subtitle segments back to a recording.
See the [Studio API reference](/en/api/studio/introduction).
## TMS API: link projects to content groups
Three new TMS endpoints let you manage a project's content group association:
* `GET /api2/v1/projects/{projectUid}/contentGroup` — get the content group linked to a project.
* `POST /api2/v1/projects/{projectUid}/contentGroup` — link a project to a content group.
* `DELETE /api2/v1/projects/{projectUid}/contentGroup` — unlink a project from its content group.
See the [TMS API reference](/en/api/tms/latest/introduction).
## TMS: quality evaluation moved under projects, plus segment warnings storage
The endpoint that triggers AI quality evaluation has moved to a project-scoped path:
* **New:** `POST /api2/v1/projects/{projectUid}/jobs/evaluateQuality`
* **Replaces:** `POST /api2/v1/qualityProfiles/evaluate`
A new `POST /api2/v1/qualityProfiles/segmentWarnings` endpoint also lets you store evaluation warnings for a job part. Update any integrations that called the previous endpoint. See the [TMS API reference](/en/api/tms/latest/introduction).
## Strings API: larger upload size for unmentioned-key reporting
When you upload translations, the response field that counts keys not mentioned in the upload is now calculated for uploads up to **100,000 keys** (previously 10,000). Larger uploads still report `0`.
See the [Strings API reference](/en/api/strings/introduction).
## SCIM endpoints moved to the Platform API
SCIM user provisioning is now part of the Phrase Platform API at `/scim/...` (for example, `GET /scim/Users`, `POST /scim/Users`, `GET /scim/ServiceProviderConfig`). The previous `/api2/v1/scim/...` paths under TMS have been removed.
Point your SCIM identity provider at the Platform endpoints. See the [Platform API reference](/en/api/platform/introduction).
## Strings API: fall back to another locale for unverified translations
Locale download endpoints accept a new optional query parameter, `fallback_for_unverified_translations`. When `true`, translations in a non-final state are replaced by the fallback locale's translation at export time:
* In the **simple** workflow, "non-final" means `unverified`.
* In the **review** workflow, it also includes `translated` (awaiting review).
No stored translations are modified. Requires `fallback_locale_id` or `use_locale_fallback` to be set. See the [Strings API reference](/en/api/strings/introduction).
## Strings API: Identify how jobs were created
Job responses in the Strings API now include two new fields so you can see where a job came from:
* **`automation_id`** — the ID of the automation that created the job, or `null` if it was created manually
* **`job_template_id`** — the ID of the job template the job was created from, or `null` if no template was used
See the [Strings API reference](/en/api/strings/introduction) for the updated schema.
## MCP Server v0.10.0: Strings Repo Sync
The Phrase MCP Server `v0.10.0` adds Repo Sync tools for Strings, so you can manage repository synchronization between Strings projects and Git repositories directly from your MCP client.
See the [MCP Server guide](/en/guides/mcp) for full details.
## TMS: Quality estimation warnings and translation memory alignment
Two new TMS API endpoints are available:
* **Quality estimation warnings** — `POST /api2/v1/qualityProfiles/qeWarnings` returns AI-generated QE warnings for selected job parts, paginated by segment.
* **Translation memory alignment** — `POST /api2/v2/transMemories/{transMemoryUid}/align` aligns supplied source and target files using the TM's source locale and returns an aligned XLSX file.
See the [TMS API reference](/en/api/tms/latest/introduction) for details.
## Quality evaluation: lock and confirm passing segments
The TMS endpoint that triggers AI quality evaluation accepts two new optional parameters:
* `lockSegments` (default `true`) — locks segments that pass all AI checks.
* `confirmSegments` (default `false`) — confirms segments that pass all AI checks.
Use these to automate post-evaluation segment status changes. See the [TMS API reference](/en/api/tms/latest/introduction).
## Style Guides API
The Style Guide API is now public. Manage organization-wide writing guidelines through the API: create and update Markdown style guides per language, version every revision, search and filter, and bulk-delete.
See the [Style Guides API Reference](/en/api/style-guides/introduction) to get started.
## MCP Server v0.9.0: Analysis and Quotes for TMS
The Phrase MCP Server `v0.9.0` now supports the TMS Analysis and Quotes APIs. New tools let you create and manage analyses and quotes directly from your MCP client.
See the [MCP Server guide](/en/guides/mcp) for full details.
## MCP Server v0.8.0: Strings screenshots
The Phrase MCP Server `v0.8.0` adds screenshot support for Strings with new `create_screenshot` and `create_screenshot_marker` tools, so you can attach visual context to keys directly from your MCP client.
See the [MCP Server guide](/en/guides/mcp) for full details.
## MCP Server v0.7.0: Google Drive connector, Quality Evaluator, and base64 uploads
The Phrase MCP Server `v0.7.0` adds several new capabilities:
* **Google Drive connector** — manage Google Drive connector resources from the MCP Server
* **Quality Evaluator** — evaluate translation quality through new Quality Evaluator tools
* **Base64 file uploads** — file upload tools now accept base64-encoded `file_content` as an alternative to `file_path`
See the [MCP Server guide](/en/guides/mcp) for full details.
## Quality Evaluator API
The new Quality Evaluator API enables automated, AI-powered assessment of translation quality. Define quality requirements in natural language, group them into reusable profiles, and evaluate translation segments at scale.
* **AI Checks** — create custom checks from natural language quality requirements
* **Quality Profiles** — group AI Checks into reusable profiles
* **Analytics** — track evaluation metrics and AI unit consumption over time
See the [Quality Evaluator API introduction](/en/api/quality-evaluator/introduction) to get started.
## MCP Server: Termbase and Translation Memory management
The Phrase MCP Server now supports managing Termbases and Translation Memories in TMS. New tools are available for:
* **Termbases** — list, get details, search, import, and export
* **Translation Memories** — list, get details, search, import, and export
These tools are available as part of the TMS product in the MCP Server. See the [MCP Server guide](/en/guides/mcp) for full details.
## MCP Server package renamed
The Phrase MCP Server npm package has been renamed and is now published under the `@phrase` organization.
**Before:**
```json theme={null}
"args": ["-y", "phrase-mcp-server"]
```
**After:**
```json theme={null}
"args": ["-y", "@phrase/phrase-mcp-server"]
```
Update your MCP client configuration to use the new package name. The old `phrase-mcp-server` package is no longer maintained.
See the [MCP Server quick start](/en/guides/mcp) for full configuration examples.
# Introduction
Source: https://developers.phrase.com/en/developer-tools/introduction
Tools and libraries to integrate with Phrase APIs
Phrase provides a collection of developer tools to help you integrate with our APIs efficiently. These tools simplify common tasks like pushing and pulling translation files, and provide access to our APIs in your preferred programming language.
## Available tools
A command-line tool for macOS, Linux, and Windows that provides direct access to the Phrase Strings API. Push and pull translation files, manage keys, and automate your localization workflow.
Official API client libraries for Ruby, Python, PHP, TypeScript, Java, and Go. Integrate Phrase Strings directly into your applications.
A GitHub Action that installs the Phrase CLI in your workflow, enabling automated push and pull of translation files in your CI/CD pipeline.
# Phrase Strings API Clients
Source: https://developers.phrase.com/en/developer-tools/strings-api-clients
These clients are for **Phrase Strings** only.
## phrase-ruby
`phrase-ruby` is a Ruby gem that provides interaction with the API. It provides a client for accessing Phrase programmatically within applications.
## phrase-python
`phrase-python` is a library for the API written in Python.
## phrase-php
`phrase-php` is a library for the API written in PHP.
## phrase-js
`phrase-js` is a library for the API written in TypeScript.
## phrase-java
`phrase-java` is a library for the API written in Java.
## phrase-go
`phrase-go` is a library for the Phrase API written in Golang.
# Phrase Strings CLI
Source: https://developers.phrase.com/en/developer-tools/strings-cli
This tool is for **Phrase Strings** only.
The Phrase Strings CLI is a self-contained binary for macOS, Linux, and Windows. It provides command-line access to the full Phrase Strings API and makes it easy to sync locale files between your project and Phrase.
## Installation
```bash Homebrew (macOS) theme={null}
brew install phrase-cli
```
```bash asdf theme={null}
asdf plugin add phrase
asdf install phrase latest # or a specific version
asdf set phrase latest
```
```bash Docker theme={null}
docker run --rm phrase/phrase-cli:latest help
```
For a direct binary, download the archive for your platform from the [phrase-cli releases page](https://github.com/phrase/phrase-cli/releases/latest):
```bash theme={null}
curl -L https://github.com/phrase/phrase-cli/releases/latest/download/phrase_linux_amd64.tar.gz | tar xz
mv phrase /usr/local/bin/phrase
phrase version
```
Download the `.zip` archive from the [releases page](https://github.com/phrase/phrase-cli/releases/latest), extract it, and add the `phrase.exe` binary to a directory on your `PATH`. Verify with `phrase version` from the command prompt.
```bash theme={null}
go install github.com/phrase/phrase-cli@latest
```
Requires Go 1.21+. The binary is placed in `$GOPATH/bin`.
## Authentication
For details on token types and how to generate them, see [Authentication](/en/api/platform/authentication).
The CLI looks for the token in this order:
1. `--access_token` flag
2. `PHRASE_ACCESS_TOKEN` environment variable (recommended)
3. `phrase.access_token` in `.phrase.yml` (discouraged)
Avoid putting the token in `.phrase.yml`, especially if the file is committed to a repository:
```bash theme={null}
export PHRASE_ACCESS_TOKEN=""
```
You can also pass it directly as a flag:
```bash theme={null}
phrase push --access_token $PHRASE_ACCESS_TOKEN
```
## Quick start
### 1. Initialize your project
**Speed up setup with our AI agent skill**
The `phrase-strings-config` skill follows the [Agent Skills](https://agentskills.io/home) open format and works with any compatible AI coding agent. It can detect your project's i18n setup and generate `.phrase.yml` automatically. Install it from the [Phrase skills repository](https://github.com/phrase/skills).
Run `phrase init` in your project root. The interactive wizard asks for your token, project ID, file format, and locale file path, then creates a `.phrase.yml` config file.
```bash theme={null}
phrase init
```
If your account is on the **US datacenter**, pass the host flag:
```bash theme={null}
phrase init --host https://api.us.app.phrase.com/v2
```
You can also skip the wizard entirely with flags:
```bash theme={null}
phrase init \
--access_token $PHRASE_ACCESS_TOKEN \
--project_id YOUR_PROJECT_ID \
--file_format yml \
--path 'config/locales/*.yml'
```
### 2. Upload locale files
```bash theme={null}
phrase push
```
Use `--wait` to block until processing completes (recommended in CI):
```bash theme={null}
phrase push --wait
```
### 3. Download locale files
```bash theme={null}
phrase pull
```
## Configuration
`phrase init` creates a `.phrase.yml` file in your project root. You can place it in the current working directory, your home directory, or point to it with the `--config` flag or the `PHRASEAPP_CONFIG` environment variable.
### Global settings
| Key | Required | Description |
| ----------------------- | -------- | ----------------------------------------------------------------------------------------------------- |
| `phrase.access_token` | No | Personal access token. Prefer `PHRASE_ACCESS_TOKEN` env var to avoid committing credentials. |
| `phrase.project_id` | Yes | Public project ID from project settings → API tab. |
| `phrase.file_format` | Yes | Default locale file format (API extension, e.g. `yml`, `json`, `strings`). |
| `phrase.host` | No | Override API host, e.g. `https://api.us.app.phrase.com/v2` for US datacenter. |
| `phrase.locale_mapping` | No | Maps Phrase locale names to custom locale names, which can be used in file or directory placeholders. |
### Framework examples
```yaml theme={null}
phrase:
access_token: ACCESS_TOKEN
project_id: PROJECT_ID
file_format: yml
push:
sources:
- file: ./config/locales/*.yml
params:
update_translations: true
pull:
targets:
- file: ./config/locales/.yml
```
```yaml theme={null}
phrase:
access_token: ACCESS_TOKEN
project_id: PROJECT_ID
file_format: strings
push:
sources:
- file: ./.lproj/Localizable.strings
pull:
targets:
- file: ./.lproj/Localizable.strings
- file: ./.lproj/Localizable.stringsdict
params:
file_format: stringsdict
```
Android uses non-standard locale directory names. Use `locale_mapping` to map Phrase locale names to Android directory names:
```yaml theme={null}
phrase:
access_token: ACCESS_TOKEN
project_id: PROJECT_ID
file_format: xml
locale_mapping:
en-US: values
de-DE: values-de-rDE
fr-FR: values-fr
push:
sources:
- file: ./app/src/main/res/values/strings.xml
params:
locale_id: en-US
pull:
targets:
- file: ./app/src/main/res//strings.xml
```
```yaml theme={null}
phrase:
access_token: ACCESS_TOKEN
project_id: PROJECT_ID
push:
sources:
- file: apps/web/src/assets/i18n/.json
params:
file_format: nested_json
- file: apps/android/app/src/main/res/values-/strings.xml
params:
file_format: xml
- file: apps/ios/.lproj/Localizable.strings
params:
file_format: strings
pull:
targets:
- file: apps/web/src/assets/i18n/.json
params:
file_format: nested_json
- file: apps/android/app/src/main/res/values-/strings.xml
params:
file_format: xml
- file: apps/ios/.lproj/Localizable.strings
params:
file_format: strings
```
### Full annotated example
```yaml theme={null}
phrase:
project_id: PROJECT_ID
file_format: FORMAT_API_EXTENSION
push:
sources:
- file: ./path/to/file/file.format
params:
file_format: FORMAT_API_EXTENSION
locale_id: LOCALE_ID
tags: TAG_1, TAG_2
update_translations: false
update_descriptions: false
skip_upload_tags: false
skip_unverification: false
file_encoding: ENCODING
autotranslate: false
mark_reviewed: false
format_options: # format-specific — see Help Center for available options per format
pull:
targets:
- file: ./path/to//file.format
params:
file_format: FORMAT_API_EXTENSION
locale_id: LOCALE_ID
tags: TAG_1, TAG_2
include_empty_translations: false
exclude_empty_zero_forms: false
include_translated_keys: true
keep_notranslate_tags: false
encoding: ENCODING
include_unverified_translations: true
use_last_reviewed_version: false
fallback_locale_id: LOCALE_ID
format_options: # format-specific — see Help Center for available options per format
```
### Placeholders and globbing
Use these placeholders in `file` paths:
| Placeholder | Description |
| --------------- | -------------------------------------------------------------------------- |
| `` | The unique locale name (e.g. `en`, `de-AT`). Preferred for `pull` targets. |
| `` | RFC 5646 locale identifier. Not guaranteed unique across locales. |
| `` | Groups keys by tag — useful for maintaining original file structures. |
Globbing operators work in `push.sources` paths:
| Operator | Behavior |
| -------- | ---------------------------------------------------- |
| `*` | Matches any characters within a single path segment. |
| `**` | Matches across directory boundaries (recursive). |
```
./config/locales/**/*.yml # matches ./config/locales/en.yml and ./config/locales/api/en.yml
```
**Note:** globbing is not supported in `pull.targets` — use explicit paths with placeholders instead.
### Push parameters
Key parameters for `push.sources[].params`:
| Parameter | Default | Description |
| ------------------------- | ------- | ------------------------------------------------------------------------------------------ |
| `locale_id` | — | Locale name (e.g. `en-US`) or public locale ID. |
| `update_translations` | `false` | Overwrite existing translations with file content. |
| `update_descriptions` | `false` | Update key descriptions; empty descriptions overwrite existing. |
| `skip_upload_tags` | `false` | Skip creating upload tags. |
| `skip_unverification` | `false` | Do not unverify updated translations. |
| `tags` | — | Comma-separated tags to apply to new keys. |
| `file_format` | — | Override the global file format for this source. |
| `file_encoding` | — | Enforce encoding: `UTF-8`, `UTF-16`, `UTF-16BE`, `UTF-16LE`, `ISO-8859-1`. |
| `autotranslate` | `false` | Auto-fetch translations for the uploaded locale. |
| `mark_reviewed` | `false` | Mark imported translations as reviewed (requires review workflow). |
| `update_translation_keys` | `true` | Set to `false` to prevent new keys from being created or existing keys from being updated. |
| `translation_key_prefix` | — | Prefix prepended to all key names on push. Use `` as a magic placeholder. |
### Pull parameters
Key parameters for `pull.targets[].params`:
| Parameter | Default | Description |
| --------------------------------- | ------- | --------------------------------------------------------------------- |
| `locale_id` | — | Locale name or public locale ID. |
| `tags` | — | Comma-separated tags to filter which keys to pull. |
| `include_empty_translations` | `false` | Include keys with no translations. |
| `include_unverified_translations` | `true` | Set to `false` to exclude unverified translations. |
| `use_last_reviewed_version` | `false` | Use the last reviewed translation version (requires review workflow). |
| `fallback_locale_id` | — | Fallback locale for missing translations. |
| `exclude_empty_zero_forms` | `false` | Exclude zero-form plurals when empty. |
| `keep_notranslate_tags` | `false` | Preserve `[NOTRANSLATE]` tags in output. |
| `encoding` | — | Enforce encoding on the output file. |
| `file_format` | — | Override the global file format for this target. |
| `translation_key_prefix` | — | Strip this prefix from key names on pull. |
| `filter_by_prefix` | `false` | Only pull keys that match the prefix. |
### Format options
Some file formats support additional `format_options` in the `params` section:
```yaml theme={null}
push:
sources:
- file: file.csv
params:
format_options:
column_separator: ";"
pull:
targets:
- file: file.xml
params:
format_options:
convert_placeholder: true
```
For the full list of supported `format_options` per file format, refer to the [Help Center format articles](https://help.phrase.com/help/supported-platforms-and-formats).
## Core commands
| Command | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `phrase init` | Interactive setup — creates `.phrase.yml`. |
| `phrase push` | Upload locale files to Phrase. |
| `phrase push --wait` | Upload and wait for processing to complete. |
| `phrase pull` | Download locale files from Phrase. |
| `phrase locales list` | List all locales in the project. |
| `phrase uploads cleanup --id ` | Delete keys in the project that were not present in the uploaded file. Run after `phrase push` to remove stale keys. The upload ID is returned by the push command. |
| `phrase --help` | Show all options for any command. |
| `phrase` | List all available commands. |
## Docker
Mount your project directory so the CLI can read `.phrase.yml` and write locale files:
```bash theme={null}
docker run --volume $(pwd):/code --workdir /code --rm phrase/phrase-cli:latest pull
```
For interactive commands like `init`, add the `-it` flag:
```bash theme={null}
docker run -it --volume $(pwd):/code --workdir /code phrase/phrase-cli:latest init
```
## Advanced
**Monorepos:** Place one `.phrase.yml` in each package and run the CLI from the corresponding folder, or use the `--config` flag to point CI jobs to different config files.
**Rate limiting:** When the locale download rate limit is reached, the CLI automatically waits and resumes. You'll see: `rate limit exceeded, download will resume in x seconds`.
**Proxy:** Set the `HTTPS_PROXY` environment variable:
```bash theme={null}
export HTTPS_PROXY=https://user:password@host:port
```
**Windows shell escaping:** When passing JSON on the command line, use double quotes and escape inner quotes with `\`:
```
phrase locales create --project_id PROJECT_ID --data "{\"name\":\"French\", \"code\":\"fr\"}" --access_token TOKEN
```
## Git integration
When using the CLI with a Git provider (GitHub, GitLab, Bitbucket), ensure the following prerequisites are met in addition to having `.phrase.yml` committed to the repository:
* A GitHub access token scoped to the repository (`public_repo` for public repositories).
* If SSO is enabled in GitHub, it must also be enabled for the access token.
* The `phrase_translations` branch must not be protected.
* The repository must not require signed commits.
* Read and write access to the repository are required.
* Read and write access to the repository are required.
* GitLab 9.5 or newer is required for API compatibility.
* Ensure `.phrase.yml` contains at least one push source and one pull target with the correct file formats.
* Read and write access to the repository are required.
* Ensure `.phrase.yml` contains at least one push source and one pull target with the correct file formats.
## GitHub Actions
To automate pushes and pulls in CI, use the [Phrase Strings GitHub Action](/en/developer-tools/strings-github-action), which installs the CLI and exposes it to your workflow steps.
# Phrase Strings GitHub Action
Source: https://developers.phrase.com/en/developer-tools/strings-github-action
This tool is for **Phrase Strings** only.
The Phrase Strings GitHub Action installs the Phrase CLI in your GitHub Actions workflow, enabling you to automate pushing and pulling translation files as part of your CI/CD pipeline.
## Usage
Add the action to your workflow to install the Phrase CLI, then use it to sync your translation files:
```yaml theme={null}
steps:
- uses: actions/checkout@v4
- uses: phrase/setup-cli@v1
with:
version: 2.19.0
- run: phrase pull
- run: phrase push --wait
env:
PHRASE_ACCESS_TOKEN: ${{ secrets.PHRASE_ACCESS_TOKEN }}
```
## Configuration
The action requires a `.phrase.yml` configuration file in your repository. Authentication is handled via the `PHRASE_ACCESS_TOKEN` environment variable, which should be stored as a GitHub Actions secret.
# Error Handling and Limits
Source: https://developers.phrase.com/en/guides/build-a-tms-plugin/error-handling-and-limits
Handle HTTP failures, retries, and Phrase TMS limits safely in plugin integrations.
Use this page as your operational reference for runtime failures and platform constraints.
## HTTP error codes
| Error code | Meaning | Recommended handling |
| ---------- | ------------------------------------- | ---------------------------------------------------------------------------- |
| `400` | Invalid request payload or parameters | Validate payload shape, required fields, and enum values before retrying. |
| `401` | Authentication failed | Refresh access token, retry once, then surface actionable auth guidance. |
| `403` | Authenticated but unauthorized | Check role/permissions in Phrase TMS and account scope. |
| `404` | Referenced resource not found | Verify IDs (`projectUid`, `jobUid`, template IDs) and endpoint path version. |
| `429` | Rate limit reached | Back off with jitter; reduce poll frequency and burst size. |
| `500` | Internal service error | Retry with bounded backoff; capture request context for support escalation. |
| `503` | Temporary service unavailability | Retry with bounded backoff; defer non-critical jobs when possible. |
## Rate and async limits
Critical constraints to design for:
* Phrase TMS documents an API limit of **6,000 requests per minute for logged-in users**.
* API request limits require bounded polling and backoff.
* Async workflows can queue under load; avoid aggressive status polling.
* Webhooks reduce polling load and should be your primary production signal.
Use exponential backoff with jitter for retries on `429` and `5xx` responses. Always treat support-doc limits as the source of truth for current values.
Reference:
* [TMS limits (support)](https://support.phrase.com/hc/en-us/articles/5784117234972-Phrase-TMS-Limits)
* [Handling API rate limits](/en/api/tms/latest/handling-api-rate-limits)
* [Handling concurrent API limits](/en/api/tms/latest/handling-concurrent-api-limits)
## File handling limits
Plan for file import constraints early:
* Validate file type and size before upload.
* Split very large submissions into smaller batches.
* Fail fast with actionable feedback when files are unsupported.
Reference:
* [File import settings and limits](https://support.phrase.com/hc/en-us/sections/5709618056604-File-Import-Settings)
## Reliability checklist
* Implement retry classification: retry only transient errors.
* Make create/export operations idempotent.
* Track correlation IDs across inbound and outbound calls.
* Alert on repeated failure classes and webhook processing lag.
* Add fallback polling if webhook events are missed.
## Next steps
Apply these safeguards to the full production lifecycle.
Build a baseline flow, then harden using this guide.
# Full Integration Workflow
Source: https://developers.phrase.com/en/guides/build-a-tms-plugin/full-integration-workflow
Build a production-ready Phrase TMS plugin with resilient import, monitoring, export, and operations.
This guide extends Quick Start with production behavior and optional capabilities.
## At a glance
Recommended implementation order:
1. Authentication and token refresh
2. Import (project/job creation)
3. Monitoring (webhook-first)
4. Export and `DELIVERED` status
5. Optional extensions and hardening
## Prerequisites
* Completed [Quick Start](/en/guides/build-a-tms-plugin/quick-start).
* Phrase TMS project template(s) aligned to your workflow.
* Platform and TMS API base URLs for your tenant/region.
* Secure storage for API token and runtime secrets.
* A callback/webhook endpoint if you use push-based progress handling.
If you only need a baseline production flow, implement sections 1-4 first. Sections 5+ are optional hardening and scaling improvements.
## 1) Authentication strategy
* Base flow: `POST ${PLATFORM_BASE_URL}/idm/oauth/token` (token exchange).
* Include `Authorization: Bearer ` on API requests.
* Refresh on expiry and on a single retry after `401`.
For production, cache token expiry and centralize token refresh to avoid refresh storms under load.
Reference:
* [Platform authentication](/en/api/platform/authentication)
* [OAuth token endpoint](/en/api/platform/oauth/token-endpoint)
## 2) Import content into TMS
### Required path
1. List templates: `GET /api2/v1/projectTemplates`
2. Create project: `POST /api2/v2/projects/applyTemplate/{templateUid}`
3. Create jobs: `POST /api2/v1/projects/{projectUid}/jobs`
Persist `templateUid`, `projectUid`, `job.uid`, and relevant `asyncRequest.id` values.
Need payload examples while implementing? Jump to Optional reference: payloads and endpoint map.
### Live preview integration point (optional)
If you use live preview, integrate it in this import phase:
1. Upload a preview package after project creation.
2. Store the returned preview package file UID.
3. Include `jobPreviewPackageFileUidRef` when creating the job.
See [Live Preview](/en/guides/build-a-tms-plugin/live-preview) for implementation details and failure handling.
### Optional path: expose content listing/download endpoints
If your integration uses TMS pull behavior from the third-party system:
* Expose a **list endpoint** to return selectable content.
* Expose a **download endpoint** to return encoded content for import.
* Validate auth and return clear HTTP errors for invalid requests.
Keep endpoint responses deterministic. Stable IDs and filenames reduce duplicate imports and debugging complexity.
Reference:
* [List project templates](/en/api/tms/latest/project-template/list-project-templates)
* [Create project from template](/en/api/tms/latest/project/create-project-from-template)
* [Create job](/en/api/tms/latest/job/create-job)
### Optional workflow extensions
If your lifecycle requires vendor automation and PM workflow support, add these operations after job creation:
* Assign providers from template: `POST /api2/v1/projects/{projectUid}/applyTemplate/{templateUid}/assignProviders`
* Create analyses by providers: `POST /api2/v1/analyses/byProviders`
* Notify assigned users: `POST /api2/v1/projects/{projectUid}/jobs/notifyAssigned`
Reference:
* [Assign providers from template](/en/api/tms/latest/project/assigns-providers-from-template)
* [Create analyses by providers](/en/api/tms/latest/analysis/create-analyses-by-providers)
* [Notify assigned users](/en/api/tms/latest/job/notify-assigned-users)
## 3) Monitor job progress
Use this priority order:
1. **Webhooks (recommended for production)**
2. **Async request status polling**
3. **Job-part polling fallback**
### Webhooks
* Configure webhook subscriptions for job/part/async lifecycle events.
* Verify sender token/header.
* Store events idempotently, acknowledge quickly, and process asynchronously.
### Async status
* Poll `GET /api2/v1/async/{asyncRequestId}` for completion when the workflow uses async endpoints.
### Callback pattern (alternative to polling)
Many async endpoints support `callbackUrl`.
* Callback payload includes async request metadata and action result.
* Callback endpoint should validate sender, persist event, and return `200` quickly.
* If callback URL is unreachable, Phrase retries after 2, 4, 8, 16, and 30 minutes (up to 10 failed retries).
* Callback delivery is considered successful only when your endpoint returns HTTP `200`.
Callback handler pseudocode:
```text theme={null}
POST /callbacks/phrase-async
verifySignatureOrToken(request)
event = parseJson(request.body)
saveEventIdempotently(event.asyncRequest.id)
enqueueAsyncProcessing(event)
return 200
```
### Job-part status fallback
* Poll `GET /api2/v1/projects/{projectUid}/jobs/{jobUid}/parts` with backoff and jitter.
Reference:
* [Get asynchronous request](/en/api/tms/latest/async-request/get-asynchronous-request)
* Job parts endpoint: `GET /api2/v1/projects/{projectUid}/jobs/{jobUid}/parts`
* [Webhooks (support article)](https://support.phrase.com/hc/en-us/articles/5709693398812-Webhooks-TMS)
## Job status reference
Use this table to map statuses to plugin behavior.
| Status | Typical phase | Meaning | Plugin action |
| --------------- | ------------------------- | ------------------------------------------------- | -------------------------------------- |
| `NEW` | Creation / pre-assignment | Job exists and is not yet accepted | Keep monitoring and surface as queued |
| `ACCEPTED` | Assignment | Provider accepted the job | Continue monitoring workflow progress |
| `DECLINED` | Assignment | Provider declined the job | Reassign provider or notify operator |
| `COMPLETED` | Completion | Translation workflow is complete | Start export flow |
| `DELIVERED` | Post-export | Target content was exported/imported successfully | Treat as terminal success |
| `CANCELLED` | Exception | Work was stopped before completion | Treat as terminal failure |
| `REJECTED` | Exception / QA | Output was rejected and needs rework | Route for rework, do not deliver |
| `JOB_NOT_READY` | Import exception | Job creation/import did not complete correctly | Inspect import errors and retry safely |
## 4) Export translated content
### Pull from TMS via API
1. Start async export: `PUT /api2/v3/projects/{projectUid}/jobs/{jobUid}/targetFile`
2. Wait for completion via webhook/callback/polling.
3. Download file once ready: `GET /api2/v2/projects/{projectUid}/jobs/{jobUid}/downloadTargetFile/{asyncRequestId}`
4. Import result into third-party system.
5. Set terminal status: `POST /api2/v1/projects/{projectUid}/jobs/{jobUid}/setStatus` with `DELIVERED`.
### Push from TMS to your plugin (optional)
Expose a secure endpoint to receive translated payloads and run your import mapping there.
`asyncRequestId` for download is single-use. Persist and consume it carefully.
Reference:
* [Download target file async](/en/api/tms/latest/job/download-target-file-async-1)
* [Download target file by async request](/en/api/tms/latest/job/download-target-file-based-on-async-request)
* [Edit job status](/en/api/tms/latest/job/edit-job-status)
## 5) Continuous updates (optional, high-change workflows)
For continuous localization, update source content on existing jobs and re-run the workflow:
* Use `POST /api2/v1/projects/{projectUid}/jobs/source`
* Monitor completion using callbacks, webhooks, or async polling.
* Re-export translated output when updates finish.
Reference:
* [Update source](/en/api/tms/latest/job/update-source)
## 6) Metadata and reporting
Send plugin metadata in the Memsource header payload when creating jobs:
```json theme={null}
{
"sourceData": {
"clientType": "MY_PLUGIN",
"clientVersion": "1.2.4",
"hostVersion": "4.5.2"
}
}
```
* `clientType` is required and must be registered with Phrase.
* `clientVersion` and `hostVersion` are optional.
## 7) Idempotency implementation guidance
Implement idempotency for all create and delivery operations:
| Operation | Recommended idempotency key |
| --------------------------- | --------------------------------------------- |
| Create project | External content batch ID + template ID |
| Upload/create job | Source content checksum + locale + project ID |
| Start export | Job ID + source revision |
| Download/import target file | Async request ID + target system item ID |
| Update status (`DELIVERED`) | Job ID + delivered revision |
Pattern:
1. Build deterministic operation key before API call.
2. Check/store key in durable storage with operation state.
3. If duplicate key appears, return previously stored result instead of replaying.
## 8) Logging field specification
Log these fields for every API call and lifecycle event:
| Field | Why it matters |
| --------------------- | ------------------------------------------- |
| `timestamp` | Event ordering and latency analysis |
| `requestId` | Tie plugin logs to HTTP calls |
| `correlationId` | Trace a single localization flow end-to-end |
| `projectUid` | Project-level troubleshooting |
| `jobUid` | Job-level troubleshooting |
| `asyncRequestId` | Async lifecycle correlation |
| `pluginInstanceId` | Multi-instance debugging |
| `endpoint` + `method` | API behavior visibility |
| `statusCode` | Error and retry analytics |
| `durationMs` | Performance and SLO measurement |
## Plugin variants
| Capability | Canonical plugin | Live content plugin | Continuous localization plugin |
| ------------------------- | ---------------- | ------------------- | ------------------------------ |
| Token exchange + refresh | Must | Must | Must |
| Project from template | Must | Must | Must |
| Webhooks | Should | Must | Must |
| Live preview | Can | Must | Can |
| Idempotent source updates | Should | Must | Must |
| Advanced observability | Should | Must | Must |
## Operational hardening checklist
* Retry transient errors (`429`, `5xx`) with exponential backoff + jitter.
* Avoid duplicate create/export actions with idempotency keys or dedupe logic.
* Log correlation IDs, project/job IDs, endpoint, status, and latency.
* Track success rates and error rates for create/monitor/export operations.
* Add alerts for repeated failures and webhook delivery issues.
## Testing expectations
* Happy path: create → monitor → export → `DELIVERED`.
* Failure path: auth errors, validation errors, rate limits, service errors.
* Resilience path: webhook miss + polling fallback.
* Update path (if enabled): source updates and repeated export.
## Optional reference: payloads and endpoint map
### Core payload snippets
Create project from template request:
```json theme={null}
{
"name": "Website Translation",
"sourceLang": "en",
"targetLangs": ["de", "fr"]
}
```
Create project from template response (excerpt):
```json theme={null}
{
"uid": "proj-uid-1",
"name": "Website Translation",
"status": "NEW",
"sourceLang": "en",
"targetLangs": ["de", "fr"]
}
```
Create job response (excerpt):
```json theme={null}
{
"jobs": [
{
"uid": "job-uid-1",
"status": "NEW",
"targetLang": "de"
}
],
"asyncRequest": {
"id": "async-req-1",
"action": "PRE_ANALYSE"
}
}
```
Set job status request:
```json theme={null}
{
"requestedStatus": "DELIVERED",
"notifyOwner": true,
"propagateStatus": true
}
```
### Endpoint reference glossary
| Action | Method | Endpoint |
| ----------------------------------------- | ------ | ---------------------------------------------------------------------------------- |
| Exchange API token for access token | `POST` | `${PLATFORM_BASE_URL}/idm/oauth/token` |
| List project templates | `GET` | `/api2/v1/projectTemplates` |
| Create project from template | `POST` | `/api2/v2/projects/applyTemplate/{templateUid}` |
| Create job | `POST` | `/api2/v1/projects/{projectUid}/jobs` |
| Assign providers from template | `POST` | `/api2/v1/projects/{projectUid}/applyTemplate/{templateUid}/assignProviders` |
| Create analyses by providers | `POST` | `/api2/v1/analyses/byProviders` |
| Notify assigned users | `POST` | `/api2/v1/projects/{projectUid}/jobs/notifyAssigned` |
| Get async request | `GET` | `/api2/v1/async/{asyncRequestId}` |
| List job parts | `GET` | `/api2/v1/projects/{projectUid}/jobs/{jobUid}/parts` |
| Start async target-file export | `PUT` | `/api2/v3/projects/{projectUid}/jobs/{jobUid}/targetFile` |
| Download target file by async request | `GET` | `/api2/v2/projects/{projectUid}/jobs/{jobUid}/downloadTargetFile/{asyncRequestId}` |
| Set job status | `POST` | `/api2/v1/projects/{projectUid}/jobs/{jobUid}/setStatus` |
| Update source for continuous localization | `POST` | `/api2/v1/projects/{projectUid}/jobs/source` |
For complete request/response schemas, use the linked endpoint pages and the [TMS API reference](/en/api/tms/latest/introduction).
## Additional resources
* Postman collection for rapid API validation and manual flow testing.
* [TMS API reference](/en/api/tms/latest/introduction) for full payload schemas.
* [Webhooks support guide](https://support.phrase.com/hc/en-us/articles/5709693398812-Webhooks-TMS) for account-level event setup and retries.
## Next steps
Add context-rich in-editor preview behavior.
Confirm failure handling and limit-safe behavior.
Review endpoint-level request and response details.
# Live Preview
Source: https://developers.phrase.com/en/guides/build-a-tms-plugin/live-preview
Add live preview support so translators can see localized content in context.
Live preview helps translators understand context by rendering a visual representation of content in the editor.
## When to use live preview
Use live preview when text meaning depends on layout, styling, or surrounding content.
Typical cases:
* CMS pages with rich formatting.
* Product or marketing pages with embedded UI copy.
* Content with many short strings where context is critical.
## Prerequisites
* A working plugin flow for project/job creation.
* Your tenant-specific TMS API base URL (for example `${TMS_BASE_URL}`).
* Access to source assets needed for preview rendering.
* A strategy for keeping preview assets aligned with source content.
```bash theme={null}
TMS_BASE_URL="https:///web"
```
## Option A: HTML file localization
If your source can be represented as HTML, upload HTML directly as the translatable file.
Pros:
* Simplest setup.
* Immediate visual context.
Limitations:
* Not always feasible for CMS models with complex field structures.
* May require custom conversion logic from source model to HTML.
## Option B: Preview package via API
If HTML-as-source is not feasible, upload a preview package and reference it during job creation.
1. Build a ZIP package with one HTML entry point plus relative assets.
2. Upload package: `POST /api2/v1/projects/{projectUid}/jobPreviewPackage`
3. Use the returned preview file UID in job creation metadata (`jobPreviewPackageFileUidRef` in Memsource header).
```bash theme={null}
curl --request POST "${TMS_BASE_URL}/api2/v1/projects/${PROJECT_UID}/jobPreviewPackage" \
--header "Authorization: Bearer ${ACCESS_TOKEN}" \
--form "file=@preview-package.zip"
```
Reference:
* [Upload job preview package](/en/api/tms/latest/project/upload-job-preview-package)
* [Create job](/en/api/tms/latest/job/create-job)
## Failure modes and safeguards
* Malformed package structure: validate ZIP before upload.
* Missing assets or incorrect relative paths: run package integrity checks in CI.
* Oversized package: enforce package size limits during build.
* Preview mismatch after source updates: re-generate preview package when content schema changes.
Keep preview generation deterministic and versioned. It makes regression debugging much faster.
## Next steps
Return to the production workflow and integrate preview where needed.
Validate limits and failure handling for preview flows.
# Introduction
Source: https://developers.phrase.com/en/guides/build-a-tms-plugin/overview
Understand what a Phrase TMS plugin is, what you will build, and where to start.
This guide is for Phrase TMS plugins. It does not cover Phrase Strings, Language AI, or other Phrase products.
A TMS plugin is software that lives in a third-party system and connects that system to Phrase TMS. It sends source content for localization, monitors translation progress, and retrieves completed translations back into the source system.
New to this flow? Start with Quick Start, then return here for the reference sections.
## What you'll build
A production plugin usually follows this lifecycle:
1. Select source content in the third-party system.
2. Authenticate with Phrase Platform and call TMS APIs.
3. Create a project from a project template.
4. Create one or more jobs by uploading source content.
5. Monitor job progress (webhooks, async status, or job-part polling).
6. Export translated files when jobs are complete.
7. Set job status (for example `DELIVERED`) after successful import.
## Choose your path
Build a working baseline plugin flow in four steps.
Add production behavior, monitoring, and hardening.
Handle failures, retries, limits, and operational safeguards.
## Supported file types
Common file types used in plugin integrations:
| File type | Typical use |
| --------------- | ------------------------------------- |
| XLIFF 1.2 / 2.0 | Standard localization exchange format |
| JSON | Structured application or CMS content |
| XML | Structured content and metadata |
| Markdown | Documentation and content workflows |
| Plain text | Simple non-structured content |
For import-specific parsing behavior, see [file import settings](https://support.phrase.com/hc/en-us/sections/5709618056604-File-Import-Settings).
## Key concepts
| Term | Definition |
| ------------------- | ----------------------------------------------------------------------------------------- |
| Project template | Reusable configuration used to create projects with standard settings and workflow steps. |
| Project | Container for related translation jobs. |
| Job | A translation unit for one file and one target language. |
| Workflow | Ordered translation/review steps that jobs move through. |
| `asyncRequest` | Identifier for asynchronous API operations that complete later. |
| Locale | Source or target language/region identifier used in localization. |
| Machine translation | Automated translation used in workflow steps, often combined with human review. |
| Markup language | Structured formats (for example HTML/XML) where text and tags must be handled safely. |
| Segmentation | Splitting content into translatable segments used for progress and QA operations. |
| Translation memory | Reusable translation database used to improve consistency and speed. |
## Core objects and data models (minimum)
Use these core object shapes in your plugin data layer.
| Object | Purpose | Key fields to persist |
| ---------------- | -------------------------------------- | ---------------------------------------------------------- |
| Project Template | Defines default project configuration | `uid`, `templateName`, `sourceLang`, `targetLangs[]` |
| Project | Container for jobs | `uid`, `name`, `status`, `sourceLang`, `targetLangs[]` |
| Job | Translation unit for a target language | `uid`, `status`, `targetLang`, `workflowLevel`, `filename` |
| Async Request | Tracks asynchronous work | `id`, `action`, `dateCreated` |
| Async Response | Completion payload for async work | `errorCode`, `errorDesc`, `warnings[]` |
Representative JSON shapes:
```json theme={null}
{
"projectTemplate": {
"uid": "pt-uid-1",
"templateName": "Website Default",
"sourceLang": "en",
"targetLangs": ["de", "fr"]
},
"project": {
"uid": "proj-uid-1",
"name": "Website Translation",
"status": "NEW"
},
"job": {
"uid": "job-uid-1",
"status": "NEW",
"targetLang": "de",
"workflowLevel": 1
},
"asyncRequest": {
"id": "async-req-1",
"action": "PRE_ANALYSE"
},
"asyncResponse": {
"errorCode": null,
"errorDesc": null
}
}
```
## Next steps
Build the minimum end-to-end flow first.
Expand to a production-ready integration.
Add editor preview capabilities for translators.
# Quick Start
Source: https://developers.phrase.com/en/guides/build-a-tms-plugin/quick-start
Build your first Phrase TMS plugin flow in four practical steps.
This quick start gives you the minimum end-to-end plugin flow: authenticate, create project and jobs, monitor progress, and download translated output.
## At a glance
You will implement this sequence:
1. Authenticate
2. Create project and job
3. Monitor progress
4. Export and set `DELIVERED`
## Prerequisites
* Phrase account with TMS access.
* Phrase Platform API token.
* Platform base URL and TMS API base URL for your tenant/region.
* At least one TMS project template available in your org.
* A sample source file to upload.
* Basic REST API familiarity.
Set base URLs once and reuse them in examples:
```bash theme={null}
PLATFORM_BASE_URL="https://.phrase.com"
TMS_BASE_URL="https:///web"
```
Do not hardcode EU endpoints unless your tenant is on EU infrastructure.
## Step 1: Authenticate
Exchange your API token for a short-lived access token and send it as `Authorization: Bearer `.
```bash theme={null}
curl --request POST "${PLATFORM_BASE_URL}/idm/oauth/token" \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'subject_token=YOUR_API_TOKEN'
```
Use this token exchange flow for all subsequent API calls. Refresh before expiry or on a single retry after `401`.
See [Platform authentication](/en/api/platform/authentication) and [OAuth token endpoint](/en/api/platform/oauth/token-endpoint).
## Step 2: Create a project and job
1. List project templates: `GET /api2/v1/projectTemplates`
2. Create project from template: `POST /api2/v2/projects/applyTemplate/{templateUid}`
3. Create job in project: `POST /api2/v1/projects/{projectUid}/jobs`
```bash theme={null}
curl --request POST "${TMS_BASE_URL}/api2/v2/projects/applyTemplate/${TEMPLATE_UID}" \
--header "Authorization: Bearer ${ACCESS_TOKEN}" \
--header 'Content-Type: application/json' \
--data '{"name":"My Plugin Quick Start Project"}'
```
Persist `projectUid`, `job.uid`, and any returned `asyncRequest.id`. You need these IDs later.
Reference docs:
* [List project templates](/en/api/tms/latest/project-template/list-project-templates)
* [Create project from template](/en/api/tms/latest/project/create-project-from-template)
* [Create job](/en/api/tms/latest/job/create-job)
## Step 3: Monitor progress
Use one of these methods:
* Simple start: poll `GET /api2/v1/projects/{projectUid}/jobs/{jobUid}/parts`
* Async operation status: poll `GET /api2/v1/async/{asyncRequestId}` when relevant
* Production approach: webhook events with polling fallback
```bash theme={null}
curl --request GET "${TMS_BASE_URL}/api2/v1/projects/${PROJECT_UID}/jobs/${JOB_UID}/parts" \
--header "Authorization: Bearer ${ACCESS_TOKEN}"
```
Treat terminal failure statuses (`CANCELLED`, `REJECTED`) as non-deliverable outcomes.
## Step 4: Export and mark delivered
1. Start async download: `PUT /api2/v3/projects/{projectUid}/jobs/{jobUid}/targetFile`
2. Wait until async request is complete.
3. Download target file: `GET /api2/v2/projects/{projectUid}/jobs/{jobUid}/downloadTargetFile/{asyncRequestId}`
4. Mark delivered: `POST /api2/v1/projects/{projectUid}/jobs/{jobUid}/setStatus` with `requestedStatus: DELIVERED`
```json theme={null}
{
"requestedStatus": "DELIVERED",
"notifyOwner": true,
"propagateStatus": true
}
```
Reference docs:
* [Download target file async](/en/api/tms/latest/job/download-target-file-async-1)
* [Download target file by async request](/en/api/tms/latest/job/download-target-file-based-on-async-request)
* [Edit job status](/en/api/tms/latest/job/edit-job-status)
## Optional: implementation patterns (pseudocode)
Use these patterns when moving from sample calls to production code. You can skip this section for a first pass.
### Auth caching and one-time `401` retry
```text theme={null}
CACHE = { token: null, expiresAt: 0 }
function getAccessToken():
if CACHE.token exists and now < CACHE.expiresAt - safetyWindow:
return CACHE.token
tokenResponse = POST ${PLATFORM_BASE_URL}/idm/oauth/token
CACHE.token = tokenResponse.access_token
CACHE.expiresAt = now + tokenResponse.expires_in
return CACHE.token
function authRequest(method, url, body):
token = getAccessToken()
response = HTTP(method, url, header={"Authorization": "Bearer " + token}, body=body)
if response.status == 401:
CACHE.token = null
token = getAccessToken()
response = HTTP(method, url, header={"Authorization": "Bearer " + token}, body=body)
return response
```
### Async polling with bounded backoff
```text theme={null}
function waitForAsync(asyncRequestId, timeout):
delay = 2s
until timeout:
status = GET /api2/v1/async/{asyncRequestId}
if status.asyncResponse exists:
if status.asyncResponse.errorCode exists:
fail(status.asyncResponse)
return status.asyncResponse
sleep(delay + jitter)
delay = min(delay * 2, 60s)
```
### Export completion flow
```text theme={null}
start = PUT /api2/v3/projects/{projectUid}/jobs/{jobUid}/targetFile
waitForAsync(start.asyncRequest.id)
file = GET /api2/v2/projects/{projectUid}/jobs/{jobUid}/downloadTargetFile/{start.asyncRequest.id}
importToSourceSystem(file)
POST /api2/v1/projects/{projectUid}/jobs/{jobUid}/setStatus {"requestedStatus":"DELIVERED"}
```
## Next steps
Add production-grade behaviors and resilience.
Add contextual preview support for translators.
Harden retries, limits, and failure behavior.
# Translate multiple segments asynchronously
Source: https://developers.phrase.com/en/guides/byo-engine/asynchronous-translation/translate-multiple-segments-asynchronously
/openapi/phrase-byo-mt.json post /translateAsync
# Translate multiple segments asynchronously - get translated segments
Source: https://developers.phrase.com/en/guides/byo-engine/asynchronous-translation/translate-multiple-segments-asynchronously--get-translated-segments
/openapi/phrase-byo-mt.json get /translateAsyncResult/{id}
# Translate multiple segments asynchronously - status polling
Source: https://developers.phrase.com/en/guides/byo-engine/asynchronous-translation/translate-multiple-segments-asynchronously--status-polling
/openapi/phrase-byo-mt.json get /translateAsyncStatus/{id}
# Get engine status
Source: https://developers.phrase.com/en/guides/byo-engine/engine-status/get-engine-status
/openapi/phrase-byo-mt.json post /status
# Introduction
Source: https://developers.phrase.com/en/guides/byo-engine/introduction
## Phrase BYO Engine API
Machine translation powered by [Phrase](https://phrase.com/) allows the translation of content using a unique AI powered feature to pick the best available translation engine for that content.
By implementing the following API it is possible to integrate custom machine translation engine directly into [Phrase Language AI](https://support.phrase.com/hc/en-us/articles/5709660879516-Phrase-Language-AI-TMS).
A minimal BYO Engine machine translation API specification.
It supports the following operations and features:
* [supported languages](/en/guides/byo-engine/supported-languages/get-supported-language-pairs)
* [engine status](/en/guides/byo-engine/engine-status/get-engine-status)
* multi-segment translation operations
* [synchronous translation](/en/guides/byo-engine/synchronous-translation/translate-multiple-segments)
* [asynchronous translation](/en/guides/byo-engine/asynchronous-translation/translate-multiple-segments-asynchronously)
* [with glossaries](/en/guides/byo-engine/synchronous-translation/translate-multiple-segments#body-glossary)
* applies to both synchronous and asynchronous endpoints
* [with custom metadata](/en/guides/byo-engine/synchronous-translation/translate-multiple-segments#body-metadata)
* applies to both synchronous and asynchronous endpoints
## Dynamic Metadata
Phrase automatically resolves the following placeholders in request-level metadata values before sending them to the engine:
| Placeholder | Resolved value |
| ------------------------ | ----------------------- |
| `{project_uid}` | TMS project UID |
| `{job_uid}` | TMS job UID |
| `{idm_organization_uid}` | Phrase organization UID |
These can be used to pass contextual information about the translation job to your engine without any additional integration work.
## Authentication
For authentication either the [OAuth client credentials flow](https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-credentials-flow) or an API token can be leveraged.
## Demo Implementation
A reference implementation of the adapter is available [here](https://github.com/phrase/custom.adapter). It demonstrates a basic integration and includes additional guidance.
## Technical Notes
* Implement your adapter in accordance with the [OpenAPI schema](https://developers.phrase.com/public/assets/openapi/phrase-byo-mt.yaml).
* In general, but especially for the asynchronous endpoints, **ensure thread safety and proper concurrency handling** to prevent race conditions where concurrent requests could overwrite each other's data.
### Stability and Performance Recommendations
* **DNS Infrastructure & Routing:** Ensure DNS resolution is stable and all entries are correctly configured. Misconfigurations or propagation delays can result in intermittent resolution failures, elevated latency, or request timeouts.
* **Application & Proxy Server Tuning (e.g., Nginx):** Optimize your proxy or gateway servers for high-concurrency traffic. Review keep-alive settings and worker process limits to avoid TCP connection resets or dropped requests under load.
* **Load & Concurrency Testing:** Perform synthetic load tests from external endpoints before going to production. Validate your adapter against a sustained concurrency of 100–200 requests per second (RPS) to confirm that performance remains linear and stable.
***
# Get supported language pairs
Source: https://developers.phrase.com/en/guides/byo-engine/supported-languages/get-supported-language-pairs
/openapi/phrase-byo-mt.json post /languages
# Translate multiple segments
Source: https://developers.phrase.com/en/guides/byo-engine/synchronous-translation/translate-multiple-segments
/openapi/phrase-byo-mt.json post /translate
# Creating an APC
Source: https://developers.phrase.com/en/guides/managing-automated-project-creation/creating-an-apc
Every field POST /api2/v3/automatedProjects needs, where its options come from, and the order the TMS UI itself resolves them in.
Creating an APC setting has more required fields than a typical create call, and several of them have a real, discoverable set of valid options — don't ask for these blind, and don't guess a shape for the ones with no dedicated list endpoint either.
## 1. Confirm the connector
Every APC belongs to an existing connector. List your connectors (`GET /api2/v1/connectors`) and confirm the one to use before anything else — never guess or fabricate a connector identifier. See [Managing TMS Connectors](/en/guides/managing-connectors/overview) if you don't have one set up yet.
## 2. Pick a project template — before languages, not after
```bash theme={null}
GET /api2/v1/projectTemplates
```
Lists your available templates by name and `uid`. Resolve this **before** asking about languages: a project template's detail response carries the source/target languages it was created with.
```bash theme={null}
GET /api2/v1/projectTemplates/{projectTemplateUid}
```
`sourceLang` and `targetLangs` on this response are inherited from the project the template was originally created from — this is exactly why the TMS UI only shows you language options once you've picked a template. Present those as your language choices; only fall back to the full, unscoped `GET /api2/v1/languages` list if the chosen template happens to have no languages set.
The resolved target languages go into the create payload's `selectedTargetLangs` field (a required array of language codes) — the field name doesn't otherwise appear anywhere in this resolution step, so don't guess a different name (e.g. `targetLangs`) when building the request.
## 3. Browse the remote folder — don't ask for a path
```bash theme={null}
GET /api2/v1/connectors/{connectorId}/folders
GET /api2/v1/connectors/{connectorId}/folders/{encodedFolder}
```
Lists the connector's root folders, or a subfolder if you pass one. Browse and present the real folders/projects found — don't ask the user to type a path from memory, and don't guess a template like `/project/{id}` or `/space/...`.
For a `PHRASE`-type connector (TMS ↔ Phrase Strings), the browsable structure returned here is Strings projects/spaces rather than a literal filesystem path — present the real project/space names the endpoint returns. `PHRASE` is a real, valid connector type; if a browse call fails for a specific connector, that's an operation-specific failure, not evidence the connector or its type doesn't exist.
Once a folder is chosen, translate it into the create payload's `monitoredFolders[]` entry: `remoteFolder` (the folder path), `folderNames` (its path segments as a list), and `humanReadableFolderPath` — derive these from the browsed result, don't fabricate them from the folder name alone. `monitoredFolders[].localToken` is the connector's own `localToken` field (from its list/get response — not its `id`), which the backend needs to resolve which remote storage to read.
If a connector type doesn't support folder browsing at all, say so and let the user pick the folder in the TMS UI instead, then continue the rest of the flow normally.
## 4. Schedule (`frequency.frequencyOption`) or webhook trigger (`webhookToken`)
The schema requires **either** `frequency` **or** `webhookToken`, not both — ask the user which triggering model they want before assuming a schedule is the only option.
For a schedule, there's no list endpoint — it's a genuine question — but the valid `frequencyOption` values come straight from the create endpoint's schema, so offer them as named options rather than an open "how often?":
* `WEEKLY_FIXED_TIMES` — runs at specific times on specific days of the week
* `HOUR_TIME_RANGE` — runs every N hours (`range`)
* `MINUTE_TIME_RANGE` — runs every N minutes (`range`)
* `MONTHLY_FIXED_TIMES` — runs at specific times on specific days of the month
Each option has its own sub-fields (e.g. `weeklyFixedTimes[]`, `monthlyFixedTimes[]`) — check the full request schema for the shape once you know which one applies.
For a webhook-triggered setup instead, `webhookToken` is a plain string field — the automated project creation runs when that webhook is called, rather than on a schedule.
## 5. Translation-export rule
At least one `translationExports[]` entry is required. Again, no list endpoint, but the schema constrains the valid values:
* `exportFrom.type`: `FINAL_WORKFLOW_STEP` or `WORKFLOW_STEP` (with `workflowStep` number, if the latter)
* `exportWhen.exportTrigger`: `SELECTED_WORKFLOW_STEP_COMPLETED`, `FINAL_WORKFLOW_STEP_COMPLETED`, or `PROJECT_COMPLETED`
A reasonable default to suggest is exporting when the final workflow step completes — but present the actual options rather than assuming silently, and don't invent a webhook-based trigger; it isn't part of this schema.
## 6. Continuous vs. one-off projects
`continuousProject: true` imports files into a single, continuously-updated project instead of creating a new project on every run. This is a yes/no the user needs to decide — there's no discoverable default.
## Putting it together
Resolve every field above — connector, template (and its languages), folder, schedule, export rule — before presenting a single confirmation message with real values for all of them. Don't send the user a list of field names to fill in one at a time, and don't offer to fetch a list "if you'd like" — fetch it, then ask.
# Introduction
Source: https://developers.phrase.com/en/guides/managing-automated-project-creation/overview
Understand what Automated Project Creation (APC) is, how it relates to connectors, and the endpoints that manage it.
This guide is about **Automated Project Creation (APC)** — a TMS feature that watches a connector's remote storage and automatically creates translation projects when new files show up. It assumes you already have a working connector; see [Managing TMS Connectors](/en/guides/managing-connectors/overview) if you don't.
## What APC does
Automated Project Creation lets an already-configured connector do more than move files on request — it watches one or more folders (or, for connector types without a real filesystem, the equivalent browsable structure) on a schedule, and automatically creates a new translation project (or updates an existing continuous one) whenever it finds new or changed content.
APC always belongs to exactly one connector. There's no such thing as an APC setting that isn't tied to a connector — if you don't have one set up yet, create it first (see [Managing TMS Connectors](/en/guides/managing-connectors/overview)).
## Not the standalone Connectors API
The same product-boundary confusion that applies to connector lifecycle applies here too: the standalone [Connectors API](/en/api/connectors/introduction) only moves file content through an already-configured connector — it has no concept of automated project creation. APC lifecycle lives entirely on the TMS API.
## The APC lifecycle endpoints
| Method & path | Purpose |
| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `GET /api2/v1/automatedProjects` | List your APC settings (paginated — see below) |
| `POST /api2/v3/automatedProjects` | Create an APC setting |
| `GET /api2/v3/automatedProjects/{settingsId}` | View an APC setting's full configuration |
| `PUT /api2/v3/automatedProjects/{settingsId}` | Update an APC setting (full replace — see [Troubleshooting](/en/guides/managing-automated-project-creation/troubleshooting)) |
| `DELETE /api2/v1/automatedProjects/{settingsId}` | Delete one APC setting |
| `DELETE /api2/v1/automatedProjects/batch` | Delete several APC settings by id in one call |
| `GET /api2/v1/automatedProjects/{settingsId}/running` | Check whether it's currently running |
| `GET /api2/v1/automatedProjects/{settingsId}/status` | Check its last-run status |
| `GET .../monitoredFolder/{folder}/connectors/{connectorId}/folders/{folder}` | Browse an existing APC's monitored folder contents |
APC settings are organization-scoped — they're addressed directly by their own `settingsId`, never nested under a project. Don't construct a project-nested path for them.
`GET /api2/v1/automatedProjects` returns up to 50 results per call (`pageSize`, 0-indexed `pageNumber`). Check `totalElements`/`totalPages` on the first response and page through all of them before treating the result as complete — a large organization can easily have well over 50 APC settings.
## Choose your path
Walk through every field the create call needs — project template, target languages, monitored folder, schedule, and translation-export rule.
The full-replace PUT trap, listing gotchas, and per-connector-type quirks.
# Troubleshooting
Source: https://developers.phrase.com/en/guides/managing-automated-project-creation/troubleshooting
The full-replace PUT trap, listing gotchas, and per-connector-type quirks for Automated Project Creation.
## Enabling, disabling, or editing silently doesn't stick
There is no dedicated enable/disable or partial-update endpoint for APC. `GET .../running` and `GET .../status` are read-only state checks — they can't be used to change anything.
The **only** write path for any change, including just flipping `active`, is:
```bash theme={null}
PUT /api2/v3/automatedProjects/{settingsId}
```
This is a **full replace** — it requires `frequency`, `id`, `monitoredFolders`, and `name` in the body regardless of what you're actually changing.
Sending a body with only the field you're changing (e.g. just `{"active": true}`) can be accepted by the API without an error, but doesn't actually persist the change — a follow-up `GET` will show the setting reverted to its previous state. The response echoing back your intended value is **not** proof the change was saved.
To make any change safely:
1. `GET /api2/v3/automatedProjects/{settingsId}` first — **not** the `GET /api2/v1/automatedProjects` list result, which is a flatter shape (`connectorId`/`connectorName` instead of nested `monitoredFolders`) and is missing fields the `v3` `PUT` requires.
2. Take that full object, change only the field(s) you actually want to change, and send the **complete** object back.
3. `GET` the same `settingsId` again afterward and confirm the field actually changed before considering the update successful.
## Listing has no owner/creator field
Neither the list nor the single-get response for an APC setting exposes who created it. There's no way to filter "my APC settings" — either server-side or by post-processing the response, since the field simply isn't there. If you need to narrow a large list down, filter by connector, by active/inactive state, or by name instead.
## Listing is paginated
`GET /api2/v1/automatedProjects` returns at most 50 results per call (`pageSize`, default and max 50; `pageNumber`, 0-indexed). Check `totalElements`/`totalPages` on the first response and keep paging until you've collected everything — reporting the first page's contents as "the full list" is wrong the moment your organization has more than 50 APC settings.
## `PHRASE` connectors are a valid, real connector type
`PHRASE` (the connector that connects TMS to Phrase Strings) is a documented `ConnectorType` enum value, same as `BOX`, `GIT`, `CONTENTFUL`, `MARKETO`, or `OPTIMIZELY` — it's a legitimate choice for an APC's underlying connector. If browsing its folders behaves differently than a filesystem-backed connector (see [Creating an APC](/en/guides/managing-automated-project-creation/creating-an-apc)), that's a property of that connector type, not a sign it doesn't exist or isn't supported.
## Deletion
`DELETE /api2/v1/automatedProjects/{settingsId}` deletes one setting; `DELETE /api2/v1/automatedProjects/batch` deletes several by id in a single call. Both are permanent — confirm the exact setting(s) before calling either.
# OAuth Connector Setup
Source: https://developers.phrase.com/en/guides/managing-connectors/oauth-setup
Authorize and create OAuth-based TMS connectors (Google Drive, GitHub, Box, Salesforce, and others).
Many connector types authenticate via OAuth 2.0 rather than a plain username/password or API key. Setting one up requires walking the end user through a browser-based authorization step before the connector can be created.
## Which connector types use OAuth
These types require the authorization flow described below:
`GITHUB`, `GITHUB2`, `BITBUCKET`, `BOX`, `ONEDRIVE`, `SHAREPOINT`, `GOOGLE`, `GOOGLE_DRIVE2`, `SALESFORCE`, `VERBIS`, `HUBSPOT`, `ZENDESK`, `CONTENTFUL`, `CONTENTFULENTRYLEVEL`, `CONTENTFUL2`.
Some similarly-named types are **not** OAuth-based, and calling the authorization endpoint for them will fail: `GITLAB` and `BITBUCKETSERVER` use a plain host+token (personal access token), `MARKETO` uses an API key/secret pair, and `OPTIMIZELY`/`TRIDION` use OIDC client-credentials with no user redirect at all. `PHRASE` (the connector to Phrase Strings) also isn't OAuth, despite having a `code`-shaped field — see the note at the end of this page.
## The authorization flow
### 1. Get a one-time state token
```bash theme={null}
POST /api2/v1/connectors/connectorAuthData
```
This returns a one-time `state` token that correlates your authorization attempt with the eventual callback.
### 2. Get the authorization page URL
```bash theme={null}
GET /api2/v1/connectorAuthPage/{type}?hostPrefix={tmsHost}
```
* `{type}` must be the **exact same** `ConnectorType` enum value you'll use to create the connector — not an abbreviation, and not a differently-cased variant. This matters more than it sounds: for example, Google Drive's correct type is `GOOGLE_DRIVE2`. `GOOGLE_DRIVE` doesn't exist as a type, and `GOOGLE` is a *different*, separate connector type — calling this endpoint with `GOOGLE` returns `200` successfully, but authorizes against the wrong provider configuration, and will break connector creation later. A `400` response here almost always means the type value is wrong, not that a parameter is missing.
* `hostPrefix` should be the bare TMS hostname (host only, no scheme or path — e.g. `qa.memsource.com`).
**The returned `url` field is a template, not a ready-to-use link.** It contains literal placeholder text — `{state}` and `{redirectUri}` — that you must substitute yourself before sending it anywhere. The backend does not fill these in.
Substitute:
* `{state}` → the token from step 1.
* `{redirectUri}` → `/web/connector/receiveConnectorAuthCode` (this exact path is correct for every provider — don't vary it, and don't guess an alternative like `/tms/...`).
If the third-party provider rejects the resulting link with something like "redirect\_uri is not associated with this application," that means this fixed path doesn't match what's registered for that specific OAuth app — that's a configuration question for whoever manages the app's registered callback URLs, not something to work around by guessing a different path.
Only send the user a **fully substituted** URL — never one still containing literal `{state}` or `{redirectUri}` text, since the provider will reject it outright.
### 3. Let the user authorize, then poll for the code
Once the user completes the provider's sign-in and consent screen, their browser is redirected to the callback URL above with the real authorization `code` attached — but that callback page auto-submits the code server-side and the URL reverts within seconds, too fast to copy by hand.
Instead, poll for it:
```bash theme={null}
GET /api2/v1/connectors/connectorAuthCode/{code}
```
Despite the path parameter's name, pass the **state** token from step 1 here, not a code you don't have yet — the parameter is misleadingly named. Poll every few seconds until the response contains a populated `code` (not `null`).
If the user reports landing on the callback page with no code and no visible error, this is usually a stale or reused `state` token — restart from step 1 with a fresh one.
### 4. Create the connector
Create the connector using the code you polled for, following the type-specific fields documented in the [API reference](/en/api/tms/latest/introduction).
## GitHub Apps (`GITHUB2`) need one extra step
`GITHUB2` uses GitHub's newer "GitHub App" model rather than a classic OAuth App, which has two extra requirements beyond the flow above:
**The Phrase GitHub App must be installed** on the user's GitHub account or organization — OAuth sign-in alone does not install it. Direct users to install it here, ideally alongside the authorization link rather than only after a failure:
```
https://github.com/apps/phrase-github-integrations-app/installations/new
```
**An extra API call is required** before creating the connector, since its payload needs `login` and `tempLocalToken` fields that the standard code exchange doesn't provide:
```bash theme={null}
POST /api2/v3/connectors/github2/connect
{ "code": "...", "redirectUri": "..." }
```
This returns `{ "localToken": "...", "logins": [...] }`. If `logins` has more than one entry, ask the user which GitHub org/account to connect. Then create the connector with:
```json theme={null}
{ "type": "GITHUB2", "login": "", "tempLocalToken": "" }
```
Don't pass `code`/`redirectUri` directly to the create call for `GITHUB2` — that request shape is for classic `GITHUB` connectors only.
## Google Drive (`GOOGLE`, `GOOGLE_DRIVE2`) need `oauthClientId` too
Both types' create payload requires an `oauthClientId` field in addition to `code`+`redirectUri` — the create request fails without it.
You don't need to look this up separately or ask the user for it: the same `connectorAuthPage` call from step 2 above already returned an `oauthClientId` field in its response. Reuse that exact value verbatim in the create call.
## Salesforce (`SALESFORCE`) specifics
Salesforce differs from every other OAuth type in two ways that aren't obvious from the generic flow above.
### `hostPrefix` means something different for Salesforce
For every other type, `hostPrefix` in step 2 is just the TMS hostname, used to build the TMS-side redirect URI. For Salesforce, `connectorAuthPage` instead builds the third-party authorize URL as `https://{hostPrefix}.salesforce.com/services/oauth2/authorize?...` — `hostPrefix` here is **Salesforce's own login-domain prefix**, not the TMS host.
Passing the TMS host (e.g. `qa.memsource.com`) produces a real-looking but wrong domain (`qa.memsource.com.salesforce.com`) — a browser certificate error, not a real Salesforce host. Omitting it produces a broken empty-prefix URL (`https://.salesforce.com/...`).
Salesforce has required "My Domain" (`*.my.salesforce.com`) for all orgs for years, including every Developer Edition org (any domain ending `-dev-ed`). If the org identifier you have doesn't already end in `.my` (e.g. `d3x000002kko8uao-dev-ed`), append it yourself (`d3x000002kko8uao-dev-ed.my`) rather than sending it as-is — only use a bare `login`/`test` prefix (no `.my`) if you've confirmed the org still uses the legacy non-My-Domain login page. Sending the identifier without `.my` produces a `net::ERR_CERT_COMMON_NAME_INVALID` browser error.
### `salesforcePublishStrategyType` is required
The create call fails without this field. Its only valid values are:
* `PUBLISH` — translated content is published live immediately
* `DRAFT` — translated content is saved as a draft, not published live
* `SAME_AS_LATEST_TRANSLATION` — mirrors the publish state of the most recent translation
No other values exist — plausible-sounding names like `KNOWLEDGE` or `SALESFORCE_KNOWLEDGE` are not valid and will be rejected. If you're unsure which to pick, `PUBLISH` is the safest default, but confirm it against your own publishing requirements.
Collect this value upfront, alongside the other connector fields, rather than as a follow-up after a failed create attempt — each OAuth authorization code from step 3 is single-use, so a failed create call burns it and requires a full re-authorization for the next attempt.
## `PHRASE` connectors are the exception
The `PHRASE` connector type (which connects TMS to Phrase Strings, not another TMS instance or a third party) has a `code`-shaped field but is **not** part of this OAuth flow — it has no third-party OAuth provider registered, and calling `connectorAuthPage` for it returns an empty `url` by design. Its authorization is instead resolved server-side from your own authenticated identity. Attempt creation directly with the connector's base fields plus its Strings-specific fields (`phraseTmsOrganizationId`/`phraseTmsOrganizationName`).
# Introduction
Source: https://developers.phrase.com/en/guides/managing-connectors/overview
Understand the difference between the Connectors API and TMS connector management, and what you can do with each.
This guide is about **managing TMS connectors** — connecting Phrase TMS to third-party systems like Google Drive, GitHub, Amazon S3, WordPress, Marketo, and others. It does not cover building a new connector type from scratch; see [Build a TMS Plugin](/en/guides/build-a-tms-plugin/overview) for that.
## Two different APIs, easy to confuse
Phrase has two separate API surfaces that both use the word "connector," and it's easy to reach for the wrong one:
* **The [Connectors API](/en/api/connectors/introduction)** only moves file content through a connector that has *already been set up* — listing, uploading, downloading, and converting files inside a connector's remote storage. It has no endpoints to create, edit, or delete a connector itself.
* **The TMS API** (this guide) owns the connector's actual lifecycle — creating a new connector, listing your existing ones, viewing or editing a connector's configuration, deleting it, and checking its sync status. This is where you go to set up a new Google Drive, GitHub, Amazon S3, WordPress, or Marketo connector.
If you're trying to programmatically create or configure a connector and you're looking at the Connectors API, you're in the wrong place — come back here instead.
## What you'll build
A typical connector setup flow looks like this:
1. Confirm the connector doesn't already exist (list existing connectors and check by name/type — avoid creating duplicates).
2. For OAuth-based connector types (Google Drive, GitHub, Box, Salesforce, and others — see [OAuth Connector Setup](/en/guides/managing-connectors/oauth-setup)), walk the user through authorizing access to the third-party account.
3. Create the connector with the fields that type requires.
4. Check the connection status, and fix credentials if it's rejected.
5. Optionally, monitor sync status and browse what the connector has picked up.
## Choose your path
Walk through the authorization-code flow required for Google Drive, GitHub, Box, Salesforce, and other OAuth-based connector types.
Diagnose access-denied errors, per-connector-type field quirks, and common setup mistakes.
## The connector lifecycle endpoints
All connector lifecycle operations live under `/api2/v1/connectors` on the TMS API:
| Method & path | Purpose |
| ----------------------------------------------- | ------------------------------------------- |
| `GET /api2/v1/connectors` | List your connectors |
| `POST /api2/v1/connectors` | Create a connector |
| `GET /api2/v1/connectors/{connectorId}` | View a connector's configuration |
| `PATCH /api2/v1/connectors/{connectorId}` | Edit a connector's configuration |
| `DELETE /api2/v1/connectors/{connectorId}` | Delete a connector |
| `GET /api2/v1/connectorAsyncTasks` | Check background sync task status |
| `GET /api2/v1/connectors/{connectorId}/folders` | Browse what a connector has actually synced |
Before creating a connector, list your existing connectors and check for one with a matching name/type — this avoids accidentally creating duplicates if a previous attempt errored or its result was unclear.
Once a connector is created, keep a reference to its `uid` — you'll need it to view, edit, delete, or check the status of that connector later, and it's what you'd use to link directly to the connector's page in the TMS UI (`/tms/connectors/edit/{uid}`).
`PATCH /api2/v1/connectors/{connectorId}` requires **both** `name` and `type` in the request body, even if you're only changing one field (e.g. a pure rename). Sending just the changed field (e.g. `{"name": "..."}` alone) is rejected with a `400` ("Cannot parse the JSON request"). Read the connector's current `type` from a prior `GET` before editing it.
A connector being successfully **created** is a different thing from its connection being successfully **tested**. Creating or editing a connector does **not** automatically verify its credentials, except for `MAGENTO` and `TYPO3` connector types, which always run a live connection test. For every other type, pass `connectionTest=true` as a query parameter on `POST`/`PATCH` to run one explicitly — otherwise a connector with entirely wrong credentials can be created successfully and only fail later. If a connection test reports an error status (e.g. `GENERAL_ERROR`, `UNAUTHORIZED`), the connector object still exists — you'll usually need to go fix its credentials rather than starting over.
# Troubleshooting
Source: https://developers.phrase.com/en/guides/managing-connectors/troubleshooting
Common connector setup errors, per-connector-type field quirks, and how to diagnose access-denied responses.
## Access-denied (403) on connector creation
A `403`/access-denied response when creating a connector has two unrelated causes that share the same generic exception — always read the exact error message text, since that's the only way to tell them apart.
### 1. Role and access rights
Creating a connector requires the **ADMIN** or **PROJECT\_MANAGER** role, *and* a separate access right called **"Modify global server settings"**. Having the ADMIN role does not guarantee this right is enabled — it's an independent per-account setting. If your error message is a generic access-denied with no further detail, check this first.
### 2. Per-connector-type subscription entitlement
Some connector types are gated behind their own subscription add-on, independent of your role or access rights: `AEM_PLUGIN`, `CONTENTFUL`, `HUBSPOT`, `OPTIMIZELY`, `TRIDION`, `KENTICO_KONTENT`, `BRAZE_MULTILANG`, `CONTENTSTACK`, `MARKETO`, and `ZENDESK` each require a separate Phrase subscription add-on.
If the error message literally says something like **"`` is not enabled"**, this is not a permissions problem at all — your organization's Phrase subscription doesn't include that connector's add-on. This is fixable through your subscription/billing, not through any access-rights toggle.
If you can successfully create one connector type but a different type fails with an access-denied error, that's a strong signal you're looking at case 2, not case 1 — a role/rights problem would affect every connector type equally.
## Per-connector-type field quirks
The generic connector-creation schema only documents the common base fields (`name`, `type`, `sourceUrl`, `defaultRemoteFolder`, `encodedDefaultRemoteFolder`, `commitMessage`) — it doesn't reliably surface each connector type's own extra fields. A handful of types have field names that don't match what you'd naturally guess:
### Amazon S3 (`AMAZON_S3`)
Fields are exactly `apiKey`, `apiSecret`, and `amazonIamRole` — not AWS's own naming (`accessKeyId`/`secretAccessKey`). There is **no `region` field**: the region is embedded directly in the bucket hostname (e.g. `integrations-plugins.qa.eu-west-1.s3.memsource.com`).
Sending the wrong field names here doesn't produce a validation error — the API silently accepts `null` credentials and only fails later, at test-connection time, with an unhelpful server error that can look like a platform outage. If test-connection fails right after creating an `AMAZON_S3` connector, check your field names before assuming anything else is wrong.
### Marketo (`MARKETO`)
`marketoConnectorType` is required — creation fails without it. Collect it upfront alongside the credentials, not as a follow-up question after the connector already failed to create.
You need four things to create a Marketo connector: the API key, the API secret, the API identity URL, and how the customer wants "Create translations" to work. Use the TMS UI's own field labels when asking for credentials — "Marketo API key", "Marketo API secret", and "Marketo API identity URL" — rather than Marketo's native OAuth terminology ("Client ID", "Client Secret", "Munchkin ID"). They map directly to the API's field names, but the host value must be sent as **`identityURL`** in the request body — `host` is a hidden/internal field name and is silently ignored if you send it instead.
The identity URL follows the format `https://.mktorest.com/identity` — for example `https://063-RVK-838.mktorest.com/identity`. Find your own instance's value in Marketo Admin → Integration → Web Services.
The "Create translations" mode field is `marketoConnectorType`, and its values are not `dynamic`/`static` as you might expect — they match the two options shown in the TMS UI:
* `SEPARATED_ASSET` — "as separate documents": translations are created as siblings of the original document, with the locale code appended to the title (e.g. *Black Friday promo \[de-de]*)
* `SEGMENTED_DYNAMIC_CONTENT` — "as segmented dynamic content": translations are stored within the original document, with each segment associated with a target locale
Required fields are exactly `apiKey`, `apiSecret`, `host` (sent as `identityURL`), and `marketoConnectorType`. Segmentation mapping (`marketoSegmentationMapping`) is optional at the API level even when using segmented dynamic content mode.
### Adobe Experience Manager (`AEM_PLUGIN`)
Beyond the base fields: `host` (required), plus optional `basicAuthUserName`/`basicAuthPassword` (used specifically for live preview, not the main connection), `forcedHttps` (boolean), and `urlRewriteFind`/`urlRewriteReplace`. Not OAuth-based.
### WordPress (`WORDPRESS`)
WordPress connector credentials are **not** your normal WordPress admin login. They come from the Phrase/Memsource Connector plugin's own settings page on the WordPress site:
```
https:///wp-admin/admin.php?page=memsource-connector
```
On that page, click **"Show Connector settings"** and copy the **"Phrase TMS Connector authentication token"**, plus the username and password shown there.
If test-connection returns `GENERAL_ERROR`, check whether the site's host is actually a public URL first. A host like `localhost` or a local development port is unreachable from Phrase's servers entirely, regardless of whether the credentials are correct — you'll need a public URL or a tunnel (e.g. ngrok) for Phrase to reach a local WordPress instance.
### GitHub (plain `GIT` type, credential-based)
If you're connecting to a `github.com` (or GitHub Enterprise) host using the plain `GIT` connector type (not `GITHUB`/`GITHUB2`), note that GitHub has rejected real account passwords for Git-over-HTTPS since August 2021. The password field must be a **Personal Access Token**, not your actual account password. An `UNAUTHORIZED` response for a GitHub-hosted `GIT` connector is almost always this.
### Contentstack (`CONTENTSTACK`)
`contentStackRegion` is required in practice even though the create-connector schema doesn't mark it required. Omitting it passes request validation, but the backend then fails while converting the missing value, surfacing as a generic `500` rather than a validation error — nothing about that response tells you which field caused it.
Send `contentStackRegion` alongside `contentStackAuthType`, `apiKey`, and `sourceLang` (the fields the schema does mark required) on every create request. Valid values are `NORTH_AMERICA`, `EUROPE`, `AZURE_NORTH_AMERICA`, `AZURE_EUROPE`, `GCP_NORTH_AMERICA`, and `CUSTOM` — pick whichever matches where your Contentstack organization is hosted; `NORTH_AMERICA` is the default region for accounts that haven't chosen a specific one. If a `CONTENTSTACK` create request 500s, check for this field before assuming a platform outage.
### Types not listed here
For any connector type not covered above, inspect its create-connector request schema directly (use the full schema view in the API reference, not the summarized one) before assuming what fields it needs: a `code`+`redirectUri` field pair means it's OAuth-based (see [OAuth Connector Setup](/en/guides/managing-connectors/oauth-setup)); anything else (a token, an API key, or host+credentials) means it isn't.
# Guides
Source: https://developers.phrase.com/en/guides/overview
Implementation guides for extending Phrase capabilities
Learn how to extend Phrase with custom integrations, AI tooling, and bring your own services.
Integrate your own machine translation engine into Phrase Language AI using a standardized API interface.
Build a plugin for Phrase TMS that sends source content for localization and retrieves translated content.
Create, configure, and troubleshoot TMS connectors to Google Drive, GitHub, Amazon S3, WordPress, Marketo, and other third-party systems.
Set up a connector to automatically create translation projects on a schedule, and troubleshoot APC's full-replace update behavior.
# Phrase MCP Server
Source: https://developers.phrase.com/en/mcp-server
Connect any Model Context Protocol (MCP)-compatible AI tool to Phrase.
The Phrase MCP Server lets an AI assistant work directly with your Phrase data. It can check job status, run quality checks, or start routine work in plain language, without opening Phrase or writing code. Any Phrase account with API access can use it.
## Connect
No installation, no tokens to manage. You sign in with your existing Phrase account.
**Prerequisites**
* An active Phrase account with access to the organization you want to connect
* An MCP-compatible AI tool that supports adding a remote server by URL
Point your MCP client at the server URL for your region:
* EU: `https://mcp.eu.phrase.com`
* US: `https://mcp.us.phrase.com`
Check your Phrase account URL (`eu.phrase.com` or `us.phrase.com`) if you're not sure which region you're on. Exactly how you register a remote server depends on your client. Refer to its documentation for the precise steps.
```bash theme={null}
claude mcp add --transport streamable-http phrase-mcp-prod-eu https://mcp.eu.phrase.com
```
Use `phrase-mcp-prod-us` and the US URL if that's your region.
1. Go to **Settings → Connectors → Advanced settings → Add custom connector**.
2. Name it `Phrase`, paste the server URL for your region, and leave authentication as-is, you'll sign in in the next step.
3. Save the connector.
Start the authorization flow from your client. Sign in, pick your organization, and choose what to allow: View data, Create data, or Modify and delete data. Your normal Phrase role permissions still apply. This only restricts what you can do, it never expands it. You can change this later.
Run `/mcp` and select the Phrase server.
Open the Phrase connector and click **Connect**.
Ask your assistant to "fetch my most recent Phrase project."
You can view what you've authorized and revoke access anytime from your Phrase profile settings, under the Consents tab. This list is scoped to whichever organization you're signed into. If you connect to more than one organization, check each one separately. To see or manage consents for a different organization, sign out and back in under that organization first.
***
## Troubleshooting
The sign-in didn't complete. Restart the authorization flow from your client and try again.
Confirm the server shows as connected in your client.
The scopes you granted at authorization limit what the assistant can do. Your Phrase role permissions still apply in addition to them, not instead of them. Check what you allowed when you connected.
## What's next
Once you're connected, head to **[Starter prompts](/en/mcp-server-starter-prompts)** to see what to try first.
***
## Local install (deprecated)
Before the hosted server above, the Phrase MCP Server was a self-hosted, open-source package you ran yourself with your own API tokens. It's still available for teams already using it, but the connect flow above is now the recommended way to get started.
Setup, configuration, and troubleshooting for the local install live in the [phrase/phrase-mcp-server](https://github.com/phrase/phrase-mcp-server) README.
# Starter prompts
Source: https://developers.phrase.com/en/mcp-server-starter-prompts
The moment you connect, here's what's worth trying first.
These prompts are grouped by what you’re trying to get done. Pick whichever one matches what you’re working on. No code, no need to know the Phrase API.
You can't accidentally break anything here. Your AI assistant can only create or change data if you explicitly granted that when you connected. Everything below is safe to try. If in doubt, check what you allowed in your Phrase profile settings, under the Consents tab.
Before you start, make sure you've picked the right organization and access level when you connected. See [connecting Phrase to your AI tools](/en/mcp-server) if you need to check.
## Get oriented
*First prompt, first win. Confirms the connection works and shows Phrase talking back.*
**See what's connected**
```text theme={null}
List my 5 most recently updated Phrase projects
```
Pulls a short, recent slice of your projects rather than everything you have access to. Confirms you're connected and pointed at the right organization, without a slow, oversized first response.
## Start building
*Now for the good part: things that used to need a ticket to engineering, or just didn't get done. One line, no queue, no waiting.*
**Terminology audit**
```text theme={null}
How many times did we use 'can't' vs 'cannot' across [project name]?
```
Searches your whole project for both terms and gives you a count.
**Placeholder mismatch check**
```text theme={null}
Check [job name] for target locales where placeholders don't match the English source, and give me a prioritized list
```
Compares placeholders across every target language against the source and returns a ranked list of mismatches. Catches a broken placeholder, like a missing tag, before it ships. Run it weekly and it's a standing quality gate, no engineering needed.
**Automated GitHub pipeline**
```text theme={null}
When a new content batch is pushed to [GitHub repo], trigger a Phrase job, assign [MT engine], and notify [Slack channel] once it's done
```
No one has to remember to start this, and no one has to be involved for it to run.
**Deadline watch**
```text theme={null}
Monitor all active TMS jobs due in the next 48 hours, and message me if any don't have a linguist assigned
```
Checks due dates and linguist assignments across active jobs, and only messages you if something's missing. A standing watch, not a one-time check.
**Stale job check**
```text theme={null}
List all jobs that haven't been updated in the last year
```
Surfaces jobs with no recent activity, so stale work doesn't sit forgotten until someone asks about it.
**Reassign ownership**
```text theme={null}
Reassign all of [name]'s live projects to [new owner]
```
Moves ownership across every active project in one step. Useful if someone’s away and the work still needs to move.
**Bulk key update**
```text theme={null}
Update these 300 keys with the values from this spreadsheet, across [languages]
```
Matches the keys in your spreadsheet to the ones already in the project and updates them in bulk.
## A note on permissions
If a prompt above doesn't work, it's usually because it needs access you didn't grant when you connected (see above), not a bug.
## Didn't find what you needed?
These prompts are starting points, not a full list. Once you're comfortable, just describe what you're trying to do in your own words. Your AI assistant will work out which Phrase actions it needs.
Once you land on a prompt that works well, save it and share it with your team. A small shared library of prompts that actually work is one of the fastest ways to get everyone using this well.
## Found a great use case, or hit a wall?
Tell us. This page grows from real examples, several of the prompts above came directly from customers who tried something and it worked. If you've got one worth sharing, or a prompt that should work but doesn't, let us know via the [Phrase Help Center](https://support.phrase.com/hc/requests/new).
# Android
Source: https://developers.phrase.com/en/ota/android
OTA SDK for Kotlin and Java Android apps
The Phrase Android SDK fetches OTA translation releases at runtime and caches them on-device. If the OTA service is unreachable, the SDK falls back to the last cached translations or to the strings bundled in your app binary.
For more information on requirements and supported platforms, see the repository [README](https://github.com/phrase/phrase-android).
## Installation
Add the Phrase Maven repository to your root `build.gradle`, then add the SDK dependency:
```gradle theme={null}
// root build.gradle
allprojects {
repositories {
maven { url "https://maven.download.phrase.com" }
}
}
// app/build.gradle
dependencies {
// With Jetpack Compose support
implementation "com.phrase.android:ota-sdk-compose:"
// Without Compose (views only)
// implementation "com.phrase.android:ota-sdk:"
}
```
## Setup
Initialize in your `Application` class and trigger the initial fetch. Both values are available in the Phrase Strings dashboard after creating a distribution, or via the [Distributions API](/en/api/strings/introduction):
```kotlin theme={null}
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
Phrase.setup(this, "YOUR_DISTRIBUTION_ID", "YOUR_ENVIRONMENT_SECRET")
Phrase.updateTranslations()
}
}
```
To react to the result:
```kotlin theme={null}
Phrase.updateTranslations(object : TranslationsSyncCallback {
override fun onSuccess(translationsChanged: Boolean) { }
override fun onFailure() { }
})
```
## Using translations
**Jetpack Compose:**
```kotlin theme={null}
Text(phraseString(R.string.your_key))
```
**Android Views — base activity pattern:**
```kotlin theme={null}
open class BaseActivity : AppCompatActivity() {
override fun getDelegate() = Phrase.getDelegate(this, wrapContext = false)
}
```
Layouts can then reference `@string/your_key` as normal; the SDK intercepts the resource lookup.
**Programmatic access:**
```kotlin theme={null}
textView.setText(context.getPhraseString(R.string.your_key))
```
## Configuration options
```kotlin theme={null}
Phrase.setTimeout(20_000) // Network timeout in ms (default: 10,000)
Phrase.setDatacenter(Datacenter.US) // Use the US data center (default: EU)
Phrase.setAppVersion("2.1.0") // Override detected app version for release targeting
```
`setAppVersion` is useful if your `versionName` does not follow `..` format, which is required for [semantic version targeting](/en/ota/introduction#releases).
# Flutter
Source: https://developers.phrase.com/en/ota/flutter
OTA SDK for Flutter apps
The Phrase Flutter SDK delivers OTA translation releases to Flutter apps across all supported platforms: iOS, Android, Linux, macOS, and Windows. The example repository demonstrates a full integration.
## Setup
1. Add the Phrase Flutter SDK to your `pubspec.yaml`
2. Run the Phrase code generator:
```bash theme={null}
dart run phrase
```
3. Create a distribution in Phrase Strings and copy the Distribution ID and environment secret from the dashboard or via the [Distributions API](/en/api/strings/introduction)
4. Initialize the SDK in `main.dart` before rendering any strings:
```dart theme={null}
Phrase.setup("YOUR_DISTRIBUTION_ID", "YOUR_ENVIRONMENT_SECRET");
```
5. Run your app:
```bash theme={null}
flutter run
```
# i18next
Source: https://developers.phrase.com/en/ota/i18next
OTA backend plugin for i18next
`@phrase/i18next-backend` is a backend plugin for [i18next](https://www.i18next.com/) that fetches translations from Phrase OTA at runtime. It works with any JavaScript framework that uses i18next — React, Vue, Angular, Node.js, React Native, and others. The plugin caches translations locally and re-fetches when the cache expires, minimizing requests to the Phrase CDN.
This guide applies to v2+ of the SDK.
## Installation
```bash theme={null}
npm install --save @phrase/i18next-backend
```
## Setup
```javascript theme={null}
import i18n from "i18next";
import { I18nextPhraseBackend } from "@phrase/i18next-backend";
i18n
.use(I18nextPhraseBackend)
.init({
fallbackLng: "en",
backend: {
distribution: "YOUR_DISTRIBUTION_ID",
environment: "YOUR_ENVIRONMENT_SECRET",
appVersion: "1.0.0",
},
});
```
Both `distribution` and `environment` are available in the Phrase Strings dashboard after creating a distribution, or via the [Distributions API](/en/api/strings/introduction).
## Configuration options
| Option | Required | Description |
| --------------------- | -------- | ------------------------------------------------------------------------------------------------------- |
| `distribution` | Yes | Your OTA distribution ID |
| `environment` | Yes | Environment secret — differs between development/beta and production |
| `appVersion` | No | (Semantic) app version for release targeting via min/max version constraints |
| `cacheExpirationTime` | No | Cache duration in seconds (default: `300`; recommended minimum for production) |
| `datacenter` | No | OTA data center — `Datacenter.EU` (default) or `Datacenter.US`, imported from `@phrase/i18next-backend` |
| `format` | No | Translation format — defaults to `i18next`; set to `i18next_4` for v4 pluralization |
| `storage` | No | Custom async storage implementation for caching. Required on React Native (see below) |
## React Native
The backend has no built-in storage on React Native, so pass an `AsyncStorage`-compatible implementation via the `storage` option. Any storage exposing `getItem`, `setItem`, and `clear` (returning Promises) works.
```bash theme={null}
npm install --save @phrase/i18next-backend @react-native-async-storage/async-storage
```
```javascript theme={null}
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { I18nextPhraseBackend } from "@phrase/i18next-backend";
i18n
.use(I18nextPhraseBackend)
.use(initReactI18next)
.init({
fallbackLng: "en",
backend: {
distribution: "YOUR_DISTRIBUTION_ID",
environment: "YOUR_ENVIRONMENT_SECRET",
storage: AsyncStorage,
},
});
```
For projects that target both React Native and web via `react-native-web`, passing `AsyncStorage` works on both — its web build is backed by `localStorage`. Alternatively, omit `storage` and the package auto-detects `localStorage` on web.
## Managing releases
Create and publish releases using the [Strings API](/en/api/strings/introduction) or the Phrase Strings dashboard. To publish releases automatically on a schedule, use [release triggers](/en/ota/introduction#release-triggers).
# Over-the-Air (OTA)
Source: https://developers.phrase.com/en/ota/introduction
Push translation updates to live apps without a new release
Phrase Strings Over-the-Air (OTA) lets you update translations in live applications without submitting a new app version to an app store or redeploying to your server. Once your app integrates our OTA SDK, it fetches translations from the Phrase CDN at runtime. When you publish a new release, users see updated strings as soon as the app has fetched them.
OTA currently integrates with iOS, Android, React Native, Flutter, i18next, and Rails.
## Core concepts
OTA is built around two resources: **distributions** and **releases**.
### Distributions
A distribution defines which Phrase Strings projects, locales, and platforms are bundled for OTA delivery. Each distribution has its own credentials — a **distribution ID** and an **environment secret** — that the SDK uses to authenticate against the Phrase CDN.
Multiple distributions are possible, but the recommended setup is one distribution per project. When a distribution targets both iOS and Android, string placeholders between the two formats are automatically converted.
A distribution can optionally be restricted to only serve **reviewed translations** — keys that have not passed review are omitted from releases published under that distribution.
Distribution settings only take effect on releases published after the change. If you update a distribution (e.g. its fallback languages or reviewed-only setting), publish a new release for connected SDKs to pick it up.
#### Language fallbacks
A distribution offers three independent fallback options, applied when a requested translation is unavailable or doesn't exist:
* **Use language fallback** — serve the fallback languages configured in the project's language settings instead of the requested language.
* **Fallback languages exclude regions** (recommended) — if a requested regional language isn't found, the region is dropped and the base language is served instead (e.g. `de-DE` → `de`).
* **Fallback languages use the default locale** (recommended) — if the requested language isn't found at all, the project's default locale is served instead (e.g. `en-AU` → `en-US`).
Fallbacks do not apply to linked keys.
### Releases
A release is a point-in-time snapshot of the translations in a distribution. Publishing a release exports the current project state and makes it available to any device or instance running an SDK configured with that distribution's credentials.
Releases support **semantic version constraints**: you can target a release to a range of app versions (e.g. `>= 2.0.0 < 3.0.0`), so users on older versions are not served translations that reference keys added in a newer release.
### Environments
Every distribution has two environment secrets: **development** and **production**. The SDK receives releases for whichever environment its secret belongs to.
A newly created release is available to the **development** environment immediately. It reaches the **production** environment only once you publish it (using the [API endpoint](/en/api/strings/releases/publish-a-release), or *Publish* in the dashboard).
In practice: point debug and QA builds at the development secret to preview a release before it ships, and point store/production builds at the production secret so end users only see releases you have explicitly published.
### Release triggers
[Release triggers](https://support.phrase.com/hc/en-us/articles/5804059067804-Over-the-Air-Strings#schedule-ota-releases-0-5) automate release publishing on a cron schedule. Configure a trigger on a distribution to create and publish new releases automatically at a set frequency, without manual intervention.
## Resilience
OTA SDKs are designed so that a slow network or an unavailable OTA service never breaks your app. When the SDK cannot reach the Phrase CDN, it resolves translations through a layered fallback chain:
1. **On-device cache** — the last release successfully fetched is stored locally. As long as a device has fetched at least one release, it continues to display those translations indefinitely, regardless of connectivity.
2. **Bundled translations** — if the device has never successfully fetched a release (e.g. on first launch with no network), the SDK falls back to the translation files shipped inside the app binary.
Because the bundled translations are the final safety net, keep them up to date with every app release. A device that has never been online will always see the bundled strings, so stale bundles lead to stale copy for those users.
## Usage limits
OTA usage is subject to limits based on the number of **requests** and **Monthly Active Users (MAU)**. The exact allowances depend on your Phrase subscription — see the [pricing page](https://phrase.com/pricing/#acc-limits) for your plan's details.
### Data sent with each request
Each SDK request to the Phrase CDN includes: the device identifier, app version, cached translation file timestamp, SDK version, requested locale, file format, client type, distribution ID, and environment secret. No other device or user data is transmitted.
## Reports
Each distribution provides usage reports, refreshed twice a day, covering:
* Active users
* Overall requests
* Requests per language
* Requests per platform
* Device languages not covered by the distribution
Reports are accessible from the *Over the air* page in the Phrase Strings dashboard.
## Supported SDKs
PhraseSDK for Swift and Objective-C apps. Supports SPM, CocoaPods, and Carthage.
Kotlin and Java SDK with Jetpack Compose and Android Views support.
Cross-platform SDK for iOS, Android, and desktop.
SDK for React Native apps on iOS and Android.
JavaScript plugin for React, Vue, Angular, Node.js, React Native, and any i18next-based app.
Ruby gem that replaces the Rails i18n backend with Phrase OTA.
## Network requirements
The SDKs communicate with the following domains. Add them to your allowlist if your network restricts outbound connections:
| Data center | Domains |
| ------------ | ------------------------------------------------ |
| EU (default) | `ota.eu.phrase.com`, `cdn.eu.phrase.com` |
| EU (legacy) | `ota.phraseapp.com`, `cdn.phraseapp.com` |
| US | `ota.us.phrase.com`, `cdn.us.phrase.com` |
| US (legacy) | `ota.us.app.phrase.com`, `cdn.us.app.phrase.com` |
## API reference
Manage distributions and releases programmatically.
Create and manage OTA distributions.
Publish translation snapshots to connected SDKs.
Automate release publishing.
# iOS
Source: https://developers.phrase.com/en/ota/ios
OTA SDK for Swift and Objective-C iOS apps
The Phrase iOS SDK fetches OTA translation releases at runtime and caches them on-device. If the OTA service is unreachable, the SDK falls back to the last cached translations or to the strings bundled in your app binary.
For more information on requirements and supported platforms, see the repository [README](https://github.com/phrase/ios-sdk).
## Installation
```text Swift Package Manager theme={null}
Add https://github.com/phrase/ios-sdk/ in Xcode under
File → Add Package Dependency
```
```ruby CocoaPods theme={null}
pod 'PhraseSDK'
```
```text Carthage theme={null}
binary "https://raw.githubusercontent.com/phrase/ios-sdk/master/PhraseSDK.json" ~> 5.0.0
```
For Carthage, run `carthage update --use-xcframeworks` after adding the entry. You can also download the latest release manually and link `PhraseSDK.xcframework` as a binary.
## Setup
Call `setup` before any strings are displayed, typically in `AppDelegate.application(_:didFinishLaunchingWithOptions:)` or at the entry point of your SwiftUI app:
```swift theme={null}
import PhraseSDK
Phrase.shared.setup(
distributionID: "YOUR_DISTRIBUTION_ID",
environmentSecret: "YOUR_ENVIRONMENT_SECRET"
)
```
Both values are available in [Phrase Strings](https://app.phrase.com/go/ota) after creating a distribution, or via the [Distributions API](/en/api/strings/introduction).
## Fetching and applying translations
```swift theme={null}
Task {
do {
let updated = try await Phrase.shared.updateTranslation()
if updated {
Phrase.shared.applyPendingUpdates()
}
} catch {
// Falls back to cached or bundled translations automatically
}
}
```
`applyPendingUpdates()` makes the new strings visible immediately. If omitted, they take effect on the next cold launch.
## Configuration options
```swift theme={null}
Phrase.shared.configuration.debugMode = true // Enable verbose SDK logging
Phrase.shared.configuration.timeout = 20 // Network timeout in seconds (default: 10)
Phrase.shared.configuration.localeOverride = "en-US" // Force a specific locale
Phrase.shared.configuration.ignoreOtherTables = true // Only patch the default strings table
Phrase.shared.configuration.apiHost = .us // Use the US data center (default: EU)
```
# Ruby on Rails
Source: https://developers.phrase.com/en/ota/rails
OTA gem for Ruby on Rails applications
`phrase-ota-i18n` is a Ruby gem that replaces the default Rails i18n backend with a Phrase-backed one. Instead of reading translations from local YAML files, your application fetches the current release from the Phrase CDN at runtime. Publishing a new release in Phrase Strings propagates to all running instances without a redeploy.
If Phrase is unreachable, the gem falls back to the locale files bundled with your application.
## Installation
Add to your `Gemfile`:
```ruby theme={null}
gem 'phrase-ota-i18n'
```
Then run:
```bash theme={null}
bundle install
```
## Setup
Run the Rails generator to create the initializer:
```bash theme={null}
bundle exec rails generate phrase_ota:install \
--distribution-id YOUR_DISTRIBUTION_ID \
--secret-token YOUR_SECRET_TOKEN
```
Both parameters are required:
| Parameter | Description |
| ------------------- | ---------------------------------------------------------------- |
| `--distribution-id` | The ID of the distribution you created in Phrase Strings |
| `--secret-token` | The environment secret for authenticating against the Phrase CDN |
Both values are available in the Phrase Strings dashboard after creating a distribution, or via the [Distributions API](/en/api/strings/introduction).
## Managing releases
Create and publish releases using the [Strings API](/en/api/strings/introduction) or the Phrase Strings dashboard. There is no version targeting for Rails releases — all running instances always receive the latest published release.
To publish releases automatically on a schedule, use [release triggers](/en/ota/introduction#release-triggers).
# React Native
Source: https://developers.phrase.com/en/ota/react-native
OTA SDK for React Native apps
The Phrase React Native SDK delivers OTA translation releases to iOS and Android apps built with React Native. It integrates with [react-i18next](https://react.i18next.com/) as a chained backend, so translations fetched from Phrase are merged with your local resource bundles. It follows the same distribution and release model as the other mobile SDKs — see the [OTA introduction](/en/ota/introduction) for a full explanation of core concepts.
Setup instructions, configuration options, and code examples are maintained in the [example app](https://github.com/phrase/react_native_sdk_example).
The SDK caches translations and a generated device identifier in `AsyncStorage`. Clearing `AsyncStorage` entirely (for example on logout) causes the app to regenerate that identifier, which inflates your Monthly Active Users count — clear only your own app's keys if you need to reset state.