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

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

## 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 doesn't allow larger batches, it just 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" />
