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

# Build your CI/CD pipeline

> Take a code push all the way to live translations, with no manual step in the middle. Wire the whole loop: source strings go up to Phrase, translation happens, and finished files come back to your build.

## Quickstart

```bash theme={null}
curl --request GET \
  --url https://api.phrase.com/v2/projects/<PROJECT_ID> \
  --header 'Authorization: Bearer <JWT_TOKEN>'
```

## Prerequisites

1. Authentication set up. See the [Authentication guide](/en/api/platform/authentication), and store the API token as a CI secret. The pipeline exchanges it for the short-lived JWT the calls below send as `Bearer <JWT_TOKEN>`.
2. Phrase Strings [command-line interface (CLI)](/en/developer-tools/strings-cli) installed. It provides `phrase push` and `phrase pull`.
3. A project ID, the project code taken from the project in Phrase.
4. A `.phrase.yml` config file. Run `phrase init` to scaffold one; it holds `push.sources` and `pull.targets`.
5. A CI platform such as GitHub Actions or GitLab CI. You author the pipeline YAML.

## How-to

The pipeline has two halves. Source strings go up when code changes. Finished translations come down before the build ships. Keep the two stages separate, since translation happens between them.

1. Push source strings on every change. Run `phrase push` in the job that reacts to a code change; the command-line interface uploads each source file. The direct API call is the multipart upload below. Processing is asynchronous: the response returns 201 with an upload id, so poll the upload status until state reads success. Prefer this file upload over a per-key loop. A 422 means the request has a bad field, usually the file format or an unknown language code; the `errors` array names it, so correct it and retry. To get translations back sooner, set `autotranslate` on the upload so machine translation (MT) fills them in as the file imports.

   ```bash theme={null}
   curl --request POST \
     --url https://api.phrase.com/v2/projects/<PROJECT_ID>/uploads \
     --header 'Authorization: Bearer <JWT_TOKEN>' \
     --form file=@locales/en.json \
     --form file_format=json \
     --form locale_id=<SOURCE_LOCALE_ID>
   ```

2. Pull finished translations before the build. Translation runs inside Phrase, by people or machine translation, and is outside the pipeline. Run `phrase pull`, or call the two-step download below: start the download, then poll the status until it reads completed and fetch `result.url`, which is valid for 15 minutes. If the status stays processing, keep polling on a short interval rather than firing many parallel requests.

   ```bash theme={null}
   # 1. Start the download
   curl --request POST \
     --url https://api.phrase.com/v2/projects/<PROJECT_ID>/locales/<LOCALE_ID>/downloads \
     --header 'Authorization: Bearer <JWT_TOKEN>' \
     --header 'Content-Type: application/json' \
     --data '{"file_format":"json"}'

   # 2. Poll, then fetch result.url
   curl --request GET \
     --url https://api.phrase.com/v2/projects/<PROJECT_ID>/locales/<LOCALE_ID>/downloads/<DOWNLOAD_ID> \
     --header 'Authorization: Bearer <JWT_TOKEN>'
   ```

## When it breaks: common errors

A 4xx response tells you what to fix. For a 422, the `errors` array names the exact field.

<Card title="422: bad file or language">
  The file format is wrong, or the language code is not one Phrase recognizes. The `errors` array names the field, so correct it and retry.
</Card>

<Card title="404: wrong ID">
  The project ID, or a locale or download ID, does not resolve. Check the ID in the path and that the token can access it.
</Card>

<Card title="403: forbidden">
  The token is missing the write scope for uploads and locale creation, or read for downloads, or you lack permission. Use a token with the right scope.
</Card>

<Card title="429: rate limited">
  A 429 means too many requests in a short time: wait for the window to reset, then retry.
</Card>

## Troubleshooting

<Card title="Status">
  Check the [Phrase status page](https://status.phrase.com) before assuming your own request is wrong.
</Card>

<Card title="Where to get help">
  If Phrase is healthy and the `errors` array does not explain the failure, [contact support](https://support.phrase.com/hc/requests/new).
</Card>

<Card title="Help center">
  [Support articles and answers](https://support.phrase.com) to common Phrase questions.
</Card>

## Next steps

1. Review the reference pages this pipeline calls: [Upload a new file](/en/api/strings/uploads/upload-a-new-file), and [Initiate a locale download](/en/api/strings/locale-downloads/initiate-async-download-of-a-locale).
2. Wire the delivery half: Push translation fixes without a release, and Auto-sync translation keys on code push.

*Last updated: September 23, 2026. Track documentation changes in the [changelog](/en/changelog).*
