API reference
Generated from openapi.json (ResilientLang control-plane API 1.0.0), 24 operations. Concepts (authentication, errors, pagination, idempotency) are on the overview page.
System#
Liveness and this document. No key needed.
GET /v1/health#
Liveness (no data)
Authentication: None (public)
Success response (200)
| Field | Type | Required | Description |
|---|
status | string | yes | |
Status codes
| Code | Meaning |
|---|
200 | Success |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
Example
curl -sS -X GET "$RL_API_BASE/v1/health"
GET /v1/openapi.json#
This document
Authentication: None (public)
Success response (200)
Status codes
| Code | Meaning |
|---|
200 | Success |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
Example
curl -sS -X GET "$RL_API_BASE/v1/openapi.json"
Sites#
Create and configure sites (name, languages, allowed origins).
GET /v1/sites#
List sites
Authentication: Secret API key · Required scope: sites:read
Parameters
| Name | In | Type | Required | Description |
|---|
cursor | query | string | no | |
limit | query | integer | no | |
Success response (200)
| Field | Type | Required | Description |
|---|
items | array of object | yes | |
items[].id | string (uuid) | yes | |
items[].siteKey | string | yes | |
items[].name | string | yes | |
items[].sourceLanguage | string | yes | |
items[].targetLanguages | array of string | yes | |
items[].allowedOrigins | array of string | yes | |
nextCursor | string | no | |
Status codes
| Code | Meaning |
|---|
200 | Success |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
403 | Insufficient scope, or origin not allowed |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
Example
curl -sS -X GET "$RL_API_BASE/v1/sites" \
-H "Authorization: Bearer $RL_SECRET_KEY"
POST /v1/sites#
Create a site
Authentication: Secret API key · Required scope: sites:write
Parameters
| Name | In | Type | Required | Description |
|---|
Idempotency-Key | header | string | no | |
Request body
| Field | Type | Required | Description |
|---|
name | string | yes | |
sourceLanguage | string | yes | |
targetLanguages | array of string | yes | |
allowedOrigins | array of string | no | |
Success response (201)
| Field | Type | Required | Description |
|---|
id | string (uuid) | yes | |
siteKey | string | yes | |
name | string | yes | |
sourceLanguage | string | yes | |
targetLanguages | array of string | yes | |
allowedOrigins | array of string | yes | |
Response example
{
"id": "3f2b1c9e-6a4d-4c8e-9a53-0d2f7b1e8c11",
"siteKey": "rl_site_live_abcdefghijklmnopqrstuv",
"name": "My shop",
"sourceLanguage": "en",
"targetLanguages": [
"fr",
"de"
],
"allowedOrigins": [
"https://shop.example.com"
]
}
Status codes
| Code | Meaning |
|---|
201 | Success |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
402 | A plan, allowance or licence blocks this action (see the problem type; D41). Retry-After is set when the allowance resets. |
403 | Insufficient scope, or origin not allowed |
409 | Idempotent request still in progress |
413 | Body too large |
415 | Unsupported media type |
422 | Idempotency-Key reused with a different request, or the import was rejected |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
503 | A plan/quota/licence/suspension check was unavailable (fails closed); retry after Retry-After. |
Example
curl -sS -X POST "$RL_API_BASE/v1/sites" \
-H "Authorization: Bearer $RL_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"string","sourceLanguage":"string","targetLanguages":["string"],"allowedOrigins":["string"]}'
GET /v1/sites/{siteId}#
Get a site
Authentication: Secret API key · Required scope: sites:read
Parameters
| Name | In | Type | Required | Description |
|---|
siteId | path | string | yes | |
Success response (200)
| Field | Type | Required | Description |
|---|
id | string (uuid) | yes | |
siteKey | string | yes | |
name | string | yes | |
sourceLanguage | string | yes | |
targetLanguages | array of string | yes | |
allowedOrigins | array of string | yes | |
Response example
{
"id": "3f2b1c9e-6a4d-4c8e-9a53-0d2f7b1e8c11",
"siteKey": "rl_site_live_abcdefghijklmnopqrstuv",
"name": "My shop",
"sourceLanguage": "en",
"targetLanguages": [
"fr",
"de"
],
"allowedOrigins": [
"https://shop.example.com"
]
}
Status codes
| Code | Meaning |
|---|
200 | Success |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
403 | Insufficient scope, or origin not allowed |
404 | Not found (also returned for other tenants' resources) |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
Example
curl -sS -X GET "$RL_API_BASE/v1/sites/${SITE_ID}" \
-H "Authorization: Bearer $RL_SECRET_KEY"
PATCH /v1/sites/{siteId}#
Update site settings
Authentication: Secret API key · Required scope: sites:write
Parameters
| Name | In | Type | Required | Description |
|---|
siteId | path | string | yes | |
Idempotency-Key | header | string | no | |
Request body
| Field | Type | Required | Description |
|---|
name | string | no | |
targetLanguages | array of string | no | |
allowedOrigins | array of string | no | |
Success response (200)
| Field | Type | Required | Description |
|---|
id | string (uuid) | yes | |
siteKey | string | yes | |
name | string | yes | |
sourceLanguage | string | yes | |
targetLanguages | array of string | yes | |
allowedOrigins | array of string | yes | |
Response example
{
"id": "3f2b1c9e-6a4d-4c8e-9a53-0d2f7b1e8c11",
"siteKey": "rl_site_live_abcdefghijklmnopqrstuv",
"name": "My shop",
"sourceLanguage": "en",
"targetLanguages": [
"fr",
"de"
],
"allowedOrigins": [
"https://shop.example.com"
]
}
Status codes
| Code | Meaning |
|---|
200 | Success |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
402 | A plan, allowance or licence blocks this action (see the problem type; D41). Retry-After is set when the allowance resets. |
403 | Insufficient scope, or origin not allowed |
404 | Not found (also returned for other tenants' resources) |
409 | Idempotent request still in progress |
413 | Body too large |
415 | Unsupported media type |
422 | Idempotency-Key reused with a different request, or the import was rejected |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
503 | A plan/quota/licence/suspension check was unavailable (fails closed); retry after Retry-After. |
Example
curl -sS -X PATCH "$RL_API_BASE/v1/sites/${SITE_ID}" \
-H "Authorization: Bearer $RL_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"string","targetLanguages":["string"],"allowedOrigins":["string"]}'
Strings#
Submit extracted source strings; keys are computed by the server (BUNDLE_FORMAT.md).
POST /v1/sites/{siteId}/strings#
Submit extracted strings (max 1000)
Up to 1000 items. The response lists the key of every item in order; identical text (after normalisation) yields the same key and is stored once.
Authentication: Secret API key · Required scope: strings:write
Parameters
| Name | In | Type | Required | Description |
|---|
siteId | path | string | yes | |
Idempotency-Key | header | string | no | |
Request body
| Field | Type | Required | Description |
|---|
items | array of object | yes | |
items[].text | string | yes | |
items[].kind | enum: text | attr | no | |
items[].context | string | no | |
items[].url | string (uri) | no | |
Success response (200)
| Field | Type | Required | Description |
|---|
items | array of object | yes | |
items[].key | string | yes | |
items[].kind | string | yes | |
stored | integer | no | |
Response example
{
"items": [
{
"key": "0123456789abcdef",
"kind": "text"
},
{
"key": "fedcba9876543210",
"kind": "attr"
}
],
"stored": 2
}
Status codes
| Code | Meaning |
|---|
200 | Success |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
402 | A plan, allowance or licence blocks this action (see the problem type; D41). Retry-After is set when the allowance resets. |
403 | Insufficient scope, or origin not allowed |
404 | Not found (also returned for other tenants' resources) |
409 | Idempotent request still in progress |
413 | Body too large |
415 | Unsupported media type |
422 | Idempotency-Key reused with a different request, or the import was rejected |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
503 | A plan/quota/licence/suspension check was unavailable (fails closed); retry after Retry-After. |
Example
curl -sS -X POST "$RL_API_BASE/v1/sites/${SITE_ID}/strings" \
-H "Authorization: Bearer $RL_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"items":[{"text":"string"}]}'
Jobs#
Asynchronous work (scan, translate, publish) and its status.
POST /v1/sites/{siteId}/scan#
Start a crawl (async job)
Every URL and the sitemap URL must be on one of the site's allowedOrigins and pass the SSRF check. Returns a job.
Authentication: Secret API key · Required scope: sites:write
Parameters
| Name | In | Type | Required | Description |
|---|
siteId | path | string | yes | |
Idempotency-Key | header | string | no | |
Request body
| Field | Type | Required | Description |
|---|
urls | array of string | no | |
sitemapUrl | string | no | |
respectRobots | boolean | no | |
Success response (202)
| Field | Type | Required | Description |
|---|
id | string (uuid) | yes | |
kind | enum: scan | translate | publish | yes | |
siteId | string (uuid) | yes | |
state | enum: queued | running | succeeded | failed | canceled | yes | |
attempts | integer | yes | |
progressPercent | integer | yes | |
createdAt | string (date-time) | yes | |
finishedAt | string (date-time) | no | |
error | string | no | |
result | object | no | Present on succeeded publish jobs. |
result.version | integer | yes | |
result.publishedAt | string (date-time) | no | |
result.targets | array of object | no | |
result.targets[].target | string | yes | |
result.targets[].ok | boolean | yes | |
result.targets[].error | string | no | |
Response example
{
"id": "9b1e6c52-2f43-4a8e-8d0b-5c7a1f3e9a20",
"kind": "publish",
"siteId": "3f2b1c9e-6a4d-4c8e-9a53-0d2f7b1e8c11",
"state": "succeeded",
"attempts": 1,
"progressPercent": 100,
"createdAt": "2026-10-28T12:00:00Z",
"finishedAt": "2026-10-28T12:00:09Z",
"result": {
"version": 7,
"publishedAt": "2026-10-28T12:00:09Z",
"targets": [
{
"target": "r2-primary",
"ok": true
},
{
"target": "cdn-secondary",
"ok": true
}
]
}
}
Status codes
| Code | Meaning |
|---|
202 | Success |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
403 | Insufficient scope, or origin not allowed |
404 | Not found (also returned for other tenants' resources) |
409 | Idempotent request still in progress |
413 | Body too large |
415 | Unsupported media type |
422 | Idempotency-Key reused with a different request, or the import was rejected |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
Example
curl -sS -X POST "$RL_API_BASE/v1/sites/${SITE_ID}/scan" \
-H "Authorization: Bearer $RL_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"urls":["string"],"sitemapUrl":"string","respectRobots":true}'
POST /v1/sites/{siteId}/translate#
Machine-translate missing strings (async job)
Enqueues one job per target language (or per requested language). Never overwrites reviewed or manual translations.
Authentication: Secret API key · Required scope: translations:write
Parameters
| Name | In | Type | Required | Description |
|---|
siteId | path | string | yes | |
Idempotency-Key | header | string | no | |
Request body
| Field | Type | Required | Description |
|---|
langs | array of string | no | |
keys | array of string | no | |
force | boolean | no | |
Success response (202)
| Field | Type | Required | Description |
|---|
jobs | array of object | yes | |
jobs[].id | string (uuid) | yes | |
jobs[].kind | enum: scan | translate | publish | yes | |
jobs[].siteId | string (uuid) | yes | |
jobs[].state | enum: queued | running | succeeded | failed | canceled | yes | |
jobs[].attempts | integer | yes | |
jobs[].progressPercent | integer | yes | |
jobs[].createdAt | string (date-time) | yes | |
jobs[].finishedAt | string (date-time) | no | |
jobs[].error | string | no | |
jobs[].result | object | no | Present on succeeded publish jobs. |
jobs[].result.version | integer | yes | |
jobs[].result.publishedAt | string (date-time) | no | |
jobs[].result.targets | array of object | no | |
jobs[].result.targets[].target | string | yes | |
jobs[].result.targets[].ok | boolean | yes | |
jobs[].result.targets[].error | string | no | |
Status codes
| Code | Meaning |
|---|
202 | Success |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
402 | A plan, allowance or licence blocks this action (see the problem type; D41). Retry-After is set when the allowance resets. |
403 | Insufficient scope, or origin not allowed |
404 | Not found (also returned for other tenants' resources) |
409 | Idempotent request still in progress |
413 | Body too large |
415 | Unsupported media type |
422 | Idempotency-Key reused with a different request, or the import was rejected |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
503 | A plan/quota/licence/suspension check was unavailable (fails closed); retry after Retry-After. |
Example
curl -sS -X POST "$RL_API_BASE/v1/sites/${SITE_ID}/translate" \
-H "Authorization: Bearer $RL_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"langs":["string"],"keys":["string"],"force":true}'
POST /v1/sites/{siteId}/publish#
Build and publish bundles (async job)
Builds and publishes signed bundles to every storage target. Poll the job; a succeeded job's `result.version` is the published version.
Authentication: Secret API key · Required scope: bundles:publish
Parameters
| Name | In | Type | Required | Description |
|---|
siteId | path | string | yes | |
Idempotency-Key | header | string | no | |
Request body
| Field | Type | Required | Description |
|---|
onlyIfChanged | boolean | no | |
Success response (202)
| Field | Type | Required | Description |
|---|
id | string (uuid) | yes | |
kind | enum: scan | translate | publish | yes | |
siteId | string (uuid) | yes | |
state | enum: queued | running | succeeded | failed | canceled | yes | |
attempts | integer | yes | |
progressPercent | integer | yes | |
createdAt | string (date-time) | yes | |
finishedAt | string (date-time) | no | |
error | string | no | |
result | object | no | Present on succeeded publish jobs. |
result.version | integer | yes | |
result.publishedAt | string (date-time) | no | |
result.targets | array of object | no | |
result.targets[].target | string | yes | |
result.targets[].ok | boolean | yes | |
result.targets[].error | string | no | |
Response example
{
"id": "9b1e6c52-2f43-4a8e-8d0b-5c7a1f3e9a20",
"kind": "publish",
"siteId": "3f2b1c9e-6a4d-4c8e-9a53-0d2f7b1e8c11",
"state": "succeeded",
"attempts": 1,
"progressPercent": 100,
"createdAt": "2026-10-28T12:00:00Z",
"finishedAt": "2026-10-28T12:00:09Z",
"result": {
"version": 7,
"publishedAt": "2026-10-28T12:00:09Z",
"targets": [
{
"target": "r2-primary",
"ok": true
},
{
"target": "cdn-secondary",
"ok": true
}
]
}
}
Status codes
| Code | Meaning |
|---|
202 | Success |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
402 | A plan, allowance or licence blocks this action (see the problem type; D41). Retry-After is set when the allowance resets. |
403 | Insufficient scope, or origin not allowed |
404 | Not found (also returned for other tenants' resources) |
409 | Idempotent request still in progress |
413 | Body too large |
415 | Unsupported media type |
422 | Idempotency-Key reused with a different request, or the import was rejected |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
503 | A plan/quota/licence/suspension check was unavailable (fails closed); retry after Retry-After. |
Example
curl -sS -X POST "$RL_API_BASE/v1/sites/${SITE_ID}/publish" \
-H "Authorization: Bearer $RL_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"onlyIfChanged":true}'
GET /v1/jobs/{jobId}#
Job status
Status of a job created by scan, translate or publish. Succeeded publish jobs include result.version, publishedAt and per-target results.
Authentication: Secret API key
Parameters
| Name | In | Type | Required | Description |
|---|
jobId | path | string | yes | |
Success response (200)
| Field | Type | Required | Description |
|---|
id | string (uuid) | yes | |
kind | enum: scan | translate | publish | yes | |
siteId | string (uuid) | yes | |
state | enum: queued | running | succeeded | failed | canceled | yes | |
attempts | integer | yes | |
progressPercent | integer | yes | |
createdAt | string (date-time) | yes | |
finishedAt | string (date-time) | no | |
error | string | no | |
result | object | no | Present on succeeded publish jobs. |
result.version | integer | yes | |
result.publishedAt | string (date-time) | no | |
result.targets | array of object | no | |
result.targets[].target | string | yes | |
result.targets[].ok | boolean | yes | |
result.targets[].error | string | no | |
Response example
{
"id": "9b1e6c52-2f43-4a8e-8d0b-5c7a1f3e9a20",
"kind": "publish",
"siteId": "3f2b1c9e-6a4d-4c8e-9a53-0d2f7b1e8c11",
"state": "succeeded",
"attempts": 1,
"progressPercent": 100,
"createdAt": "2026-10-28T12:00:00Z",
"finishedAt": "2026-10-28T12:00:09Z",
"result": {
"version": 7,
"publishedAt": "2026-10-28T12:00:09Z",
"targets": [
{
"target": "r2-primary",
"ok": true
},
{
"target": "cdn-secondary",
"ok": true
}
]
}
}
Status codes
| Code | Meaning |
|---|
200 | Success |
401 | Missing or invalid API key |
404 | Not found (also returned for other tenants' resources) |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
Example
curl -sS -X GET "$RL_API_BASE/v1/jobs/${JOB_ID}" \
-H "Authorization: Bearer $RL_SECRET_KEY"
Translations#
List translations and set manual overrides, which machine runs never overwrite.
GET /v1/sites/{siteId}/translations#
List translations
Authentication: Secret API key · Required scope: translations:read
Parameters
| Name | In | Type | Required | Description |
|---|
siteId | path | string | yes | |
cursor | query | string | no | |
limit | query | integer | no | |
lang | query | string | yes | |
status | query | string | no | |
Success response (200)
| Field | Type | Required | Description |
|---|
items | array of object | yes | |
items[].key | string | yes | |
items[].lang | string | yes | |
items[].text | string | yes | |
items[].status | enum: machine | reviewed | manual | yes | |
items[].updatedAt | string (date-time) | yes | |
items[].flags | array of string | no | |
nextCursor | string | no | |
Status codes
| Code | Meaning |
|---|
200 | Success |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
403 | Insufficient scope, or origin not allowed |
404 | Not found (also returned for other tenants' resources) |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
Example
curl -sS -X GET "$RL_API_BASE/v1/sites/${SITE_ID}/translations" \
-H "Authorization: Bearer $RL_SECRET_KEY"
PUT /v1/sites/{siteId}/translations/{lang}/{key}#
Manual override
Authentication: Secret API key · Required scope: translations:write
Parameters
| Name | In | Type | Required | Description |
|---|
siteId | path | string | yes | |
lang | path | string | yes | |
key | path | string | yes | |
Idempotency-Key | header | string | no | |
Request body
| Field | Type | Required | Description |
|---|
text | string | yes | |
Success response (200)
| Field | Type | Required | Description |
|---|
key | string | yes | |
lang | string | yes | |
text | string | yes | |
status | enum: machine | reviewed | manual | yes | |
updatedAt | string (date-time) | yes | |
flags | array of string | no | |
Status codes
| Code | Meaning |
|---|
200 | Success |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
403 | Insufficient scope, or origin not allowed |
404 | Not found (also returned for other tenants' resources) |
409 | Idempotent request still in progress |
413 | Body too large |
415 | Unsupported media type |
422 | Idempotency-Key reused with a different request, or the import was rejected |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
Example
curl -sS -X PUT "$RL_API_BASE/v1/sites/${SITE_ID}/translations/${LANG}/${KEY}" \
-H "Authorization: Bearer $RL_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"string"}'
Glossary#
Do-not-translate terms and preferred translations.
GET /v1/sites/{siteId}/glossary#
Get glossary
Authentication: Secret API key · Required scope: translations:read
Parameters
| Name | In | Type | Required | Description |
|---|
siteId | path | string | yes | |
Success response (200)
| Field | Type | Required | Description |
|---|
doNotTranslate | array of string | no | |
preferred | array of object | no | |
preferred[].source | string | yes | |
preferred[].lang | string | no | Omit to apply to every target language. |
preferred[].target | string | yes | |
Response example
{
"doNotTranslate": [
"Acme"
],
"preferred": [
{
"source": "cart",
"lang": "fr",
"target": "panier"
}
]
}
Status codes
| Code | Meaning |
|---|
200 | Success |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
403 | Insufficient scope, or origin not allowed |
404 | Not found (also returned for other tenants' resources) |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
Example
curl -sS -X GET "$RL_API_BASE/v1/sites/${SITE_ID}/glossary" \
-H "Authorization: Bearer $RL_SECRET_KEY"
PUT /v1/sites/{siteId}/glossary#
Replace glossary
Authentication: Secret API key · Required scope: translations:write
Parameters
| Name | In | Type | Required | Description |
|---|
siteId | path | string | yes | |
Idempotency-Key | header | string | no | |
Request body
| Field | Type | Required | Description |
|---|
doNotTranslate | array of string | no | |
preferred | array of object | no | |
preferred[].source | string | yes | |
preferred[].lang | string | no | Omit to apply to every target language. |
preferred[].target | string | yes | |
Success response (200)
| Field | Type | Required | Description |
|---|
doNotTranslate | array of string | no | |
preferred | array of object | no | |
preferred[].source | string | yes | |
preferred[].lang | string | no | Omit to apply to every target language. |
preferred[].target | string | yes | |
Response example
{
"doNotTranslate": [
"Acme"
],
"preferred": [
{
"source": "cart",
"lang": "fr",
"target": "panier"
}
]
}
Status codes
| Code | Meaning |
|---|
200 | Success |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
403 | Insufficient scope, or origin not allowed |
404 | Not found (also returned for other tenants' resources) |
409 | Idempotent request still in progress |
413 | Body too large |
415 | Unsupported media type |
422 | Idempotency-Key reused with a different request, or the import was rejected |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
Example
curl -sS -X PUT "$RL_API_BASE/v1/sites/${SITE_ID}/glossary" \
-H "Authorization: Bearer $RL_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"doNotTranslate":["string"],"preferred":[{"source":"string","target":"string"}]}'
Interchange#
Export and import translations as JSON, XLIFF, PO/POT, CSV or TMX (the ownership guarantee).
GET /v1/sites/{siteId}/export#
Full export (format=json|xliff|xliff2|po|pot|csv|tmx, optional lang)
Returns every translation of the site in the chosen format so a customer can keep or edit them elsewhere. Never cached. Formats that hold one language per file (po, xliff2) return a .zip when several languages are included.
Authentication: Secret API key · Required scope: translations:read
Parameters
| Name | In | Type | Required | Description |
|---|
siteId | path | string | yes | |
format | query | enum: json | xliff | xliff2 | po | pot | csv | tmx | no | Output format. xliff = XLIFF 1.2, xliff2 = XLIFF 2.x, pot = blank gettext template. Formats that hold one language (po, xliff2) return a .zip when several languages are exported. |
lang | query | string | no | Limit to these target languages; repeat the parameter or separate with commas. Default: all. |
Status codes
| Code | Meaning |
|---|
200 | The export file (a .zip when several one-language files are produced). Always Cache-Control: no-store. |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
403 | Insufficient scope, or origin not allowed |
404 | Not found (also returned for other tenants' resources) |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
Example
curl -sS -X GET "$RL_API_BASE/v1/sites/${SITE_ID}/export" \
-H "Authorization: Bearer $RL_SECRET_KEY"
POST /v1/sites/{siteId}/import#
Import translations from a file (multipart) or the JSON export (application/json)
Send multipart/form-data with field `file` (and `reference` for key-based formats such as android, messages-json and keyvalue-json), plus optional fields `format` (auto-detected when omitted), `lang` (for single-language files that do not state one), `status` (machine|reviewed|manual: overrides every entry's status; default: the file's own status, else manual) and `dryRun` (true = report the plan, write nothing). Strings the site does not know are skipped and listed in `issues`. Entries whose inline tags or placeholders do not survive are rejected. Any error-level issue rejects the whole import (422).
Authentication: Secret API key · Required scope: translations:write
Parameters
| Name | In | Type | Required | Description |
|---|
siteId | path | string | yes | |
Idempotency-Key | header | string | no | |
Request body
| Field | Type | Required | Description |
|---|
file | string (binary) | yes | The translation file. |
reference | string (binary) | no | Source-language file; required for android, messages-json and keyvalue-json. |
format | enum: json | xliff | xliff2 | po | csv | tmx | android | messages-json | keyvalue-json | no | Omit to detect from the file name and content. |
lang | string | no | Target language for single-language files that do not state one. |
status | enum: machine | reviewed | manual | no | Overrides the status of every entry. Default: the file's own status, else manual. |
dryRun | boolean | no | Report what would happen and write nothing. |
Success response (200)
| Field | Type | Required | Description |
|---|
formatId | string | yes | |
dryRun | boolean | yes | |
applied | boolean | yes | |
planned | integer | yes | |
written | integer | yes | |
skippedProtected | integer | no | Machine entries not written because a reviewed or manual translation exists. |
stats | object | yes | |
stats.unitsRead | integer | no | |
stats.matched | integer | no | |
stats.skippedUntranslated | integer | no | |
stats.skippedUnknown | integer | no | |
stats.skippedInvalid | integer | no | |
stats.conflicts | integer | no | |
issues | array of object | yes | |
issues[].severity | enum: info | warning | error | yes | |
issues[].code | string | yes | |
issues[].message | string | yes | |
issues[].where | string | no | |
Response example
{
"formatId": "xliff",
"dryRun": false,
"applied": true,
"planned": 1,
"written": 1,
"skippedProtected": 0,
"stats": {
"unitsRead": 2,
"matched": 1,
"skippedUntranslated": 0,
"skippedUnknown": 1,
"skippedInvalid": 0,
"conflicts": 0
},
"issues": [
{
"severity": "warning",
"code": "unknown-string",
"message": "String not found on this site; skipped.",
"where": "trans-unit b"
}
]
}
Status codes
| Code | Meaning |
|---|
200 | Success |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
403 | Insufficient scope, or origin not allowed |
404 | Not found (also returned for other tenants' resources) |
409 | Idempotent request still in progress |
413 | Body too large |
415 | Unsupported media type |
422 | Idempotency-Key reused with a different request, or the import was rejected |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
Example
curl -sS -X POST "$RL_API_BASE/v1/sites/${SITE_ID}/import" \
-H "Authorization: Bearer $RL_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"file":"string","reference":"string","format":"json","lang":"string","status":"machine","dryRun":true}'
Usage#
Strings, characters, engine cost and the plan allowance.
GET /v1/sites/{siteId}/usage#
Strings, characters, engine cost
Authentication: Secret API key · Required scope: usage:read
Parameters
| Name | In | Type | Required | Description |
|---|
siteId | path | string | yes | |
from | query | string | no | |
to | query | string | no | |
Success response (200)
| Field | Type | Required | Description |
|---|
siteId | string (uuid) | yes | |
from | string (date) | yes | |
to | string (date) | yes | |
strings | integer | yes | |
characters | integer | yes | |
engineCostUsd | number | yes | |
Status codes
| Code | Meaning |
|---|
200 | Success |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
403 | Insufficient scope, or origin not allowed |
404 | Not found (also returned for other tenants' resources) |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
Example
curl -sS -X GET "$RL_API_BASE/v1/sites/${SITE_ID}/usage" \
-H "Authorization: Bearer $RL_SECRET_KEY"
GET /v1/usage/quota#
Plan allowance and period usage for the key's tenant
The plan allowance and where the tenant stands in the current period (404 when quotas are not enabled on the deployment).
Authentication: Secret API key · Required scope: usage:read
Success response (200)
| Field | Type | Required | Description |
|---|
planId | string | yes | |
periodStart | string (date) | yes | |
periodEnd | string (date) | yes | |
unlimited | boolean | yes | |
usedCharacters | integer | yes | |
reservedCharacters | integer | no | |
allowanceCharacters | integer | no | |
usedWords | integer | no | |
allowanceWords | integer | no | |
remainingWords | integer | no | |
fraction | number | no | |
state | enum: ok | soft | exhausted | yes | |
byoCharacters | integer | no | |
stringsDiscovered | integer | no | |
engineCostUsd | number | no | |
Status codes
| Code | Meaning |
|---|
200 | Success |
401 | Missing or invalid API key |
403 | Insufficient scope, or origin not allowed |
404 | Not found (also returned for other tenants' resources) |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
503 | A plan/quota/licence/suspension check was unavailable (fails closed); retry after Retry-After. |
Example
curl -sS -X GET "$RL_API_BASE/v1/usage/quota" \
-H "Authorization: Bearer $RL_SECRET_KEY"
Keys#
Secret API keys: create, rotate, revoke. Secrets are shown once.
GET /v1/keys#
List secret keys (never returns secrets)
Authentication: Secret API key · Required scope: keys:manage
Parameters
| Name | In | Type | Required | Description |
|---|
cursor | query | string | no | |
limit | query | integer | no | |
Success response (200)
| Field | Type | Required | Description |
|---|
items | array of object | yes | |
items[].id | string (uuid) | yes | |
items[].name | string | yes | |
items[].prefix | string | yes | |
items[].kind | string | no | |
items[].scopes | array of string | yes | |
items[].siteId | string (uuid) | no | |
items[].expiresAt | string (date-time) | no | |
items[].lastUsedAt | string (date-time) | no | |
items[].revokedAt | string (date-time) | no | |
items[].graceUntil | string (date-time) | no | |
nextCursor | string | no | |
Status codes
| Code | Meaning |
|---|
200 | Success |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
403 | Insufficient scope, or origin not allowed |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
Example
curl -sS -X GET "$RL_API_BASE/v1/keys" \
-H "Authorization: Bearer $RL_SECRET_KEY"
POST /v1/keys#
Create a secret key (secret shown once)
The secret is returned once in this response and never again. The new key cannot hold scopes the calling key lacks. Test keys cannot create keys. Responses are never replayed from an Idempotency-Key.
Authentication: Secret API key · Required scope: keys:manage
Parameters
| Name | In | Type | Required | Description |
|---|
Idempotency-Key | header | string | no | |
Request body
| Field | Type | Required | Description |
|---|
name | string | yes | |
scopes | array of enum: sites:read | sites:write | strings:write | translations:read | translations:write | bundles:publish | usage:read | keys:manage | webhooks:manage | audit:read | audit:manage | billing:read | billing:manage | yes | |
siteId | string (uuid) | no | |
expiresAt | string (date-time) | no | |
Success response (201)
| Field | Type | Required | Description |
|---|
id | string (uuid) | yes | |
name | string | yes | |
prefix | string | yes | |
kind | string | no | |
scopes | array of string | yes | |
siteId | string (uuid) | no | |
expiresAt | string (date-time) | no | |
lastUsedAt | string (date-time) | no | |
revokedAt | string (date-time) | no | |
graceUntil | string (date-time) | no | |
secret | string | no | Shown once, never again. |
Response example
{
"id": "c2a4f0de-7b61-4e3a-9f55-1a8b2c3d4e5f",
"name": "CI",
"prefix": "rl_sec_live_",
"kind": "secret",
"scopes": [
"strings:write",
"bundles:publish"
],
"secret": "rl_sec_live_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
Status codes
| Code | Meaning |
|---|
201 | Success |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
403 | Insufficient scope, or origin not allowed |
409 | Idempotent request still in progress |
413 | Body too large |
415 | Unsupported media type |
422 | Idempotency-Key reused with a different request, or the import was rejected |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
Example
curl -sS -X POST "$RL_API_BASE/v1/keys" \
-H "Authorization: Bearer $RL_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"string","scopes":["sites:read"],"siteId":"00000000-0000-0000-0000-000000000000","expiresAt":"2026-01-01T00:00:00Z"}'
POST /v1/keys/{id}/rotate#
Rotate a key (new valid now, old valid for a grace period)
The new key works immediately; the old key keeps working for gracePeriodSeconds (default 24 h, max 30 days).
Authentication: Secret API key · Required scope: keys:manage
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string | yes | |
Idempotency-Key | header | string | no | |
Request body
| Field | Type | Required | Description |
|---|
gracePeriodSeconds | integer | no | |
Success response (201)
| Field | Type | Required | Description |
|---|
id | string (uuid) | yes | |
name | string | yes | |
prefix | string | yes | |
kind | string | no | |
scopes | array of string | yes | |
siteId | string (uuid) | no | |
expiresAt | string (date-time) | no | |
lastUsedAt | string (date-time) | no | |
revokedAt | string (date-time) | no | |
graceUntil | string (date-time) | no | |
secret | string | no | Shown once, never again. |
Response example
{
"id": "c2a4f0de-7b61-4e3a-9f55-1a8b2c3d4e5f",
"name": "CI",
"prefix": "rl_sec_live_",
"kind": "secret",
"scopes": [
"strings:write",
"bundles:publish"
],
"secret": "rl_sec_live_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
Status codes
| Code | Meaning |
|---|
201 | Success |
400 | Validation failed or malformed request |
401 | Missing or invalid API key |
403 | Insufficient scope, or origin not allowed |
404 | Not found (also returned for other tenants' resources) |
409 | Idempotent request still in progress |
413 | Body too large |
415 | Unsupported media type |
422 | Idempotency-Key reused with a different request, or the import was rejected |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
Example
curl -sS -X POST "$RL_API_BASE/v1/keys/${ID}/rotate" \
-H "Authorization: Bearer $RL_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"gracePeriodSeconds":1}'
DELETE /v1/keys/{id}#
Revoke a key
Authentication: Secret API key · Required scope: keys:manage
Parameters
| Name | In | Type | Required | Description |
|---|
id | path | string | yes | |
Idempotency-Key | header | string | no | |
Status codes
| Code | Meaning |
|---|
204 | No content |
401 | Missing or invalid API key |
403 | Insufficient scope, or origin not allowed |
404 | Not found (also returned for other tenants' resources) |
409 | Idempotent request still in progress |
422 | Idempotency-Key reused with a different request, or the import was rejected |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
Example
curl -sS -X DELETE "$RL_API_BASE/v1/keys/${ID}" \
-H "Authorization: Bearer $RL_SECRET_KEY"
Discovery#
Optional browser-side reporting of untranslated strings (site key and allowed Origin, off by default).
POST /v1/discover#
Browser snippet reports untranslated strings (site key + allowed Origin; off by default)
Disabled unless the deployment enables it (404). Authenticated by the public site key plus the browser's Origin header, which must be on the site's allowedOrigins.
Authentication: None (public)
Parameters
| Name | In | Type | Required | Description |
|---|
Idempotency-Key | header | string | no | |
Request body
| Field | Type | Required | Description |
|---|
siteKey | string | yes | |
items | array of object | yes | |
items[].text | string | yes | |
items[].kind | enum: text | attr | no | |
items[].context | string | no | |
items[].url | string (uri) | no | |
Success response (202)
| Field | Type | Required | Description |
|---|
accepted | integer | yes | |
Status codes
| Code | Meaning |
|---|
202 | Success |
400 | Validation failed or malformed request |
402 | A plan, allowance or licence blocks this action (see the problem type; D41). Retry-After is set when the allowance resets. |
403 | Insufficient scope, or origin not allowed |
404 | Not found (also returned for other tenants' resources) |
413 | Body too large |
415 | Unsupported media type |
422 | Idempotency-Key reused with a different request, or the import was rejected |
429 | Rate limited (see Retry-After) |
500 | Unexpected error (no details are exposed; quote the reference) |
503 | A plan/quota/licence/suspension check was unavailable (fails closed); retry after Retry-After. |
Example
curl -sS -X POST "$RL_API_BASE/v1/discover" \
-H "Content-Type: application/json" \
-d '{"siteKey":"string","items":[{"text":"string"}]}'