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

# Connect an engine with Bring your own (BYO) Lite (BETA)

> Connect an engine that has its own API to Phrase Language AI with a JSON template, with no adapter to build or host.

> [!NOTE]
>
> BYO Lite is in beta and enabled per organization. Details can change before general availability.

If your engine already has its own HTTP API, for example an OpenAI-compatible one, you don't need to build and host an adapter. With BYO Lite you give Phrase a JSON **template** that describes how to call your engine's API directly and how to read its response. Phrase runs the template on its side, with the same authentication and retries as for an adapter. There is nothing to develop, deploy or keep running.

BYO Lite is meant for engines whose API maps easily to the [Phrase BYO schema](https://developers.phrase.com/en/guides/byo-engine). The full adapter is for heavier logic, such as custom processing, several upstream calls per request or an API that a template can't describe.

| | Full BYO adapter | BYO Lite |
| - | - | - |
| What you provide | A hosted adapter that implements the [BYO Engine API](https://developers.phrase.com/en/guides/byo-engine) | A JSON template, plus the URL and credentials of your engine's own API |
| What Phrase calls | Your adapter, which then calls your engine | Your engine's own API |
| Good fit when | You need custom logic, or your engine can't be described by a template | Your engine's API is already usable as it is |

## Prerequisites

* BYO Lite is enabled for your organization. It is in beta.
* An engine with its own HTTP API, and its URL and credentials, such as an API key.
* For a custom template, a JSON template that you wrote, or one you adapted from the [pre-built template](#pre-built-template).

## Quickstart

To connect an OpenAI-compatible service with the pre-built template:

1. In the engine settings, set **Engine API** to **Pre-built template**.
2. Enter the engine URL, for example `https://api.openai.com`, and your credentials.
3. Optionally add metadata (Custom labels) such as `model`, see [Metadata in a template](#metadata-in-a-template).
4. Save the engine.

## Ways to connect

In the engine settings, **Engine API** offers three options:

| Option | Mode | What you provide |
| - | - | - |
| **Phrase BYO API adapter** | Full | A hosted adapter that implements the [BYO Engine API](https://developers.phrase.com/en/guides/byo-engine). |
| **Pre-built template** | Lite | The URL and credentials of your engine. Phrase maintains the template, see [Pre-built template](#pre-built-template). |
| **Custom template** | Lite | The URL and credentials of your engine, and your own template as a JSON file. You can upload it or paste it into the editor. |

Phrase validates the template before the engine is saved.

## Template structure

A template is a single JSON file of up to 8 KB. Keys that are not listed here are rejected. A template needs only three blocks:

```json theme={null}
{
  "type": "my-engine/v1",
  "translate": {
    "path": "/v1/translate",
    "request": "{ \"source\": $source, \"target\": $target, \"texts\": [$segments.text] }",
    "response": "[translations.text]"
  },
  "languages": {
    "mode": "static",
    "codes": ["en", "de", "fr", "es"]
  }
}
```

| Block | Required | Purpose and behavior when missing |
| - | - | - |
| `type` | Yes | A free-text identifier for the template. |
| `translate` | Yes | How to translate a batch of segments: `request`, `response`, and optionally `path` (default `/translate`), `method` (default `POST`) and `maxSegments`. |
| `languages` | Yes | Which language pairs the engine supports, see [Supported languages in a template](#supported-languages-in-a-template). |
| `status` | No | A health check. Without it, the engine is always reported as healthy, and the validation shows a warning. |
| `error` | No | How to read an error message, and optionally an error code, from the engine's response. When a code is set, it is shown in front of the message. Without this block, the raw response body is used as the message, and the validation shows a warning. |
| `languageMap` | No | Maps Phrase locale codes to the codes your engine uses. |
| `headers` | No | Static HTTP headers sent with every call, for example a fixed API version. |
| `translateAsync`, `translateAsyncStatus`, `translateAsyncResult` | No | An asynchronous flow, for engines that translate in the background. Define all three or none. Without them, asynchronous requests from Phrase are translated with the `translate` block. Most engines don't need this. |

Each `request` and `response` value is a [JSONata](https://jsonata.org/) expression. A `request` expression builds the request body of your engine from the [variables](#variables) Phrase provides. A `response` expression reads the translated text out of the engine's response. For `translate`, it must return one translated string per segment, in the same order. Wrap a single value in `[...]` so that one segment still produces a list.

`maxSegments` is the highest number of segments your engine accepts in one call. If Phrase has more, it splits them into several calls and joins the results in the original order, so the template only ever describes one call.

Every `path` in a template (`translate`, `status`, `languages` and the asynchronous blocks) is added to the engine URL you enter in the engine settings. With the engine URL `https://api.example.com` and the path `/v1/translate`, Phrase calls `https://api.example.com/v1/translate`. If the engine URL already ends with a path, such as `https://gateway.example.com/openai`, the template path is added after it. Repeated slashes are merged, so it doesn't matter whether either side has a slash at the junction.

The following example shows every block:

```json theme={null}
{
  "type": "my-engine/v1",
  "languageMap": { "pt-br": "PT-BR", "zh-hans": "ZH" },
  "headers": { "X-Api-Version": "2" },
  "status": {
    "path": "/v1/health",
    "method": "GET",
    "okWhen": "status = 'ok'"
  },
  "languages": {
    "mode": "endpoint",
    "path": "/v1/languages",
    "method": "GET",
    "crossProduct": true,
    "response": "languages.code"
  },
  "translate": {
    "path": "/v1/translate",
    "method": "POST",
    "maxSegments": 50,
    "request": "{ \"source\": $langOut($source), \"target\": $langOut($target), \"texts\": [$segments.text] }",
    "response": "[translations.text]"
  },
  "translateAsync": {
    "path": "/v1/jobs",
    "method": "POST",
    "request": "{ \"source\": $langOut($source), \"target\": $langOut($target), \"texts\": [$segments.text] }",
    "response": "id"
  },
  "translateAsyncStatus": {
    "path": "/v1/jobs/{jobId}",
    "method": "GET",
    "doneWhen": "state = 'done'",
    "failedWhen": "state = 'failed'",
    "detail": "message"
  },
  "translateAsyncResult": {
    "path": "/v1/jobs/{jobId}/result",
    "method": "GET",
    "response": "[translations.text]"
  },
  "error": {
    "message": "error.message",
    "code": "error.code"
  }
}
```

* In `translateAsyncStatus` and `translateAsyncResult`, `{jobId}` in the path is replaced with the job ID returned by `translateAsync`. The `doneWhen` and `failedWhen` expressions are evaluated against the status response. While neither matches, Phrase keeps polling. `detail` is evaluated only when `failedWhen` matches, and becomes the failure message. Both blocks can also have a `request`, which is optional. Without it, the call has no body, so a `GET` poll doesn't need one.
* `headers` accepts up to 10 headers with values of up to 256 characters. `Authorization`, `Host`, `Content-Type`, `Content-Length`, `Cookie` and `X-Phrase-Action-Id` can't be set, because Phrase manages them.
* The `$eval` and `$error` functions are not allowed.

## Supported languages in a template

The `languages` block has two modes.

**`static`** lists the languages in the template. Use either `pairs`, a list of `{ "sourceLanguage", "targetLanguage" }` objects, or `codes`, a flat list of language codes. Phrase combines the codes into all pairs, leaving out same-to-same pairs. You can also add `codesFrom`, a JSONata expression evaluated against the [metadata](#metadata-in-a-template). When it returns at least one valid language code, or a comma-separated string of codes, those codes replace `pairs` or `codes`. This lets you restrict the languages per engine without editing the template.

```json theme={null}
"languages": {
  "mode": "static",
  "codes": ["en", "de", "fr", "es"],
  "codesFrom": "$meta.languages ? $split($meta.languages, \",\").$trim()"
}
```

Use `pairs` when your engine doesn't support every combination, for example when it translates only from English, or only into some languages. Phrase offers exactly the pairs you list, in that direction only:

```json theme={null}
"languages": {
  "mode": "static",
  "pairs": [
    { "sourceLanguage": "en", "targetLanguage": "de" },
    { "sourceLanguage": "en", "targetLanguage": "fr" },
    { "sourceLanguage": "de", "targetLanguage": "en" }
  ]
}
```

**`endpoint`** asks your engine. Phrase calls `path` with `method`, and `response` turns the answer into a list of `{ "sourceLanguage", "targetLanguage" }` pairs. If your engine returns only a flat list of codes, set `crossProduct` to `true` and let `response` return that list. Phrase then builds the pairs itself.

Use Phrase locale codes in `pairs`, `codes` and `codesFrom`, for example `pt-br`. If your engine uses other codes, add them to `languageMap` and convert in the expressions with `$langOut(code)` for outgoing codes, and `$langIn(code)` for incoming ones. A code that is not in the map is converted to a plain upper-case (outgoing) or lower-case (incoming) form.

## Variables

These variables are available in `request` expressions:

| Variable | Value |
| - | - |
| `$source`, `$target` | Source and target language, as Phrase locale codes. |
| `$sourceName`, `$targetName` | The same languages as readable names, which is useful in prompts for language models. |
| `$segments` | The segments to translate, as a list of `{ "text": "..." }` objects. |
| `$glossary` | Glossary entries as a list of `{ "term": "...", "translation": "..." }` objects, see below. |
| `$meta` | The [metadata](#metadata-in-a-template) of the engine. |
| `$jobId` | The job ID returned by `translateAsync`. It replaces `{jobId}` in the `path` of `translateAsyncStatus` and `translateAsyncResult`, and is available as `$jobId` in the optional `request` of those two blocks. |

`response`, `doneWhen`, `failedWhen`, `detail`, `okWhen` and `error` expressions are evaluated against the engine's response body and have no other variables.

On top of the [standard JSONata functions](https://docs.jsonata.org/overview), these functions are available: `$langOut` and `$langIn` for [language codes](#supported-languages-in-a-template), and `$fromJson(text)`, which parses JSON out of a text. `$fromJson` is meant for language models, which often wrap their answer in a markdown code block or add a sentence before it. If the text is not pure JSON, it uses the last JSON object it finds.

**Glossary.** For each request, Phrase searches the term bases attached to the job and puts the entries that match the segments of that request into `$glossary`, without duplicates. If your engine supports glossaries, map them in the request. For a language model, list them in the prompt:

```text theme={null}
($count($glossary) > 0 ? "Glossary: " & $join($glossary.(term & " => " & translation), "; ") : "")
```

## Metadata in a template

Metadata is a set of custom key/value pairs that you add to the engine. In the Phrase UI they are called **Custom labels**, and in the [OpenAPI schema](https://developers.phrase.com/public/assets/openapi/phrase-byo-mt.yaml) they are the `metadata` object. A template reads them from `$meta`, for example `$meta.model`. The placeholders from [Dynamic metadata](https://developers.phrase.com/en/guides/byo-engine#dynamic-metadata) are resolved before the template runs. Values you enter as Custom labels are text, so convert them in the template when you need a number. A key that is not set has no value, so check it before you use it.

The pre-built template reads these keys:

| Key | Description |
| - | - |
| `model` | The model to use. |
| `temperature` | The sampling temperature. |
| `custom_instructions` | Extra instructions for the model. |
| `languages` | The languages the engine offers in Phrase. |

**Custom instructions.** Set `custom_instructions` to add your own rules to every translation, such as tone of voice, terminology or style. The text is added to the prompt after the built-in translation rules. It applies to every request of the engine, so use a separate engine for rules that differ between projects. The placeholders from [Dynamic metadata](https://developers.phrase.com/en/guides/byo-engine#dynamic-metadata) work here too, but they insert only an ID, not project-specific text. There is no fixed length limit. The text is sent with every request, so it counts against the context size and cost of your model, and you should keep it short.

```text theme={null}
Use a formal tone. Always address the reader as "Sie". Do not translate the product name "Acme Cloud".
```

**Model.** Set `model` to the name of any model your endpoint offers. Without it, the pre-built template uses `gpt-4.1-mini`. The template sends `temperature` 0 by default. It leaves it out for model names that contain `claude`, because those models reject it. A `temperature` you set always wins.

**Languages.** Set `languages` to a comma-separated list of Phrase locale codes, such as `en,de,fr`, to limit the languages the engine offers. Phrase combines them into all pairs. Without it, the pre-built template offers its built-in list of languages. At most 100 codes are used, and unknown codes are ignored.

## Pre-built template

The pre-built template connects to OpenAI and to other services that implement the OpenAI chat completions API. Set the engine URL to the base URL of the service, for example `https://api.openai.com`. The template:

* calls `POST /v1/chat/completions` with up to 50 segments per request, and checks health with `GET /v1/models`;
* asks the model to translate the segments, to keep inline tags and to return JSON, and adds your glossary and `custom_instructions`;
* reads the translations from the first answer of the model, even when it is wrapped in a markdown code block;
* reads the error message from the `error` object of the response;
* sends a static `anthropic-version` header (`2023-06-01`). If your service doesn't need it, remove the `headers` block from your copy of the template.

You can use it as the starting point for your own: download it, change it, and upload it as a **Custom template**.

> [card] [openai-compatible template](https://developers.phrase.com/public/assets/byo-lite/openai-compatible.json)
>
> Download the template that Phrase uses for OpenAI-compatible engines.

## Troubleshooting

| Message | Cause |
| - | - |
| `Template exceeds the maximum allowed size of 8192 bytes` | The template is larger than 8 KB. |
| `'translate.request' is required` (or `'translate.response'`, `'type'`) | A required key is missing. |
| `The Custom Adapter API returned a different number of translated segments` | The `response` expression returned a different number of strings than the number of segments sent. Check that it returns exactly one string per segment, in order. |
| `Custom Adapter API returned HTTP 4xx` followed by a message | Your engine rejected the request. The message is read with the `error` block, or is the raw response body when the template has none. |

## Next steps

* Read the [BYO Engine guide](https://developers.phrase.com/en/guides/byo-engine) if you need the full adapter.
* Look up the expression syntax in the [JSONata documentation](https://docs.jsonata.org/overview).
* Start from the [pre-built template](#pre-built-template) and adapt it.

*Last updated: October 30, 2026. Track documentation changes in the [changelog](https://developers.phrase.com/en/changelog.md).*


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