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

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:

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:

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

Localize.to

Translation management for apps and websites — manage keys, languages, users, imports and exports in one place.

[email protected]