Developer API
REST API · CLI · MCP · GitHub Action · 46 languages

Developer API, CLI & MCP

Translate JSON, YAML, .strings, .po, ARB, Android XML, CSV, XLIFF 1.2, Java .properties, .NET RESX and Mozilla Fluent (.ftl) into 46 languages, with verify-meaning QA on every string — from the REST API, the CLI, your CI/CD pipeline, or your AI editor.

CLI

Terminal & CI. Incremental & reproducible — only new/changed keys.

pip install kaeris
MCP server

For Claude Desktop, Cursor & Claude Code. How it works

pip install kaeris-mcp
GitHub Action

Auto-translate new keys & open a PR on every push.

RaiGanja/kaeris-action@v1 ↗

Every translation — CLI, MCP, Action or raw API — runs through the same verify-meaning QA pass: each string is back-translated to catch meaning drift before it ships.

Agentic localization in CI

GitHub Action

Push a new string to locales/en.json. KAERIS translates just the new keys and opens a pull request with the diff — you review and merge like any other change. Zero-touch, incremental, with an i18n-firewall check gating merges on missing or placeholder-broken translations.

Zero-touchPush → PR. No dashboard, no manual export/import.
IncrementalOnly new/changed keys, tracked via kaeris.lock — not a full re-translate every run.
i18n-firewallkaeris check --ci as a required status check — blocks merges with missing or broken translations.
.github/workflows/translate.yml
on: { push: { branches: [main], paths: ["locales/en.json"] } }

jobs:
  translate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: RaiGanja/kaeris-action@v1
        id: kaeris
        with: { source: locales/en.json, languages: es,fr,de,ja, only-new: "true" }
      - if: steps.kaeris.outputs.changed == 'true'
        uses: peter-evans/create-pull-request@v6
        with: { branch: kaeris/i18n-updates, title: "i18n: new translations" }

Ready-to-copy workflow in the repo: .github/workflows/translate.example.yml — pair with i18n-check.example.yml as a branch-protection required check.

Reproducible by design

deterministic

Run it again, get the same result. kaeris.lock records a hash of every source string, your settings and the model that produced them, so unchanged strings are never re-translated — the diff only ever contains what you actually changed. No surprise churn, no noisy PRs.

Edit one stringOnly that string's translations change — everything else stays byte-for-byte identical.
Change tone or glossaryThe whole locale re-translates so it never mixes old and new settings.
Change planYour plan picks the model, and the lock records it — switching re-translates rather than mixing two models.
Stable outputA pinned model seed keeps even fresh translations as consistent as the model allows.

Get your free API key

Free API keys unlock 50,000 characters per file (vs 10,000 anonymous), no account required. Paid keys raise this: Pro 200k, Scale 500k, Team/API 1M, Lifetime 1M (BYOK) — see pricing.

Integrate in 3 steps

Quick start

1

Upload file → get job_id

bash
# POST /api/translate
curl -X POST https://kaeris.dev/api/translate \
  -H "X-API-Key: kaeris_your_key_here" \
  -F "[email protected]" \
  -F "languages=ru,de,fr,es,zh,ja"

# Response:
{ "job_id": "3f8a2c1d-..." }
2

Poll until done

bash
# GET /api/status/{job_id}
curl https://kaeris.dev/api/status/3f8a2c1d-...

# Response when done:
{ "status": "done", "progress": 6, "total": 6, "error": "" }
3

Download ZIP

bash
# GET /api/download/{job_id}
curl -O https://kaeris.dev/api/download/3f8a2c1d-...

# Saves translations.zip with ru.json, de.json, fr.json ...
Copy-paste ready

Python example

translate.py
import requests, time

API_KEY = "kaeris_your_key_here"
HEADERS = {"X-API-Key": API_KEY}

# 1. Upload
with open("en.json", "rb") as f:
    r = requests.post(
        "https://kaeris.dev/api/translate",
        headers=HEADERS,
        files={"file": f},
        data={"languages": "ru,de,fr,es,zh,ja"},
    )
job_id = r.json()["job_id"]

# 2. Poll
while True:
    status = requests.get(f"https://kaeris.dev/api/status/{job_id}").json()
    if status["status"] == "done": break
    time.sleep(2)

# 3. Download
zip_bytes = requests.get(f"https://kaeris.dev/api/download/{job_id}").content
open("translations.zip", "wb").write(zip_bytes)
Reference

API Reference

POST /api/translate

Start a translation job. Returns job_id.

X-API-KeyHeader — optional. Raises limit to 50k chars (Pro: 200k; Team/API 1M).
filemultipart — JSON file to translate
languagesform field — comma-separated codes: ru,de,fr
GET /api/status/{job_id}

Poll job progress. Status: pending | running | done | error.

progressbatches completed so far
totaltotal batches
GET /api/download/{job_id}

Download ZIP with ru.json, de.json etc. Available for 1 hour after completion.

POST /api/v1/keys/request

Generate a new API key. No auth required.

Coverage

Supported languages

46 languages
🇬🇧 en English
🇷🇺 ru Russian
🇺🇦 uk Ukrainian
🇩🇪 de German
🇫🇷 fr French
🇪🇸 es Spanish
🇧🇷 pt Portuguese
🇮🇹 it Italian
🇨🇳 zh Chinese
🇯🇵 ja Japanese
🇰🇷 ko Korean
🇸🇦 ar Arabic
🇵🇱 pl Polish
🇳🇱 nl Dutch
🇹🇷 tr Turkish
🇮🇳 hi Hindi
🇮🇩 id Indonesian
🇻🇳 vi Vietnamese
🇹🇭 th Thai
🇲🇾 ms Malay
🇧🇩 bn Bengali
🇮🇷 fa Persian
🇮🇱 he Hebrew
🇸🇪 sv Swedish
🇨🇿 cs Czech
🇷🇴 ro Romanian
🇭🇺 hu Hungarian
🇬🇷 el Greek
🇩🇰 da Danish
🇫🇮 fi Finnish
🇳🇴 no Norwegian
🇸🇰 sk Slovak
🇭🇷 hr Croatian
🇧🇬 bg Bulgarian
🇷🇸 sr Serbian
🇱🇹 lt Lithuanian
🇱🇻 lv Latvian
🇪🇪 et Estonian
🇸🇮 sl Slovenian
🇪🇸 ca Catalan
🇵🇭 tl Filipino
🇰🇪 sw Swahili
🇹🇼 zh-Hant Traditional Chinese
🇲🇽 es-419 Latin Am. Spanish
🇵🇹 pt-PT European Portuguese
🇨🇦 fr-CA Canadian French