Integration API
Every Translatize integration – the CLI, the CI actions, and the MCP server – is built on this token-authenticated REST API. It exposes labels, exports, git-like branches, and AI translation over one bearer-token surface.
Authentication
Every request authenticates with a Translatize API token – a string beginning with mcni_ – sent as a bearer token in the Authorization header. All endpoints live under https://api.translatize.com/v1/integrations. Your app login session cannot be used here.
curl https://api.translatize.com/v1/integrations/me \
-H "Authorization: Bearer mcni_xxx"A token is bound to exactly one project and one base branch, and carries a role and a branch scope. Create tokens in the app under Project Settings → Integrations.
Roles
The role hierarchy is owner ≥ admin ≥ developer ≥ translator. Reading and writing labels needs translator or higher; creating, merging, and deleting branches needs developer or higher. Token introspection, listing branches, comparing, conflict checks, and translation status work with any valid token. A role that is too low is rejected with insufficient_permissions.
Branch scope & the branch parameter
A fixed-scope token can only act on its one base branch. A create-own token may additionally create branches (always forked from the base) and read, write, export, compare, merge, and delete the branches it created – never any others. Wherever a branch parameter is accepted it defaults to the base branch; naming a branch outside the allowed set returns branch_not_allowed with the token's boundBranch and full allowedBranches list. Merge and delete additionally require that the branch was created by this token, else not_token_branch.
Response & error envelope
Successful calls return a JSON body (documented per endpoint below). Semantic errors return a machine-readable code – { "error": "branch_not_allowed", ... } – sometimes with extra context fields. Schema-validation failures instead return an errors array of per-field messages. See the full error-code table.
Token introspection
/integrations/meDescribe the calling token: its project, base branch, branch scope, role, and non-secret settings. No role gate – any valid token may introspect itself. Secrets (token hash, IP allowlist, webhook ids) are never returned.
{
"project": {
"id": "6659f0c3a1b2c3d4e5f60718",
"name": "Acme Web",
"langs": ["en", "lv", "ru"]
},
"branch": "main",
"branchScope": "fixed",
"role": "developer",
"token": {
"name": "ci-token",
"autoPublish": false,
"expiresAt": null
}
}Labels
/integrations/projects/{projectId}/labelsList labels on a branch, sorted by key ascending. Requires the translator role or higher.
| Param | Type | In | Description |
|---|---|---|---|
| projectId* | string | path | The project the token is bound to. A mismatch is rejected with project_mismatch. |
| branch | string | query | Branch to read. Defaults to the token's base branch. |
| namespace | string | query | Keep only keys prefixed <namespace>. (a dotted prefix). |
| status | string | query | One of draft, review, approved, rejected. |
{
"branch": "main",
"total": 3,
"labels": [
{
"key": "app.title",
"values": { "en": "My App" },
"status": "draft",
"tags": [],
"updatedAt": "2026-07-19T09:12:04.771Z"
},
{
"key": "greeting.hello",
"values": { "en": "Hello", "lv": "Sveiki" },
"status": "approved",
"tags": ["ui"],
"updatedAt": "2026-07-19T09:12:05.133Z"
}
]
}/integrations/projects/{projectId}/exportDownload labels as a translation file. The JSON format is a flat, language-keyed object; every configured language is a top-level key. Requires the translator role or higher.
| Param | Type | In | Description |
|---|---|---|---|
| projectId* | string | path | The project the token is bound to. A mismatch is rejected with project_mismatch. |
| format | string | query | json (default), csv, ios, or android. The ios/android formats are single-language and default to the project's first language when lang is omitted. |
| lang | string | query | Restrict the export to a single language. |
| namespace | string | query | Dotted key-prefix filter. |
| branch | string | query | Branch to export. Defaults to the base branch. |
The response is served as a file download (for JSON, Content-Type: application/json with a Content-Disposition attachment). A key appears under a language only when it has a value there.
{
"en": {
"app.title": "My App",
"greeting.hello": "Hello"
},
"lv": {
"greeting.hello": "Sveiki"
}
}/integrations/projects/{projectId}/labelsCreate or update a single label (upsert by key) on a branch. Requires the translator role or higher.
| Param | Type | In | Description |
|---|---|---|---|
| projectId* | string | path | The project the token is bound to. A mismatch is rejected with project_mismatch. |
| key* | string | body | Dotted label key, 1–1000 chars, no empty segments (and not __proto__/constructor/prototype). |
| values | object | body | { langCode: string }. Every language must be configured on the project, else unknown_languages. |
| tags | string[] | body | Tags to set on the label. |
| status | string | body | One of draft, review, approved, rejected. |
| branch | string | body | Branch to write to. Defaults to the base branch. |
At least one of values, tags, or status is required. created is true when the key did not previously exist on the branch.
{
"key": "greeting.hello",
"branch": "main",
"created": true,
"values": { "en": "Hello" },
"status": "approved",
"tags": ["ui"]
}/integrations/projects/{projectId}/labels/batchUpsert up to 500 labels in one call. Each item is upserted independently – a rejected item lands in failed[] instead of aborting the batch. Requires the translator role or higher.
| Param | Type | In | Description |
|---|---|---|---|
| projectId* | string | path | The project the token is bound to. A mismatch is rejected with project_mismatch. |
| labels* | object[] | body | Array of { key, values?, tags?, status? }. Max 500 items, else batch_too_large. |
| branch | string | body | Branch to write to. Defaults to the base branch. |
Failures are collected per key, e.g. { "key": "bad.item", "error": "unknown_languages" } – the rest of the batch still applies.
{
"updated": 0,
"created": 2,
"failed": []
}Branches
Branch reads work with any token; writes (create / merge / delete) require a create-own token with the developer role. Merges are non-destructive by default and never delete the source branch.
/integrations/projects/{projectId}/branchesList every branch in the project, each flagged with this token's access. main is always listed. Any valid token.
| Param | Type | In | Description |
|---|---|---|---|
| projectId* | string | path | The project the token is bound to. A mismatch is rejected with project_mismatch. |
{
"baseBranch": "main",
"branchScope": "create-own",
"branches": [
{
"name": "main",
"description": "Main production branch",
"basedOn": null,
"createdAt": "2026-07-10T08:00:00.000Z",
"createdByThisToken": false,
"writable": true
},
{
"name": "feat-checkout",
"description": "checkout copy",
"basedOn": "main",
"createdAt": "2026-07-19T09:20:11.402Z",
"createdByThisToken": true,
"writable": true
}
]
}/integrations/projects/{projectId}/branchesCreate a branch forked from the token's base branch. Requires a create-own token and the developer role; a fixed-scope token is rejected with branch_scope_fixed.
| Param | Type | In | Description |
|---|---|---|---|
| projectId* | string | path | The project the token is bound to. A mismatch is rejected with project_mismatch. |
| name* | string | body | 1–100 chars matching ^[a-zA-Z0-9_-]+$, and not main. |
| description | string | body | Optional, max 500 chars. |
| basedOn | string | body | Optional and forced to the base branch – any other value returns branch_not_allowed. |
Creating a branch copies the base branch's labels into it. The plan's branch limit is enforced (branch_limit_reached).
{
"branch": {
"name": "feat-checkout",
"description": "checkout copy",
"isDefault": false,
"basedOn": "main",
"createdBy": "6650aa11bb22cc33dd44ee55",
"createdAt": "2026-07-19T09:20:11.402Z",
"lastModified": "2026-07-19T09:20:11.402Z",
"createdByName": "Alex Rivera"
}
}/integrations/projects/{projectId}/branches/{branchName}/compareDiff a branch (source) against the token's base branch (target): changed, added, and deleted keys, each with the differing per-language source/target values. Any valid token; the branch must be in the allowed set.
| Param | Type | In | Description |
|---|---|---|---|
| projectId* | string | path | The project the token is bound to. A mismatch is rejected with project_mismatch. |
| branchName* | string | path | A branch in the token's allowed set. Must match ^[a-zA-Z0-9_-]+$. |
{
"source": "feat-checkout",
"target": "main",
"differences": {
"changed": [],
"added": [
{
"key": "checkout.pay_button",
"languages": {
"en": { "sourceValue": "Pay now", "targetValue": null }
}
}
],
"deleted": []
},
"summary": { "changed": 0, "added": 1, "deleted": 0, "total": 1 }
}/integrations/projects/{projectId}/branches/{branchName}/conflictsFlat per-key/per-language conflicts of a branch (source) against the base (target) – cases where both sides hold a different non-empty value. An empty conflicts array means the branch is safe to merge with the default strategy. Any valid token.
| Param | Type | In | Description |
|---|---|---|---|
| projectId* | string | path | The project the token is bound to. A mismatch is rejected with project_mismatch. |
| branchName* | string | path | A branch in the token's allowed set. Must match ^[a-zA-Z0-9_-]+$. |
{
"source": "feat-checkout",
"target": "main",
"conflicts": [
{
"key": "app.title",
"lang": "lv",
"sourceValue": "Veikals",
"targetValue": "Sākums"
}
],
"hasConflicts": true,
"conflictCount": 1
}/integrations/projects/{projectId}/branches/{branchName}/mergeMerge a branch this token created into the base branch. Requires a create-own token with the developer role; the target is implicitly the base. The source branch is NOT deleted.
| Param | Type | In | Description |
|---|---|---|---|
| projectId* | string | path | The project the token is bound to. A mismatch is rejected with project_mismatch. |
| branchName* | string | path | A branch in the token's allowed set. Must match ^[a-zA-Z0-9_-]+$. |
| strategy | string | body | overwrite (default, non-destructive union – source wins conflicts), keep-newer, manual, or replace (destructive – base becomes a copy of source). |
| conflicts | object[] | body | Per-conflict resolutions for the manual strategy: { key, lang, resolution: "source"|"target"|"custom", customValue? }. |
| target | string | body | Optional and forced to the base branch – any other value returns branch_not_allowed. |
{
"message": "Branch merged successfully",
"source": "feat-checkout",
"target": "main",
"strategy": "overwrite"
}/integrations/projects/{projectId}/branches/{branchName}Delete a branch this token created – cleanup after a merge. Requires the developer role. The base branch, main, and branches created by others cannot be deleted (not_token_branch).
| Param | Type | In | Description |
|---|---|---|---|
| projectId* | string | path | The project the token is bound to. A mismatch is rejected with project_mismatch. |
| branchName* | string | path | A branch in the token's allowed set. Must match ^[a-zA-Z0-9_-]+$. |
{
"message": "Branch deleted successfully"
}AI translation
Platform AI auto-translation is a paid-plan feature (Professional and Agency). On a plan without it the launch returns feature_not_available – fall back to exporting the missing values and translating them yourself.
/integrations/projects/{projectId}/translation/auto-translateLaunch a background AI job to fill in missing translations on a branch. Returns HTTP 202 with the started job, or HTTP 200 with a no-op when nothing needs work. Any valid token (plan-gated).
| Param | Type | In | Description |
|---|---|---|---|
| projectId* | string | path | The project the token is bound to. A mismatch is rejected with project_mismatch. |
| targetLangs* | string[] | body | Languages to translate into (at least one). Each must be configured on the project, else unknown_target_language. |
| sourceLang | string | body | Language to translate from. Defaults to the project's first language. |
| labelKeys | string[] | body | Restrict to these keys; omit to translate every label needing work. |
| overwriteTranslated | boolean | body | Re-translate values that already have a translation. Defaults to false. |
| branch | string | body | Branch to translate. Defaults to the base branch. |
When nothing is eligible, the response is HTTP 200 with { "started": false, "nothingToTranslate": true, "reason": "already_translated", ... }. Quota exhaustion returns ai_quota_exceeded (with used/limit/remaining), and a job already in flight returns translation_already_running. Poll progress with the status endpoint below.
{
"started": true,
"branch": "main",
"job": {
"id": "6659f1a2b3c4d5e6f7081920",
"status": "queued",
"targetLangs": ["lv", "ru"],
"counts": {
"requestedLabels": 12,
"translatedValues": 0,
"failedValues": 0,
"skippedValues": 0
},
"chars": { "estimated": 840, "actual": 0 }
}
}/integrations/projects/{projectId}/translation/statusThe project's live translation job (or null), the most-recent job for context, and the AI quota of the subscription governing the project. Any valid token.
| Param | Type | In | Description |
|---|---|---|---|
| projectId* | string | path | The project the token is bound to. A mismatch is rejected with project_mismatch. |
In aiQuota, a limit of -1 means unlimited. While a job is queued, activeJob carries its position in the global FIFO queue.
{
"activeJob": null,
"lastJob": {
"id": "6659f1a2b3c4d5e6f7081920",
"status": "completed",
"branch": "main",
"sourceLang": "en",
"targetLangs": ["lv", "ru"],
"counts": {
"requestedLabels": 12,
"translatedValues": 24,
"failedValues": 0,
"skippedValues": 0
},
"chars": { "estimated": 840, "actual": 812 },
"error": null,
"startedAt": "2026-07-19T09:25:00.000Z",
"finishedAt": "2026-07-19T09:25:48.311Z"
},
"aiQuota": { "used": 812, "limit": 100000, "remaining": 99188 }
}Error codes
Semantic errors carry a machine-readable error code; some add context fields (shown in the notes). Schema-validation failures return an errors array instead.
| Code | HTTP | When it happens |
|---|---|---|
| access_denied | 401 | Missing/malformed Authorization header, a platform JWT instead of an mcni_ token, or an unknown/revoked token. |
| token_expired | 401 | The token's expiresAt has passed. |
| token_inactive | 403 | The token's bound branch was deleted; re-point it at an existing branch to reactivate (carries reason). |
| ip_not_allowed | 403 | The request IP is not in the token's IP allowlist. |
| project_mismatch | 403 | The {projectId} in the path is not the token's project. |
| project_not_found | 404 | The token's project has been removed. |
| insufficient_permissions | 403 | The token's role is below the route's minimum (translator / developer). |
| branch_not_allowed | 400 | The named branch is not in the token's allowed set (carries boundBranch + allowedBranches). |
| branch_not_found | 404 | The resolved branch does not exist on the project. |
| branch_scope_fixed | 403 | A fixed-scope token tried to create a branch. |
| not_token_branch | 403 | Merge/delete targeted a branch this token did not create. |
| invalid_name | 400 | Attempted to create a branch named "main" (the reserved default branch). Other malformed names – illegal characters or over 100 chars – fail schema validation and come back in the errors array instead. |
| branch_exists | 400 | A branch with that name already exists. |
| branch_limit_reached | 403 | The project hit its plan's branch limit (carries limit + current). |
| unknown_languages | 400 | A label value used a language not configured on the project. |
| character_limit_exceeded | 400 | A value exceeded the label's configured character limit. |
| batch_too_large | 400 | More than 500 labels in one batch request (carries max: 500). |
| feature_not_available | 403 | The project's plan does not include platform AI auto-translation. |
| unknown_target_language | 400 | A targetLangs entry is not configured on the project (carries languages). |
| too_many_labels | 400 | The job would span more than 100,000 labels (carries limit). |
| ai_quota_exceeded | 403 | The AI character quota is exhausted (carries used, limit, remaining, estimated). |
| translation_already_running | 409 | A translation job is already running for this project. |
| errors | 400 | Request body or query failed schema validation – an array of per-field messages. |
| rate_limit_exceeded | 429 | More than 2,000 requests from one IP in a 15-minute window. |
Limits
Batch upserts are capped at 500 labels per request. A single AI job may span at most 100,000 labels. AI translation is metered in characters against the governing subscription's monthly quota (a limit of -1 is unlimited). The whole integration surface is rate-limited to 2,000 requests per IP per 15 minutes; exceeding it returns HTTP 429 with { "error": "rate_limit_exceeded" }.
mcni_ tokens.