# Machine Translation

Request, stream, cache-clear and batch machine translation suggestions through the Comvi REST API.

Canonical: https://comvi.io/docs/api/machine-translation

Comvi returns translation suggestions from several providers: one key at a time, as a stream, or as a batch job over many keys.

Machine translation spends the organization's credits, so it stays with a signed-in person. Every endpoint needs `machine_translation.use`; the configured-providers read needs `machine_translation.view`. No scope grants either, so a key built from the six scopes gets `403 FORBIDDEN` on every call; only a key created over the API with the raw permission string `machine_translation.use` passes. Language restrictions apply to the source and the target locale separately.

Common errors: `401 AUTHENTICATION_REQUIRED`, `403 FORBIDDEN` (missing permission for the source or target locale, or an API key), `403 READ_ONLY_MODE` on writes. See [Errors](/docs/api/errors).

## Providers

| Provider | ID | Type | Glossary terms in the prompt |
|----------|----|------|------------------------------|
| Google Translate | `google` | Traditional | No |
| DeepL | `deepl` | Traditional | No |
| Amazon Translate | `aws` | Traditional | No |
| Azure Translator | `azure` | Traditional | No |
| OpenAI | `openai` | AI | Yes |
| Anthropic Claude | `anthropic` | AI | Yes |

Glossary terms, no-translate terms, translation memory matches, the key description and the project's AI instructions are assembled into a prompt for the AI providers only. Traditional providers receive the source text and nothing else. Availability depends on the keys the instance administrator has configured.

ICU plural and select messages, including a select wrapping a plural, go through the same suggestions endpoint as plain messages: send the whole message as `sourceText`. An AI provider translates the whole pattern and rebuilds it with the target language's plural categories, so an English `one`/`other` message can come back with the Ukrainian `one`/`few`/`many`/`other` set. A traditional provider translates each form on its own and keeps the source's categories.

### `GET /v1/projects/{projectId}/mt/configured-providers`

Which providers are available on the server. `machine_translation.view`.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `projectId` | path | `integer` | Yes | Project ID |

```bash
curl -b cookies.txt https://api.comvi.io/v1/projects/1/mt/configured-providers
```

```json
{ "providers": { "google": true, "deepl": true, "aws": false, "azure": false, "openai": true, "anthropic": true } }
```

### `POST /v1/projects/{projectId}/mt/suggestions`

Ask each configured provider for a suggestion for one key. Rate limit: 60 requests per minute per caller per project. Suggestions are cached; editing a glossary, the translation memory or the AI instructions does not invalidate the cache, so clear it with `DELETE /v1/projects/{projectId}/mt/suggestions`.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `projectId` | path | `integer` | Yes | Project ID |
| `key` | body | `string` | Yes | Key name, 1 to 512 characters |
| `namespace` | body | `string` | No | Default `default` |
| `sourceLocale` | body | `string` | Yes | Source language code |
| `targetLocale` | body | `string` | Yes | Target language code. Must differ from `sourceLocale` |
| `sourceText` | body | `string` | No | Text to translate; resolved from the key if omitted |
| `limit` | body | `integer` | No | Providers to ask, 1 to 10, default 5. With six providers configured, pass 10 to ask them all |

```bash
curl -X POST -b cookies.txt -H "Content-Type: application/json" \
-d '{"key": "welcome.message", "sourceLocale": "en", "targetLocale": "uk", "limit": 3}' \
https://api.comvi.io/v1/projects/1/mt/suggestions
```

```json
[
{ "providerId": "google", "providerLabel": "Google Translate", "text": "Ласкаво просимо до нашого додатку!" },
{ "providerId": "openai", "providerLabel": "OpenAI", "text": "Вітаємо у нашому застосунку!" }
]
```

| Status and code | When |
|-----------------|------|
| `400 INVALID_INPUT` | `sourceLocale` and `targetLocale` are the same |
| `403 LIMIT_EXCEEDED` | The organization has used all its MT credits |
| `422 FIELD_REQUIRED` | `key`, `sourceLocale` or `targetLocale` is missing |
| `422 VALIDATION_ERROR` | `limit` is outside 1 to 10, or a field has the wrong type |
| `429 TOO_MANY_REQUESTS` | More than 60 requests in a minute |

### `DELETE /v1/projects/{projectId}/mt/suggestions`

Delete the cached suggestions for one key and locale pair, for every provider. Idempotent: an unknown key or an empty cache returns `{ "deleted": 0 }`, not `404`. Rate limit: 30 requests per minute per caller per project.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `projectId` | path | `integer` | Yes | Project ID |
| `key` | body | `string` | Yes | Key name, 1 to 512 characters |
| `namespace` | body | `string` | No | Default `default` |
| `sourceLocale` | body | `string` | Yes | Source language code |
| `targetLocale` | body | `string` | Yes | Target language code |

```bash
curl -X DELETE -b cookies.txt -H "Content-Type: application/json" \
-d '{"key": "welcome.message", "sourceLocale": "en", "targetLocale": "uk"}' \
https://api.comvi.io/v1/projects/1/mt/suggestions
```

```json
{ "deleted": 3 }
```

| Status and code | When |
|-----------------|------|
| `400 INVALID_INPUT` | `sourceLocale` and `targetLocale` are the same |
| `422 FIELD_REQUIRED` | `key`, `sourceLocale` or `targetLocale` is missing |
| `422 FIELD_TOO_LONG` | `key` over 512 characters, or a locale code over 50 |
| `422 FIELD_TOO_SHORT` | `key` or a locale code is empty |
| `429 TOO_MANY_REQUESTS` | More than 30 requests in a minute |

### `GET /v1/projects/{projectId}/mt/suggestions/stream`

The same suggestions as Server-Sent Events, for `EventSource`. This variant waits for every provider and then writes all `suggestion` events at once, followed by `done`. Every 15 seconds the server writes an SSE comment, `: ping <milliseconds>`, which `EventSource` ignores. A failure mid-stream arrives as an `error` event carrying `{ "message": "..." }` and the stream closes; it is not an HTTP status, because the headers were already sent.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `projectId` | path | `integer` | Yes | Project ID |
| `key` | query | `string` | Yes | Key name |
| `sourceLocale` | query | `string` | Yes | Source language code |
| `targetLocale` | query | `string` | Yes | Target language code |
| `namespace` | query | `string` | No | Namespace |
| `sourceText` | query | `string` | No | Text to translate |
| `limit` | query | `integer` | No | 1 to 10, default 5 |

```javascript
const url = new URL('https://api.comvi.io/v1/projects/1/mt/suggestions/stream');
url.searchParams.set('key', 'welcome.message');
url.searchParams.set('sourceLocale', 'en');
url.searchParams.set('targetLocale', 'uk');
const source = new EventSource(url, { withCredentials: true });
source.addEventListener('suggestion', (e) => console.log(JSON.parse(e.data).text));
source.addEventListener('error', (e) => { if (e.data) console.error(JSON.parse(e.data).message); source.close(); });
source.addEventListener('done', () => source.close());
```

### `POST /v1/projects/{projectId}/mt/suggestions/stream`

The same suggestions as newline-delimited JSON, delivered as they land. The body is the same as `POST /v1/projects/{projectId}/mt/suggestions`. Every 15 seconds the server writes a bare newline as a keep-alive, so skip blank lines before parsing.

```text
{"event":"suggestion","data":{"providerId":"google","providerLabel":"Google Translate","text":"Ласкаво просимо!"}}
{"event":"suggestion","data":{"providerId":"deepl","providerLabel":"DeepL","text":"Ласкаво просимо!"}}
{"event":"done","data":{}}
```

### `POST /v1/projects/{projectId}/keys/batch/mt`

Translate many keys into one or more target locales. One job is queued per target locale, each attempted up to 3 times with exponential backoff starting at 10 seconds.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `projectId` | path | `integer` | Yes | Project ID |
| `keyIds` | body | `integer[]` | Yes | At least one |
| `sourceLocaleCode` | body | `string` | Yes | Source language code |
| `targetLocaleCodes` | body | `string[]` | Yes | At least one. No duplicates, none equal to the source |
| `overwriteExisting` | body | `boolean` | No | Default `false` |

```bash
curl -X POST -b cookies.txt -H "Content-Type: application/json" \
-d '{"keyIds": [42, 43, 44, 45], "sourceLocaleCode": "en", "targetLocaleCodes": ["uk", "de"]}' \
https://api.comvi.io/v1/projects/1/keys/batch/mt
```

Returns `202 Accepted`. There is no top-level `jobId`; poll each entry of `jobs[]`. `totalKeys` counts the keys sent, not keys times locales.

```json
{
"jobs": [{ "jobId": "1042", "targetLocaleCode": "uk" }, { "jobId": "1043", "targetLocaleCode": "de" }],
"status": "queued", "totalKeys": 4,
"message": "2 batch MT job(s) queued. Subscribe to SSE events for project 1 to receive progress updates."
}
```

| Status and code | When |
|-----------------|------|
| `400 INVALID_INPUT` | `targetLocaleCodes` has duplicates, or one equals `sourceLocaleCode` |
| `403 LIMIT_EXCEEDED` | The organization has used all its MT credits |
| `422 FIELD_REQUIRED` | `keyIds`, `sourceLocaleCode` or `targetLocaleCodes` is missing |
| `422 VALIDATION_ERROR` | `keyIds` or `targetLocaleCodes` is empty |

### `GET /v1/projects/{projectId}/keys/batch/mt/{jobId}/status`

Poll one batch job. `result` is present only when `status` is `completed`; a failed job carries `error` instead.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `projectId` | path | `integer` | Yes | Project ID |
| `jobId` | path | `string` | Yes | A `jobId` from `jobs[]` |

```bash
curl -b cookies.txt https://api.comvi.io/v1/projects/1/keys/batch/mt/1042/status
```

```json
{ "jobId": "1042", "status": "completed", "progress": 100, "result": { "processed": 4, "failed": 0, "duration": 3200.5, "errors": [] } }
```

| `status` | Meaning |
|----------|---------|
| `waiting` | In the queue |
| `active` | Being processed |
| `completed` | Finished |
| `failed` | Failed |

| Status and body | When |
|-----------------|------|
| `404 { "error": "Job not found" }` | Unknown job, or one from another project. Not the standard envelope |

### `POST /v1/projects/{projectId}/keys/batch/mt/{jobId}/cancel`

Cancel a queued job. A job that has started cannot be stopped: the response is `200` with `success: false` and a message saying it is active, completed or failed. Check `success`, not the status code.

```bash
curl -X POST -b cookies.txt https://api.comvi.io/v1/projects/1/keys/batch/mt/1042/cancel
```

```json
{ "success": true, "message": "Job cancelled successfully" }
```

| Status and body | When |
|-----------------|------|
| `404 { "error": "Job not found" }` | Unknown job |

## Next steps

- [Translations](/docs/api/translations) to apply suggestions and follow `batch_mt.*` events
- [Projects](/docs/api/projects) to configure providers per project
- [Glossaries](/docs/api/glossaries) for terminology in the prompt
