API Reference
REST endpoints under https://api.localize.to/v1. For a gentler introduction, see the Developers page.
Base URL
- Base URL:
https://api.localize.to - All requests must be HTTPS.
Authentication — two keys per project
Every project has two independent API keys. They both identify the same project and their privileges are hierarchical:
- Read key (
apikey) — reads only. Passed as a query parameter:?apikey=READ_KEY. Safe to bundle into a client build. - Write key (
apikey2) — reads and writes. Passed as an HTTP header:x-apikey-write: WRITE_KEY. Authenticates every write endpoint and every read endpoint too, so an integration that needs both can use only this key. Treat it like any other secret — never commit it, never ship it to the client.
Both keys are visible in the project's API tab and each can be regenerated independently.
Read endpoints
Authenticate with either key: ?apikey=READ_KEY as a query parameteror x-apikey-write: WRITE_KEY as a header. The write key can replace the read key everywhere — the reverse is not true.
GET /v1/project
Returns project metadata, enabled languages, and basic stats.
curl "https://api.localize.to/v1/project?apikey=READ_KEY"{
"id": 1,
"name": "Demo project",
"workspace": { "id": 2, "name": "Demo workspace" },
"account": { "id": 1, "name": "Demo account" },
"languages": ["en", "sk"],
"stats": { "keys": 97, "translations": { "en": 97, "sk": 78 } }
}GET /v1/language/{language}
Returns all translations for a single language.
Query parameters: format — one of json (default, pretty-printed), php, ios, android (same as xml), csv. separator and crlf apply to csv. nested=true splits dotted keys into nested objects/arrays for json and php; ignored by other formats. Leaf wins on collisions.
curl "https://api.localize.to/v1/language/en?apikey=READ_KEY&format=json"{
"sign_in": "Sign in",
"sign_out": "Sign out",
"projects": "Projects"
}GET /v1/languages/{lang1,lang2}
Returns translations for the given comma-separated subset of languages. Same format options as above (json, php, csv). nested=true applies per-language to json and php.
curl "https://api.localize.to/v1/languages/en,sk?apikey=READ_KEY"{
"en": { "sign_in": "Sign in", "sign_out": "Sign out" },
"sk": { "sign_in": "Prihlásiť sa", "sign_out": "Odhlásiť sa" }
}GET /v1/languages
Same as above but returns every enabled language.
Write endpoints — use the write key
All write endpoints (and the single-key inspection endpoint) are authenticated with the x-apikey-write header. Request/response bodies are JSON.
GET /v1/translations/{lkey}
Returns the current state of a single key across all languages, including ownership flags. Use this before a write to verify what's there.
curl "https://api.localize.to/v1/translations/nav.home" \
-H "x-apikey-write: WRITE_KEY"{
"id": 123,
"lkey": "nav.home",
"description": "Label for the Home link in the top nav",
"ai": true,
"values": {
"en": { "text": "Home", "ai": true },
"sk": { "text": "Domov", "ai": false }
}
}URL-encode the lkey if it contains anything other than letters, digits, dots, dashes, and underscores.
PUT /v1/translations
Create a key or update an existing one. Idempotent — safe to retry. All fields other than lkey are optional; send only what you want to set.
Query parameter: overwrite=true (default) updates existing AI-owned values and descriptions; overwrite=false only fills in missing ones.
curl -X PUT "https://api.localize.to/v1/translations" \
-H "x-apikey-write: WRITE_KEY" \
-H "Content-Type: application/json" \
-d '{
"lkey": "nav.home",
"description": "Label for the Home link in the top nav",
"values": {
"en": "Home",
"sk": "Domov"
}
}'Response is the full post-update state, same shape as the GET above.
DELETE /v1/translations
Delete either a single language value or an entire key with all its values.
# delete just one language
curl -X DELETE "https://api.localize.to/v1/translations" \
-H "x-apikey-write: WRITE_KEY" \
-H "Content-Type: application/json" \
-d '{ "lkey": "nav.home", "language": "sk" }'
# delete the whole key
curl -X DELETE "https://api.localize.to/v1/translations" \
-H "x-apikey-write: WRITE_KEY" \
-H "Content-Type: application/json" \
-d '{ "lkey": "nav.home" }'Responds with 201 and an empty body on success.
POST /v1/translations/rename
Rename a key while preserving all its values.
curl -X POST "https://api.localize.to/v1/translations/rename" \
-H "x-apikey-write: WRITE_KEY" \
-H "Content-Type: application/json" \
-d '{ "lkey": "nav.home", "new_lkey": "nav.homepage" }'Fails with 400 if new_lkey already exists, 409 if the key is human-owned.
Ownership model (the ai flag)
Every key and every language value carries an ai boolean. Rows written through the write API are stored with ai=true; rows written by a human in the web UI are stored with ai=false. A human edit flips a row to ai=false permanently.
- The write API may always CREATE new keys and new language values.
- The write API may MODIFY or DELETE only rows that are currently
ai=true. - Attempts to modify or delete a human-owned (
ai=false) row return 409 and are not applied.
This protects human edits from being silently reverted by an automated integration.
Errors
Standard JSON error shape: { "error": "human-readable message" }.
- 400 — malformed body, missing field, or rename target already exists.
- 404 — missing
x-apikey-writeheader, or key/value not found. - 409 — bad write key (project lookup failed) or an ownership rule rejected the change. The error message says which.
5xx responses are transient — retry with backoff. Never retry 409 with the same body; it will fail the same way.
End-to-end example
Fetch project metadata, pull the English strings, then add a new key:
# 1. confirm project + languages
curl "https://api.localize.to/v1/project?apikey=READ_KEY"
# 2. pull English strings into a local file
curl "https://api.localize.to/v1/language/en?apikey=READ_KEY" > en.json
# 3. add a new key (uses the write key in a header)
curl -X PUT "https://api.localize.to/v1/translations" \
-H "x-apikey-write: WRITE_KEY" \
-H "Content-Type: application/json" \
-d '{ "lkey": "nav.welcome", "values": { "en": "Welcome" } }'