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

# Bring your own (BYO) engine

> Connect your own machine translation engine to Phrase Language AI — from implementing the API to running translations.

export const DownloadOpenApiCard = ({url}) => {
  return <Card title="Full API Schema" href={url} target="_blank" horizontal>
      Explore the raw schema to see all endpoints in one place. Perfect for tools and integration setup.
    </Card>;
};

Machine translation powered by [Phrase](https://phrase.com/) picks the best available engine for a given piece of content using AI. BYO (Bring Your Own) Engine lets you plug a custom machine translation engine into that selection by implementing a small, standardized HTTP API. Once your adapter implements the endpoints below, [Phrase Language AI](https://support.phrase.com/hc/en-us/articles/5709660879516-Phrase-Language-AI-TMS) can route translation jobs to it like any other engine.

This guide covers the whole flow: what your adapter needs to expose, how authentication works, every endpoint you need to implement, and how to harden it for production traffic.

## What you'll build

A minimal BYO Engine adapter exposes:

* **Engine status** — lets Phrase check your engine is healthy before routing jobs to it.
* **Supported languages** — tells Phrase which source/target language pairs your engine can translate.
* **Synchronous translation** — translates a batch of segments and returns the result in the same request.
* **Asynchronous translation** — accepts a batch of segments, returns immediately with a job ID, and lets Phrase poll for status and results.

Both translation modes support ad-hoc glossaries and custom request-level metadata. Per-segment metadata is also accepted, but is currently reserved for future use — see [Glossaries and custom metadata](#glossaries-and-custom-metadata).

## What to expect from Phrase

Design your adapter for the traffic Phrase actually sends.

### Segment counts

* **Synchronous calls** (`POST /translate`) from the Phrase editor normally carry 1 segment. Synchronous calls from other sources can carry more than one segment, and the engine validation test sends 500 segments in one synchronous call.
* **Asynchronous calls** (`POST /translateAsync`), used for example by pre-translation and analysis, can carry up to 500 segments.
* Both endpoints accept 1 to 500 segments per request. If Phrase has more segments to translate, it splits them into several requests of at most 500 segments before calling your adapter. Your adapter never has to handle more than 500 segments in one request.

### Engine validation test

When you validate your engine in Phrase, it runs these checks in order: supported languages, engine status, a synchronous request with 500 segments, a synchronous request with 1 segment, and an asynchronous request with 500 segments. For the translation checks, the number of translated segments in the response must match the number sent.

### Retries and timeouts

* Phrase retries a request when your adapter responds with `429`, `502`, `503`, or `504`. The wait between attempts grows with each retry, and a `Retry-After` header is honored when it asks for a longer wait. Retries stop when the time limit for that call runs out, and the call then fails.
* Any other error status fails the call. Phrase does not fall back from synchronous to asynchronous translation.
* Time limits per call:

  | Call | Time limit |
  | - | - |
  | Supported languages | 120 seconds |
  | Engine status | 60 seconds |
  | Synchronous translation | Calls from the Phrase editor must respond within 20 seconds, because the editor stops waiting after that. Calls from the API and Phrase Language AI should respond within about 60 seconds. Any retries happen inside whichever window applies. |
  | Asynchronous translation | The job must finish within 1800 seconds in total. Phrase polls the status every 5 seconds. Retries for each step (submit, each status poll, fetch result) stop after 60 seconds. |

## Prerequisites

* An HTTPS endpoint you control that can implement the [OpenAPI schema](https://developers.phrase.com/public/assets/openapi/phrase-byo-mt.yaml) below.
* Either an OAuth 2.0 client credentials setup or an API token to authenticate incoming requests from Phrase.
* For a working starting point, see the [reference adapter implementation](https://github.com/phrase/custom.adapter), which demonstrates a basic integration and includes additional guidance.

## Authentication

Phrase authenticates to your engine using either of the following — support at least one:

* **OAuth 2.0 client credentials flow.** Phrase requests a token from your token endpoint and sends it as `Authorization: Bearer <token>`. Scopes are granted per operation:

  | Scope | Grants access to |
  | - | - |
  | `languages:read` | `POST /languages` |
  | `status:read` | `POST /status` |
  | `translate:write` | `POST /translate` |
  | `translateAsync:write` | `POST /translateAsync` |
  | `translateAsyncStatus:read` | `GET /translateAsyncStatus/{id}` |
  | `translateAsyncResult:read` | `GET /translateAsyncResult/{id}` |

* **API token.** Phrase sends a static token in the `X-Api-Token` header.

## Dynamic metadata

Phrase automatically resolves the following placeholders in request-level metadata values before sending them to your engine:

| Placeholder | Resolved value |
| - | - |
| `{project_uid}` | TMS project UID |
| `{job_uid}` | TMS job UID |
| `{idm_organization_uid}` | Phrase organization UID |

Use these to pass contextual information about the translation job to your engine without any additional integration work, for example:

```json theme={null}
{
  "formality": "informal",
  "model": "model123",
  "domain": "legal",
  "project_uid": "{project_uid}",
  "job_uid": "{job_uid}",
  "idm_organization_uid": "{idm_organization_uid}"
}
```

## Engine status

`POST /status` — lets Phrase check that your engine is operational before routing work to it.

**Request body** (optional)

| Field | Type | Description |
| - | - | - |
| `metadata` | object | Optional custom key/value pairs. |

**Response**

| Field | Type | Description |
| - | - | - |
| `status` | string | `ok` when fully operational, `not_ok` otherwise (e.g. warming up). |

```json theme={null}
{
  "status": "ok"
}
```

## Supported languages

`POST /languages` — tells Phrase which source/target language pairs your engine can translate.

**Request body** (optional)

| Field | Type | Description |
| - | - | - |
| `metadata` | object | Optional custom key/value pairs. |

**Response**

| Field | Type | Description |
| - | - | - |
| `languagePairs` | array | List of `{ sourceLanguage, targetLanguage }` objects, using [Phrase locale codes](https://developers.phrase.com/public/assets/openapi/phrase-byo-mt.yaml). |

```json theme={null}
{
  "languagePairs": [
    { "sourceLanguage": "en", "targetLanguage": "es" },
    { "sourceLanguage": "en", "targetLanguage": "de" }
  ]
}
```

## Synchronous translation

`POST /translate` — translates a batch of segments and returns the result in the same request. Use this for latency-sensitive requests, where the caller can wait for the result inline.

**Request body**

| Field | Type | Required | Description |
| - | - | - | - |
| `sourceLanguage` | string | Yes | Source locale code. |
| `targetLanguage` | string | Yes | Target locale code. |
| `segments` | array | Yes | 1–500 segments, each `{ idx, text, metadata }`. Only `text` is required per segment; `idx` and `metadata` are optional. |
| `glossary` | array | No | 0–500 `{ term, translation }` entries to apply during translation. Each `term` and `translation` is 1–50 characters. |
| `metadata` | object | No | Custom request-level key/value pairs. |

<Note>When a segment includes `idx`, echo it back unchanged on the corresponding segment in the response — it's Phrase's internal identifier for matching translated segments back to their source, and must not be altered by your adapter.</Note>

```json theme={null}
{
  "sourceLanguage": "en",
  "targetLanguage": "es",
  "segments": [
    { "idx": "1", "text": "Hello World!" }
  ],
  "glossary": [
    { "term": "hello", "translation": "hola" }
  ],
  "metadata": {
    "project_uid": "{project_uid}"
  }
}
```

**Response**

```json theme={null}
{
  "sourceLanguage": "en",
  "targetLanguage": "es",
  "segments": [
    { "idx": "1", "text": "Hello World!", "translatedText": "¡Hola Mundo!" }
  ]
}
```

## Asynchronous translation

Use the asynchronous flow whenever translation may take longer than a single request should reasonably wait for. Both flows accept the same request shape and the same 1–500 segment limit. Async does not allow larger batches; it decouples submission from result retrieval so slower translations don't hold a request open.

<Warning>Implement these endpoints with thread safety and proper concurrency handling — concurrent requests for different jobs must not overwrite each other's data.</Warning>

### 1. Submit the job

`POST /translateAsync` — accepts the same request body as [synchronous translation](#synchronous-translation) (source/target language, segments, optional glossary and metadata) and returns immediately with a job ID.

```json theme={null}
{
  "id": "ffffffff-ffff-ffff-ffff-ffffffffffff"
}
```

### 2. Poll for status

`GET /translateAsyncStatus/{id}` — poll using the job ID until the job is no longer `running`.

| Field | Type | Description |
| - | - | - |
| `status` | string | `running`, `done`, or `failed`. |
| `detail` | string | Present when `status` is `failed`; describes the failure. |

```json theme={null}
{
  "status": "done"
}
```

### 3. Fetch the result

`GET /translateAsyncResult/{id}` — once `status` is `done`, fetch the translated segments. The response has the same shape as the [synchronous translation](#synchronous-translation) response.

## Glossaries and custom metadata

* **Glossaries** — pass a `glossary` array of `{ term, translation }` pairs (0–500 entries, each field 1–50 characters) on either `/translate` or `/translateAsync` to enforce specific terminology during translation.
* **Custom metadata** — pass a `metadata` object at the request level on either endpoint. Combine it with [dynamic metadata](#dynamic-metadata) placeholders to pass job context to your engine without extra integration work. A `metadata` object can also be set per segment, but this field is currently a reserved placeholder for future use — don't rely on your engine receiving it yet.

## Rate limiting and errors

If your engine needs to shed load, respond with `429` and a `Retry-After` header (seconds until the client may retry):

```http theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 30
```

For any other failure, return an error object:

```json theme={null}
{
  "error": "Unauthorized, Invalid token"
}
```

## Stability and performance recommendations

Load coming from Phrase can be bursty, especially with concurrent asynchronous jobs. Before going to production:

* **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 proxy/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.

## Reference

* [Demo implementation](https://github.com/phrase/custom.adapter) — a reference adapter demonstrating a basic integration.
* [OpenAPI schema](https://developers.phrase.com/public/assets/openapi/phrase-byo-mt.yaml) — implement your adapter in accordance with this schema.

<DownloadOpenApiCard url="https://developers.phrase.com/public/assets/openapi/phrase-byo-mt.yaml" />


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.