Developers
How to integrate Localize.to with your app, site or build pipeline.
Overview
Localize.to stores your translation keys and their values in every language you support. Your app fetches them at build time or at runtime over a simple REST API. Each project gets its own API key, so you can ship integrations without exposing any account credentials.
Core concepts
- Workspace — a container for related projects; teams are managed at the workspace level.
- Project — the unit of integration. One app or site = one project.
- Language — each project enables a set of ISO language codes (
en,sk,uk, …). - Key & value — a key is an identifier (
sign_in); its values are the translated strings, one per language. - API keys — every project has two keys with distinct roles: a read key for fetching strings and a write key for managing them. See below.
The two API keys: read and write
Open any project, switch to the API tab, and you will see two keys. Their privileges are hierarchical — the write key can do everything the read key can, plus writes:
- Read key (
apikey) — reads only. Passed as a query parameter:?apikey=READ_KEY. Safe to bundle into a client build because it cannot modify anything. - Write key (
apikey2) — reads and writes. Passed as an HTTP header:x-apikey-write: WRITE_KEY. Authorizes create/update/rename/delete and also authenticates every read endpoint, so a single integration that needs both can use only this key. Treat it like any other secret — store it in an environment variable or secrets manager, never commit it, and never ship it to the client.
The two keys are independent and either can be regenerated without affecting the other. Use the read key for anything that touches the client or a URL (its leakage is low-impact); keep the write key server-side.
Quickstart: fetch one language
curl "https://api.localize.to/v1/language/en?apikey=READ_KEY"Response (default format is JSON):
{
"sign_in": "Sign in",
"sign_out": "Sign out",
"projects": "Projects"
}Build-time integration (Node)
The simplest integration is a short script that pulls every language into your repo at build time. This project uses exactly that pattern — see src/i18n/ls.mjs:
import { mkdir, writeFile } from 'node:fs/promises'
import { join } from 'node:path'
const config = {
targetPath: 'src/i18n/data',
apiKey: process.env.LOCALIZE_READ_KEY,
baseUrl: 'https://api.localize.to/v1',
locales: ['en', 'sk', 'uk'],
}
await mkdir(config.targetPath, { recursive: true })
await Promise.all(
config.locales.map(async (lang) => {
const url = `${config.baseUrl}/language/${lang}?apikey=${config.apiKey}`
const res = await fetch(url)
if (!res.ok) throw new Error(`${lang}: ${res.status}`)
await writeFile(join(config.targetPath, `${lang}.json`), await res.text())
}),
)Wire it as yarn ls (or any script name) and run it before your build. The read key is safe to keep in a repo variable, but storing it in an environment variable is still a good habit.
Output formats
Add &format= to any language endpoint to get a different representation:
json— default; flat{ key: value }object.php— areturn [...]associative array.ios— Apple.stringsfile, ready for Xcode.android— Androidstrings.xmlresource file.xml— generic XML.csv— spreadsheet-friendly export.
Add &nested=true to json or php output to split dotted keys (auth.login) into nested objects/arrays. Other formats ignore the flag. If a flat key is both a leaf and a prefix (e.g. auth and auth.login), the leaf wins and the deeper key is dropped.
curl "https://api.localize.to/v1/language/en?apikey=READ_KEY&nested=true"{
"auth": { "login": "Sign in", "logout": "Sign out" },
"projects": "Projects"
}Writing translations programmatically
Use the write key in the x-apikey-write header to create or update keys from CI, a CMS hook, or a migration script:
curl -X PUT "https://api.localize.to/v1/translations" \
-H "x-apikey-write: WRITE_KEY" \
-H "Content-Type: application/json" \
-d '{
"lkey": "nav.home",
"values": { "en": "Home", "sk": "Domov" }
}'See the API reference for the full payload shape, the delete and rename endpoints, and the ownership model that protects human edits.
Letting an AI assistant manage your translations
The write API is designed to be driven by AI coding assistants (Claude Code, Cursor, Copilot, custom agents). Instead of hardcoding English strings into your app, the AI adds new keys through the API as it writes UI code, and references them by lkey.
The quickest way to onboard an assistant is to point it at https://api.localize.to/llms.txt. That URL always serves the current, detailed behavior contract — hand the link to Claude Code / Cursor / your agent and ask it to read and follow the document. Because it's fetched live from the API, it stays in sync with any future endpoint changes.
If your setup needs inline context (no network fetch from the agent), paste the condensed block below into your project's system prompt, AGENTS.md, CLAUDE.md, or the equivalent file for your tool. Set the two environment variables it refers to to your project's read and write keys.
# Translations
This project localizes strings through the Localize.to API. Do NOT hardcode
user-facing English text in the app source — create a key through the API
and reference it by `lkey` in the UI.
Base URL: https://api.localize.to/v1
Two API keys (both identify the same project, privileges are hierarchical):
- Read key — reads only. Query param `?apikey=$LOCALIZE_READ_KEY` (bundled with app)
- Write key — reads AND writes. HTTP header `x-apikey-write: $LOCALIZE_WRITE_KEY` (secret).
Authenticates every endpoint below — you only need this one key.
Ownership rule: every key/value has an `ai` boolean. Rows you write are
`ai=true`. Rows a human edits become `ai=false` permanently. You may
always CREATE new keys and new language values. You may MODIFY or DELETE
only rows that are currently `ai=true`. Touching a human-owned row
returns HTTP 409 — do not retry, surface to the human instead.
Workflow when adding a new UI string:
1. Pick a stable dotted lkey (e.g. `screens.checkout.submit_button`).
2. (Optional but recommended) GET /v1/translations/{lkey} with the write
key — confirms whether the key already exists and who owns it.
3. PUT /v1/translations with the write key and body:
{ "lkey": "...", "description": "...", "values": { "en": "..." } }
Batch every language you have in one call.
4. Reference `lkey` in the UI code. Do NOT inline the English text.
Endpoints (all under /v1, all JSON):
- GET /translations/{lkey} — inspect one key (write key)
- PUT /translations — create or update (write key)
- DELETE /translations — delete key or one language (write key)
- POST /translations/rename — rename key (write key)
- GET /language/{lang}?apikey=.. — fetch a full language (read key)
Errors: 400 bad body, 404 missing write header / not found,
409 bad write key OR ownership-rule rejection. Retry 5xx with backoff,
never retry 409.
Do not store the write key in the repo, in screenshots, or in chat logs.
Do not invent numeric IDs — the API is lkey-addressed.The block above is a condensed preview. For the full behavior contract (workflows, edge cases, error handling), the authoritative source is https://api.localize.to/llms.txt.
Rate limits and errors
Requests are rate-limited per API key. On error, the API returns a standard HTTP status code and a JSON body with an error field. Full status-code reference on the API page.
Next steps
- Full endpoint reference: API.
- Questions or missing features: contact us.