Skip to content

Translations

View .md

A translation key belongs to a project and a namespace and holds one value per locale.

Every endpoint accepts a session cookie or an API key, except the /v1/keys/ routes, which take a key only. Reads need translations:read, writes translations:write, deletes translations:delete. The two status endpoints also need translations:review when the new status is translated; every other endpoint writes "status": "translated" with translations:write alone.

Common errors: 401 AUTHENTICATION_REQUIRED, 403 FORBIDDEN (no permission, or no access to the locale), 403 READ_ONLY_MODE on writes, 409 TRANSACTION_CONFLICT on writes (a deadlock, retry), 422 FIELD_REQUIRED, FIELD_TOO_LONG and VALIDATION_ERROR with meta.field and no message. See Errors.

{
"id": 42, "key": "welcome.message", "namespace": "default", "namespaceId": 1,
"description": "Main welcome message on the homepage", "isPlural": false, "characterLimit": null,
"translations": { "en": { "id": 101, "value": "Welcome to our app!", "status": "translated", "createdAt": "2025-01-15T10:30:00.000Z", "updatedAt": "2025-01-15T10:30:00.000Z", "createdBy": 1 } },
"createdAt": "2025-01-15T10:30:00.000Z", "updatedAt": "2025-01-15T11:00:00.000Z"
}

Statuses are translated, not_reviewed and not_translated.

List keys with cursor pagination and filters. The envelope is { data, pagination }; keep calling with cursor: nextCursor while pagination.hasMore is true. totalItems is never filled in.

Parameter In Type Required Meaning
projectId path integer Yes Project ID
cursor query string No nextCursor from the previous response
limit query integer No 1 to 100, default 50
search query string No Case-insensitive substring on the key name or any value
namespaceIds query integer[] No Filter by namespace
statuses query string[] No Filter by status
localeCodes query string[] No Filter by locale
sortBy query string No key, updatedAt, createdAt
sortOrder query string No asc, desc
includeContext query boolean No Inline each key’s collected context, in the shape of GET …/keys/{keyId}/context
Terminal window
curl -H "Authorization: Bearer $COMVI_API_KEY" "https://api.comvi.io/v1/projects/1/keys?limit=20&search=welcome&sortBy=key"
{ "data": [{ "id": 42, "key": "welcome.message", "…": "…" }], "pagination": { "nextCursor": "eyJpZCI6NDN9", "hasMore": true } }
Status and code When
400 INVALID_INPUT cursor is not one this endpoint issued
422 VALIDATION_ERROR limit outside 1 to 100, or sortBy/sortOrder not in its enum

Read one key with all its values. The project comes from the API key.

Path: namespace (name) and key (name, URL-encoded), both required.

Terminal window
curl -H "Authorization: Bearer $COMVI_API_KEY" https://api.comvi.io/v1/keys/default/welcome.message

Returns the key object.

Status and code When
401 UNAUTHORIZED A session cookie was used. Message: API key authentication required
403 FORBIDDEN The key lacks translations:read
404 { "message": "…" } No key by that name in that namespace. No code

Create a key, or merge new translations into an existing one. The project comes from the API key. Idempotent: the same body twice leaves the same result. A namespace that does not exist is created when the key holds translations:write, so a push from a fresh checkout needs no setup. Creating needs translations:write; updating an existing key also asserts translation.view, from translations:read, so a key with translations:write alone creates on the first push and gets 403 FORBIDDEN on the second. Give a pushing key both.

Body: the fields of POST /v1/projects/{projectId}/keys.

Terminal window
curl -X PUT -H "Authorization: Bearer $COMVI_API_KEY" -H "Content-Type: application/json" \
-d '{"key": "welcome.message", "namespace": "marketing", "translations": {"en": {"value": "Welcome to our app!", "status": "translated"}}}' \
https://api.comvi.io/v1/keys/

Returns 201 Created when created, 200 OK when updated, with the key object.

Status and code When
400 { "message": "…" } The namespace could not be created, or the project has no default namespace. No code
401 UNAUTHORIZED A session cookie was used
403 FORBIDDEN Missing translations:write, or translations:read when the key exists
403 LIMIT_EXCEEDED The plan’s key limit. meta carries metric, current, limit
404 NAMESPACE_NOT_FOUND namespaceId names no namespace
422 FIELD_REQUIRED key is missing
422 FIELD_TOO_LONG key, namespace, description or aiContext over its limit

Soft-delete a key by namespace and name. API key with translations:delete; translations:write is not enough.

Terminal window
curl -X DELETE -H "Authorization: Bearer $COMVI_API_KEY" https://api.comvi.io/v1/keys/marketing/welcome.message

Returns 204 No Content.

Status and code When
401 UNAUTHORIZED A session cookie was used
403 FORBIDDEN The key lacks translations:delete
404 { "message": "…" } No key by that name in that namespace. No code

Create a key, optionally with initial values.

Parameter In Type Required Meaning
projectId path integer Yes Project ID
key body string Yes 1 to 768 characters
namespace body string No Namespace name, 1 to 255 characters, default default
namespaceId body integer No Namespace ID, alternative to namespace
description body string No Context for translators, up to 5000 characters
aiContext body string No Context for AI providers, up to 2000 characters
isPlural body boolean No ICU plural key
characterLimit body integer No 1 or greater
translations body object No Per locale code: { "value": string, "status": string }, both required. value up to 65,536 characters
Terminal window
curl -X POST -H "Authorization: Bearer $COMVI_API_KEY" -H "Content-Type: application/json" \
-d '{"key": "welcome.message", "namespace": "default", "translations": {"en": {"value": "Welcome to our app!", "status": "translated"}}}' \
https://api.comvi.io/v1/projects/1/keys

Returns 201 Created with the key object.

Status and code When
400 TRANSLATION_KEY_ALREADY_EXISTS The name exists in the namespace. A 409 here is a retryable transaction conflict, so read code before retrying
403 LIMIT_EXCEEDED The plan’s key limit
422 FIELD_TOO_LONG key, namespace, description or aiContext over its limit
422 VALIDATION_ERROR Wrong type, or characterLimit not a positive integer

PATCH /v1/projects/{projectId}/keys/{keyId}

Section titled “PATCH /v1/projects/{projectId}/keys/{keyId}”

Update a key’s metadata. Values go through PUT …/keys/{key}.

Parameter In Type Required Meaning
projectId path integer Yes Project ID
keyId path integer Yes Key ID
key body string No 1 to 768 characters
namespaceId body integer No Move to another namespace
description body string | null No Up to 5000 characters
aiContext body string | null No Up to 2000 characters
isPlural body boolean No ICU plural key
characterLimit body integer | null No 1 or greater
Terminal window
curl -X PATCH -H "Authorization: Bearer $COMVI_API_KEY" -H "Content-Type: application/json" \
-d '{"description": "Updated welcome message", "characterLimit": 100}' https://api.comvi.io/v1/projects/1/keys/42

Returns the updated key object.

Status and code When
400 VALIDATION_ERROR namespaceId is in another project, or characterLimit is shorter than an existing value; then meta carries limit, maxExistingLength, keyId
404 { "message": "Key not found" } No such key. No code
409 VALIDATION_ERROR The new name exists in the target namespace
422 FIELD_TOO_LONG key, description or aiContext over its limit
422 VALIDATION_ERROR Wrong type or out of range

Create or overwrite the value of one key in one locale.

Parameter In Type Required Meaning
projectId path integer Yes Project ID
key path string Yes Key name, URL-encoded
namespace query string No Default default
locale body string Yes Locale code
value body string Yes Translation text, up to 65,536 characters
status body string No Status
Terminal window
curl -X PUT -H "Authorization: Bearer $COMVI_API_KEY" -H "Content-Type: application/json" \
-d '{"locale": "uk", "value": "Ласкаво просимо!", "status": "translated"}' \
"https://api.comvi.io/v1/projects/1/keys/welcome.message?namespace=default"
{ "id": 102, "value": "Ласкаво просимо!", "status": "translated", "createdAt": "2025-01-15T11:00:00.000Z", "updatedAt": "2025-01-16T09:15:00.000Z", "createdBy": 1 }
Status and code When
400 LOCALE_NOT_ENABLED locale is not on the project
400 VALIDATION_ERROR value is longer than the key’s characterLimit; meta carries limit and actual
404 TRANSLATION_KEY_NOT_FOUND No such key
422 FIELD_REQUIRED locale or value is missing
422 VALIDATION_ERROR status not in the enum

DELETE /v1/projects/{projectId}/keys/{keyId}

Section titled “DELETE /v1/projects/{projectId}/keys/{keyId}”

Soft-delete a key and all its values.

Terminal window
curl -X DELETE -H "Authorization: Bearer $COMVI_API_KEY" https://api.comvi.io/v1/projects/1/keys/42

Returns 204 No Content.

Status and code When
404 { "message": "Translation key not found" } No such key. No code

PUT /v1/projects/{projectId}/translation/{translationId}/status

Section titled “PUT /v1/projects/{projectId}/translation/{translationId}/status”

Change the review status of one value. Every status needs translations:write (translation.edit for the locale); translated also needs translations:review. A key with translations:review alone fails the write check first, with 403 FORBIDDEN and meta.requiredPermission set to translation.edit.

Parameter In Type Required Meaning
projectId path integer Yes Project ID
translationId path integer Yes Translation value ID
status body string Yes New status
Terminal window
curl -X PUT -H "Authorization: Bearer $COMVI_API_KEY" -H "Content-Type: application/json" \
-d '{"status": "translated"}' https://api.comvi.io/v1/projects/1/translation/102/status

Returns 204 No Content. Read reviewedBy and reviewedAt back from the key list.

Status and code When
404 TRANSLATION_KEY_NOT_FOUND No such translation, or it belongs to another project
422 FIELD_REQUIRED status is missing
422 VALIDATION_ERROR status not one of the three values

The IDs of every key matching a filter, without pagination. Query: search, namespaceIds, statuses, localeCodes, all optional, as on the key list.

Terminal window
curl -H "Authorization: Bearer $COMVI_API_KEY" "https://api.comvi.io/v1/projects/1/keys/ids?search=welcome"
{ "ids": [42, 43, 87], "count": 3 }

GET /v1/projects/{projectId}/keys/{keyId}/context

Section titled “GET /v1/projects/{projectId}/keys/{keyId}/context”

The UI context collected for one key: the element it renders in, its role, the space available, and its neighbours. Machine translation reads the same signal. Read-only; context is collected from running apps.

Terminal window
curl -H "Authorization: Bearer $COMVI_API_KEY" https://api.comvi.io/v1/projects/1/keys/42/context
{
"observed": true, "hasSignal": true,
"context": { "uiType": "button", "translationRole": "action-label", "lengthHint": "short", "neighbors": [{ "role": "sibling", "namespace": "default", "key": "checkout.cancel", "text": "Cancel" }], "screenGroups": ["checkout"], "lastSeenAt": "2026-03-20T09:00:00.000Z", "isStale": false }
}

observed: false means the key was never seen rendering in a running app. observed: true with hasSignal: false means it was seen but nothing useful resolved. Only when both are true is context present.

Context field Type Meaning
uiType string Kind of element
translationRole string Role in that element
lengthHint string | null Room available
neighbors object[] Nearby strings, { role, namespace, key, text }
screenGroups string[] Screens the key was seen on
lastSeenAt string | null Last observation
isStale boolean The observation may be outdated. Read it before trusting the rest
raw unknown The unresolved profile, present on some responses
Status and code When
404 { "message": "…" } No such key, or another project’s. No code

GET /v1/projects/{projectId}/keys/tm/search

Section titled “GET /v1/projects/{projectId}/keys/tm/search”

Find similar source text already translated in this project. It searches the project’s own values; the organization TMs are searched with POST /v1/projects/{projectId}/tm/search. minSimilarity here is a 0 to 1 fraction, while similarity in the response is a 0 to 100 integer.

Parameter In Type Required Meaning
projectId path integer Yes Project ID
sourceText query string Yes Text to match
targetLocale query string Yes Locale wanted, 1 to 50 characters
limit query integer No 1 to 20
minSimilarity query number No 0 to 1
excludeKeyId query integer No Leave this key out
excludeKey query string No Leave this key name out
excludeNamespace query string No Narrows excludeKey to one namespace
Terminal window
curl -H "Authorization: Bearer $COMVI_API_KEY" "https://api.comvi.io/v1/projects/1/keys/tm/search?sourceText=Welcome%20to%20our%20app&targetLocale=uk&excludeKeyId=42"
{ "matches": [{ "keyId": 87, "key": "home.greeting", "sourceText": "Welcome to our app", "targetText": "Ласкаво просимо до нашого застосунку", "similarity": 96 }] }
Status and code When
422 FIELD_REQUIRED sourceText or targetLocale is missing
422 VALIDATION_ERROR limit or minSimilarity out of range

A Server-Sent Events stream of changes in a project. Events are filtered by your language restrictions. Open streams are capped per project and per user; over the cap the request fails with 429 RATE_LIMIT_EXCEEDED. A named heartbeat event with a timestamp is written every 15 seconds, so EventSource delivers it and a dropped connection can be detected.

const source = new EventSource('https://api.comvi.io/v1/projects/1/keys/events', { withCredentials: true });
source.addEventListener('translation.upserted', (e) => console.log(JSON.parse(e.data)));
source.addEventListener('heartbeat', () => { /* reset a watchdog */ });

Every payload carries projectId, plus:

Event Fires when Payload
translation.upserted A value was written keyId, translationId, key, locale, namespace, value (may be null), status
key.created A key was created keyId, key, namespace, isPlural
key.updated Metadata changed keyId, key, namespace, changes with boolean description, aiContext, characterLimit, isPlural
key.deleted A key was deleted keyId, key, namespace
namespace.created A namespace was created, including by a push namespaceId, namespace
schema.updated A key’s generated type surface changed keyId, key, namespace, params (each { name, type, options?, required })
project.locale.updated A language was added or removed locale, action (added or removed). Spelled with dots; the webhook enum uses project.locale_added and project.locale_removed
batch_mt.scheduled Batch MT jobs were queued sourceLocale, jobs (each { jobId, targetLocale, keyCount }), totalKeys
batch_mt.started A job began jobId, totalKeys, sourceLocale, targetLocale
batch_mt.progress A job advanced jobId, processed, total, currentKey
batch_mt.completed A job finished jobId, processed, failed, failedKeyIds, errors (each { keyId, key, message, type }), duration. type is one of no_source, no_translation, insufficient_credits, provider_error, unknown
batch_mt.failed A job failed outright jobId, error
cdn_deploy.started A CDN deploy began nothing further
cdn_deploy.completed A CDN deploy finished filesUploaded, filesFailed, duration, warningCount, warnings (each { file, key, message })
cdn_deploy.failed A CDN deploy failed error
Status and code When
429 RATE_LIMIT_EXCEEDED Too many open streams for the project or user

An API key gets the same stream at GET /v1/keys/events/stream, for the key’s project and with translations:read: only translation.upserted, key.created, key.updated, key.deleted, namespace.created and schema.updated are sent, in the locales the key’s creator may see, and ?events=key.created,key.deleted (up to 32 names) narrows them. The stream opens with a connected event carrying { projectId, events }, then the same heartbeat events; streams are capped at 50 per project and 10 per key creator.

Five endpoints under /v1/projects/{projectId}/keys/batch/ act on many keys at once. They are not atomic: items that succeeded are not rolled back when others fail, so read errors. Each returns { success, processed, failed, duration?, errors: [{ keyId, key, message }] }, duration in milliseconds.

Terminal window
curl -X POST -H "Authorization: Bearer $COMVI_API_KEY" -H "Content-Type: application/json" \
-d '{"keyIds": [42, 43, 44]}' https://api.comvi.io/v1/projects/1/keys/batch/delete
{ "success": true, "processed": 3, "failed": 0, "errors": [] }

Every body carries keyIds (integer[], required, 1 to 10,000 IDs) plus the fields listed per endpoint.

POST /v1/projects/{projectId}/keys/batch/delete

Section titled “POST /v1/projects/{projectId}/keys/batch/delete”

Delete many keys. No further fields.

POST /v1/projects/{projectId}/keys/batch/status

Section titled “POST /v1/projects/{projectId}/keys/batch/status”

Set the status of many keys in one locale. Body: localeCode (string, required) and status (string, required). translated also needs translations:review, as on the single-value endpoint.

POST /v1/projects/{projectId}/keys/batch/namespace

Section titled “POST /v1/projects/{projectId}/keys/batch/namespace”

Move many keys. Body: targetNamespaceId (integer, required).

POST /v1/projects/{projectId}/keys/batch/copy

Section titled “POST /v1/projects/{projectId}/keys/batch/copy”

Copy values from one locale to another. Body: fromLocaleCode (string, required), toLocaleCode (string, required), overwriteExisting (boolean, default false).

POST /v1/projects/{projectId}/keys/batch/clear

Section titled “POST /v1/projects/{projectId}/keys/batch/clear”

Remove values in the given locales. Body: localeCodes (string[], required).

Status and code When
422 FIELD_REQUIRED keyIds, or the locale field the operation needs, is missing
422 VALIDATION_ERROR keyIds is empty or over 10,000, or a status is not in the enum