Translation Memory
A translation memory (TM) stores translated segments for reuse across projects. TMs live at the organization level; a project can be assigned several with different priorities, and one of them receives new translations.
The /v1/organizations/{orgId}/tms and /v1/tms/{tmId}/... endpoints are session-only and need organization.manage_settings. An API key is refused before the permission check: the organization route answers 403 FORBIDDEN with API keys cannot access organization-scoped routes, and every /v1/tms/{tmId}/... route answers 404 with Translation memory not found, so the TM’s existence is not disclosed. Three endpoints are project-scoped: GET …/tm-settings (translation_memory.view) and POST …/tm/search (translation_memory.view and translation.view) accept a key with translations:read; PATCH …/tm-settings needs translation_memory.edit, which no scope grants, so a key gets 403 there.
Common errors: 401 AUTHENTICATION_REQUIRED, 403 FORBIDDEN, 403 READ_ONLY_MODE on writes, 404 NOT_FOUND on /v1/tms/{tmId} routes. The full list is under Errors.
TM object
Section titled “TM object”| Field | Type | Meaning |
|---|---|---|
id, organizationId |
integer |
IDs |
name |
string |
Name |
description |
string | null |
Description |
sourceLocaleId, sourceLocaleCode |
integer, string |
Source language |
isDefault |
boolean |
The organization’s default TM |
isShared |
boolean |
Searched by every project with the same source locale |
autoPopulate |
boolean |
New translations are added automatically |
onlyApproved |
boolean |
Only reviewed translations are stored |
entriesCount, languageCount, linkedProjectsCount |
integer |
Counts |
createdAt, updatedAt |
string |
ISO 8601 timestamps |
GET /v1/organizations/{orgId}/tms
Section titled “GET /v1/organizations/{orgId}/tms”List an organization’s TMs. A cursor this endpoint did not issue is ignored and the first page comes back.
| Parameter | In | Type | Required | Meaning |
|---|---|---|---|---|
orgId |
path | integer |
Yes | Organization ID |
cursor |
query | string |
No | nextCursor from the previous page |
limit |
query | integer |
No | 1 to 100, default 50 |
curl -b cookies.txt https://api.comvi.io/v1/organizations/1/tms{ "tms": [{ "id": 1, "organizationId": 1, "name": "Main TM", "description": null, "sourceLocaleId": 10, "sourceLocaleCode": "en", "isDefault": true, "isShared": true, "autoPopulate": true, "onlyApproved": false, "entriesCount": 12450, "languageCount": 8, "linkedProjectsCount": 3, "createdAt": "2025-01-10T08:00:00Z", "updatedAt": "2025-03-20T14:00:00Z" }], "nextCursor": null, "hasMore": false}POST /v1/organizations/{orgId}/tms
Section titled “POST /v1/organizations/{orgId}/tms”Create a TM.
| Parameter | In | Type | Required | Meaning |
|---|---|---|---|---|
orgId |
path | integer |
Yes | Organization ID |
name |
body | string |
Yes | 1 to 255 characters |
description |
body | string |
No | Up to 2000 characters |
sourceLocaleId |
body | integer |
Yes | Source locale ID |
isShared |
body | boolean |
No | Default false |
onlyApproved |
body | boolean |
No | Default false |
curl -X POST -b cookies.txt -H "Content-Type: application/json" \ -d '{"name": "Marketing TM", "sourceLocaleId": 10, "isShared": true}' https://api.comvi.io/v1/organizations/1/tmsReturns the TM object.
GET /v1/tms/{tmId}
Section titled “GET /v1/tms/{tmId}”Read one TM. Returns the TM object.
curl -b cookies.txt https://api.comvi.io/v1/tms/1PATCH /v1/tms/{tmId}
Section titled “PATCH /v1/tms/{tmId}”Update settings. Send at least one field; an empty object returns 422 VALIDATION_ERROR with no meta.
| Parameter | In | Type | Required | Meaning |
|---|---|---|---|---|
tmId |
path | integer |
Yes | TM ID |
name |
body | string |
No | New name |
description |
body | string | null |
No | null clears it |
isShared, autoPopulate, onlyApproved |
body | boolean |
No | Flags |
curl -X PATCH -b cookies.txt -H "Content-Type: application/json" \ -d '{"autoPopulate": true, "onlyApproved": true}' https://api.comvi.io/v1/tms/2Returns the updated TM object.
DELETE /v1/tms/{tmId}
Section titled “DELETE /v1/tms/{tmId}”Permanently delete a TM and all its entries. Export it as TMX first for a backup.
curl -X DELETE -b cookies.txt https://api.comvi.io/v1/tms/2Returns 204 No Content.
GET /v1/tms/{tmId}/entries
Section titled “GET /v1/tms/{tmId}/entries”Page through the segments in a TM.
| Parameter | In | Type | Required | Meaning |
|---|---|---|---|---|
tmId |
path | integer |
Yes | TM ID |
cursor |
query | string |
No | nextCursor from the previous page |
limit |
query | integer |
No | 1 to 100, default 50 |
targetLocaleId |
query | integer |
No | Only this target language |
sourceProjectId |
query | integer |
No | Only entries from this project |
search |
query | string |
No | Substring on source or target text, 1 to 500 characters |
curl -b cookies.txt "https://api.comvi.io/v1/tms/1/entries?targetLocaleId=11&limit=2"{ "entries": [{ "id": 5001, "tmId": 1, "sourceText": "Welcome to the dashboard", "targetText": "Bienvenue sur le tableau de bord", "targetLocaleId": 11, "targetLocaleCode": "fr", "contextKey": "dashboard.welcome", "sourceProjectId": 5, "sourceProjectName": "Web App", "authorId": 42, "authorName": "Jane Smith", "usageCount": 12, "lastUsedAt": "2026-03-20T09:00:00.000Z", "createdAt": "2026-01-15T08:00:00.000Z", "updatedAt": "2026-03-01T11:30:00.000Z" }], "nextCursor": "NTAwMQ==", "hasMore": true}POST /v1/tms/{tmId}/entries
Section titled “POST /v1/tms/{tmId}/entries”Add one segment by hand. This is an upsert: an entry with the same source text, target locale and contextKey has its targetText overwritten and keeps its ID, and the response is still 201 Created with Entry created. The response carries no entry ID either way.
| Parameter | In | Type | Required | Meaning |
|---|---|---|---|---|
tmId |
path | integer |
Yes | TM ID |
sourceText |
body | string |
Yes | 1 to 10000 characters |
targetText |
body | string |
Yes | 1 to 10000 characters |
targetLocaleId |
body | integer |
Yes | Target locale ID |
contextKey |
body | string |
No | Translation key, for context-aware matching |
curl -X POST -b cookies.txt -H "Content-Type: application/json" \ -d '{"sourceText": "Welcome to the dashboard", "targetText": "Bienvenue sur le tableau de bord", "targetLocaleId": 11, "contextKey": "dashboard.welcome"}' \ https://api.comvi.io/v1/tms/1/entries{ "message": "Entry created" }PUT /v1/tms/{tmId}/entries/{entryId}
Section titled “PUT /v1/tms/{tmId}/entries/{entryId}”Change an entry’s translation. Source text and locale are fixed. targetText is the only field and is required; an empty object returns 422 VALIDATION_ERROR with no meta.
| Parameter | In | Type | Required | Meaning |
|---|---|---|---|---|
tmId |
path | integer |
Yes | TM ID |
entryId |
path | integer |
Yes | Entry ID |
targetText |
body | string |
Yes | 1 to 10000 characters |
curl -X PUT -b cookies.txt -H "Content-Type: application/json" \ -d '{"targetText": "Bienvenue sur votre tableau de bord"}' https://api.comvi.io/v1/tms/1/entries/5001{ "message": "Entry updated" }DELETE /v1/tms/{tmId}/entries/{entryId}
Section titled “DELETE /v1/tms/{tmId}/entries/{entryId}”Delete an entry. Returns 204 No Content.
curl -X DELETE -b cookies.txt https://api.comvi.io/v1/tms/1/entries/5001GET /v1/tms/{tmId}/stats
Section titled “GET /v1/tms/{tmId}/stats”Counts for one TM.
curl -b cookies.txt https://api.comvi.io/v1/tms/1/stats{ "totalEntries": 12840, "uniqueLanguagePairs": 7, "totalUsageCount": 31502 }POST /v1/tms/{tmId}/search
Section titled “POST /v1/tms/{tmId}/search”Search one TM for matches, ranked by similarity.
| Parameter | In | Type | Required | Meaning |
|---|---|---|---|---|
tmId |
path | integer |
Yes | TM ID |
sourceText |
body | string |
Yes | 1 to 10000 characters |
targetLocaleId |
body | integer |
Yes | Target locale ID |
contextKey |
body | string |
No | Translation key, up to 512 characters |
minSimilarity |
body | integer |
No | Percentage, 50 to 100, default 50 |
maxResults |
body | integer |
No | 1 to 20, default 5 |
curl -X POST -b cookies.txt -H "Content-Type: application/json" \ -d '{"sourceText": "Welcome to the dashboard", "targetLocaleId": 11, "minSimilarity": 70}' https://api.comvi.io/v1/tms/1/search{ "matches": [{ "id": 5001, "sourceText": "Welcome to the dashboard", "targetText": "Bienvenue sur le tableau de bord", "targetLocaleId": 11, "similarity": 100, "matchType": "exact", "contextKey": "dashboard.welcome", "sourceProjectName": "Web App", "authorName": "Jane Smith", "tmName": "Main TM", "usageCount": 12 }], "total": 1}matchType |
Similarity | Meaning |
|---|---|---|
perfect |
101 | Exact text with identical context key |
exact |
100 | Exact text, different or no context |
fuzzy |
50 to 99 | Partial match by text similarity |
POST /v1/tms/{tmId}/find-replace
Section titled “POST /v1/tms/{tmId}/find-replace”Rewrite text across every entry for one target language. Run with preview: true first and read affectedCount. The preview array is present only on a preview; a real run returns affectedCount alone.
| Parameter | In | Type | Required | Meaning |
|---|---|---|---|---|
tmId |
path | integer |
Yes | TM ID |
targetLocaleId |
body | integer |
Yes | Only this target language |
find |
body | string |
Yes | 1 to 10000 characters |
replace |
body | string |
Yes | Replacement. Empty string deletes the match |
preview |
body | boolean |
No | Report without writing |
curl -X POST -b cookies.txt -H "Content-Type: application/json" \ -d '{"targetLocaleId": 11, "find": "tableau de bord", "replace": "panneau", "preview": true}' https://api.comvi.io/v1/tms/1/find-replace{ "affectedCount": 34, "preview": [{ "id": 5001, "originalText": "Bienvenue sur le tableau de bord", "replacedText": "Bienvenue sur le panneau" }] }GET /v1/projects/{projectId}/tm-settings
Section titled “GET /v1/projects/{projectId}/tm-settings”Which TMs a project searches, ordered by priority descending. Session or API key with translations:read.
| Parameter | In | Type | Required | Meaning |
|---|---|---|---|---|
projectId |
path | integer |
Yes | Project ID |
curl -H "Authorization: Bearer $COMVI_API_KEY" https://api.comvi.io/v1/projects/5/tm-settings{ "assignments": [{ "tmId": 1, "tmName": "Main TM", "priority": 90, "isWritable": true, "penalty": 0 }, { "tmId": 4, "tmName": "Legacy TM", "priority": 20, "isWritable": false, "penalty": 10 }] }| Field | Type | Meaning |
|---|---|---|
priority |
integer |
1 to 100. Higher is searched first |
isWritable |
boolean |
Receives new translations. At most one assignment |
penalty |
integer |
0 to 100, subtracted from every match’s similarity |
Any TM in the organization with isShared: true and the project’s source locale is searched as well, with priority 0 and penalty 0, and does not appear here; assign it explicitly to give it a priority or penalty. Assignments whose TM has a different source locale are dropped from the response and the search, and assigning one is refused with 400 INVALID_INPUT.
PATCH /v1/projects/{projectId}/tm-settings
Section titled “PATCH /v1/projects/{projectId}/tm-settings”Replace the whole assignment list; anything left out is unassigned. Session only (translation_memory.edit). There is no organization-route guard here, so a key reaches the permission check and gets 403, not 404.
| Parameter | In | Type | Required | Meaning |
|---|---|---|---|---|
projectId |
path | integer |
Yes | Project ID |
assignments |
body | object[] |
Yes | Up to 50 |
assignments[].tmId |
body | integer |
Yes | TM ID |
assignments[].priority |
body | integer |
Yes | 1 to 100 |
assignments[].isWritable |
body | boolean |
Yes | At most one true |
assignments[].penalty |
body | integer |
Yes | 0 to 100 |
curl -X PATCH -b cookies.txt -H "Content-Type: application/json" \ -d '{"assignments": [{"tmId": 1, "priority": 90, "isWritable": true, "penalty": 0}, {"tmId": 4, "priority": 20, "isWritable": false, "penalty": 10}]}' \ https://api.comvi.io/v1/projects/5/tm-settingsReturns the updated settings in the same shape as the GET. Two writable assignments return 400 INVALID_INPUT with meta.field set to assignments.isWritable.
POST /v1/projects/{projectId}/tm/search
Section titled “POST /v1/projects/{projectId}/tm/search”Search every TM assigned to a project at once, each TM’s penalty applied. Session or API key with translations:read. The body is the same as POST /v1/tms/{tmId}/search; the response is { matches, total } with tmName naming the memory. targetLocaleId must name an existing locale, or the call fails with 400 INVALID_INPUT before the permission check.
curl -X POST -H "Authorization: Bearer $COMVI_API_KEY" -H "Content-Type: application/json" \ -d '{"sourceText": "Welcome to the dashboard", "targetLocaleId": 11, "contextKey": "dashboard.welcome"}' \ https://api.comvi.io/v1/projects/5/tm/searchPOST /v1/tms/{tmId}/import
Section titled “POST /v1/tms/{tmId}/import”Import a TMX file. Capped at 50,000 translation units and 10 MB of XML; split larger files.
| Parameter | In | Type | Required | Meaning |
|---|---|---|---|---|
tmId |
path | integer |
Yes | TM ID |
content |
body | string |
Yes | TMX XML, up to 10 MB |
conflictResolution |
body | string |
No | skip (default), overwrite or keep_highest |
localeMapping |
body | object |
No | File codes to your codes, such as {"en-US": "en"} |
curl -X POST -b cookies.txt -H "Content-Type: application/json" \ -d '{"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><tmx version=\"1.4\">...</tmx>", "conflictResolution": "overwrite", "localeMapping": {"en-US": "en"}}' \ https://api.comvi.io/v1/tms/1/import{ "imported": 1250, "skipped": 30, "errors": ["Row 45: target locale 'xx' not found"] }skipped counts duplicates and invalid segments; errors holds a message per failed entry.
POST /v1/tms/{tmId}/import/preview
Section titled “POST /v1/tms/{tmId}/import/preview”Parse a TMX file without writing. Read targetLocales first: a locale the organization lacks needs a localeMapping entry, or its segments are skipped. A file that cannot be parsed returns 400 VALIDATION_ERROR with the parser’s message.
| Parameter | In | Type | Required | Meaning |
|---|---|---|---|---|
tmId |
path | integer |
Yes | TM ID |
content |
body | string |
Yes | TMX XML, up to 10 MB |
{ "sourceLocale": "en", "targetLocales": ["fr", "de"], "totalUnits": 1280, "sampleEntries": [{ "sourceText": "Welcome to the dashboard", "translations": [{ "locale": "fr", "text": "Bienvenue sur le tableau de bord" }] }], "errors": [] }GET /v1/tms/{tmId}/export
Section titled “GET /v1/tms/{tmId}/export”Stream the TM as TMX. The response has Content-Type: application/xml; charset=utf-8 and Content-Disposition: attachment; filename*=UTF-8''<percent-encoded TM name>.tmx, with no plain filename= parameter.
| Parameter | In | Type | Required | Meaning |
|---|---|---|---|---|
tmId |
path | integer |
Yes | TM ID |
targetLocaleId |
query | integer |
No | Only this target language |
curl -b cookies.txt -o translations.tmx https://api.comvi.io/v1/tms/1/exportErrors
Section titled “Errors”Standard envelope, except 422 validation failures, which carry code and meta and no message. A 404 NOT_FOUND on a /v1/tms/{tmId} route has the same body for a missing TM, an organization you cannot see, and an API-key request.
| Status and code | When |
|---|---|
400 INVALID_INPUT |
A locale ID does not resolve, two assignments are writable, the same tmId appears twice, or an assigned TM’s source locale differs from the project’s |
400 VALIDATION_ERROR |
The TMX will not parse, holds more than 50,000 units, or its source locale does not match the memory’s |
401 AUTHENTICATION_REQUIRED |
No session cookie and no API key |
403 FORBIDDEN |
Missing organization.manage_settings, or an API key on an organization-scoped route |
403 READ_ONLY_MODE |
The organization is over its plan limits and can only read |
404 NOT_FOUND |
No such TM, one in an organization you cannot see, or an API-key request |
422 FIELD_REQUIRED |
A required field is missing. meta.field names it |
422 FIELD_TOO_LONG |
name, sourceText, targetText or find is over its limit |
422 VALIDATION_ERROR |
A value is out of range or not in its enum, including minSimilarity outside 50 to 100 and maxResults outside 1 to 20 |
Next steps
Section titled “Next steps”- Translation memory in the platform for the dashboard
- Translations for the project-level TM search over a project’s own values
- Machine Translation to combine TM matches with MT