> ## 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.

# Push translation fixes without a release

> Fix a wrong string in a shipped app and get it to users the same day, with no new app-store version. Over-the-Air (OTA) delivery lets the app fetch translations from a content delivery network at runtime, so a copy change is a publish, not a redeploy.

## 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). The token needs the write scope to create and publish releases, and the calls below send the short-lived JWT it is exchanged for as `Bearer <JWT_TOKEN>`.
2. The Over-the-Air (OTA) feature on your plan. It is a paid add-on, and a request without it returns 403.
3. A project ID, the project code taken from the project in Phrase.
4. Your account ID. Distribution and release calls are scoped to it.
5. An [Over-the-Air (OTA) SDK](/en/ota/introduction) in your app. Add the SDK for your platform; it fetches translations at runtime.

## How-to

Three terms do the work here. A distribution is one delivery channel for a project and its platforms. A release is a versioned snapshot of translations inside that distribution. Publishing a release promotes it to production, where the SDK can fetch it. A Phrase release is not an app-store release; the whole point is to ship one without the other.

1. Create the distribution once. A distribution is set up once per project and platform set, against your account. The `name` and `project_id` fields are required; valid platforms are `android`, `ios`, `flutter`, `i18next`, and `rails`. The response does not include the distribution secret the SDK needs; copy that from the distribution's page in the Phrase web interface. If creation returns 403, the account plan does not include Over-the-Air, so enable the feature before going further.

   ```bash theme={null}
   curl --request POST \
     --url https://api.phrase.com/v2/accounts/<ACCOUNT_ID>/distributions \
     --header 'Authorization: Bearer <JWT_TOKEN>' \
     --header 'Content-Type: application/json' \
     --data '{
       "name": "Acme iOS",
       "project_id": "<PROJECT_ID>",
       "platforms": ["ios"],
       "locale_ids": ["<LOCALE_ID>"]
     }'
   ```

2. Create a release. A new release starts in the development environment. The response returns 201 with an auto-incremented version. At this point the release exists but is not live yet.

   ```bash theme={null}
   curl --request POST \
     --url https://api.phrase.com/v2/accounts/<ACCOUNT_ID>/distributions/<DISTRIBUTION_ID>/releases \
     --header 'Authorization: Bearer <JWT_TOKEN>' \
     --header 'Content-Type: application/json' \
     --data '{"description": "Fix checkout button copy"}'
   ```

3. Publish the release. Publishing is the step that pushes the fix to users. A 200 confirms it, and the release now lists production in its environments, so the SDK fetches it on its next check with no app-store submission. If publish returns 200 but nothing changes, the release was already published, which is a safe no-op; create a new release for the next fix. A 422 means a field failed validation, often an `app_min_version` that is not valid semantic versioning; the `errors` array names it, so correct it and retry.

   ```bash theme={null}
   curl --request POST \
     --url https://api.phrase.com/v2/accounts/<ACCOUNT_ID>/distributions/<DISTRIBUTION_ID>/releases/<RELEASE_ID>/publish \
     --header 'Authorization: Bearer <JWT_TOKEN>'
   ```

4. How the fix reaches the app. Once a release is published, the SDK fetches the updated translations from the content delivery network at runtime and applies them. If the network is slow or down, the SDK falls back to its cached files, then to the copy bundled in the app, so a fetch failure never leaves the screen blank.

## 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="403: forbidden">
  The account plan does not include Over-the-Air, the token lacks the write scope, or you lack permission. Enable OTA or use a write-scoped token.
</Card>

<Card title="404: not found">
  The account, distribution, or release ID does not resolve. Check each ID in the path and that the token can access it.
</Card>

<Card title="422: invalid field">
  A field failed validation, often an `app_min_version` or `app_max_version` that is not valid semantic versioning. The `errors` array names it, so fix it and retry.
</Card>

<Card title="Publish did nothing">
  A publish that returns 200 but changes nothing means the release was already published. That is a safe no-op; create a new release for the next fix.
</Card>

<Card title="429: rate limited">
  A 429 means too many requests in a short time: wait for the window to reset, then retry. CDN fetches are exempt.
</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 guide calls: [Create a distribution](/en/api/strings/distributions/create-a-distribution), [Create a release](/en/api/strings/releases/create-a-release), and [Publish a release](/en/api/strings/releases/publish-a-release).
2. To publish on a schedule instead of by hand, see [Release triggers](/en/api/strings/release-triggers/create-a-release-trigger).

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