Automating Weblate with Its REST API: What Actually Works in a CI Pipeline
Weblate's REST API handles CI automation well, but token scoping, plural-form validation, and batch limits trip up first integrations. Here's what actually works.
23 Jul 2025, 04:33 UTC

Your translators work in Weblate, but your release pipeline needs to seed new source strings, backfill approved translations, and audit untranslated units without anyone clicking through a web UI. The good news: Weblate's REST API covers all of this. The less-good news: the parts that bite you in automation — token scoping, plural forms, and batch payload limits — are exactly the parts the quick-start examples skip.
This post walks through the API patterns that hold up in a CI context, with a worked example you can adapt, plus the trade-offs worth knowing before you hand a token to a build job.
Authentication: tokens, and why scope matters
Weblate uses token-based authentication. Each user generates a personal API token from their profile settings, and scripts pass it in the Authorization header:
curl -H "Authorization: Token wlu_your_token_here" \
https://weblate.example.com/api/components/Run this from any machine that can reach your Weblate instance; no special permissions are needed beyond what the token's owning user already has. A successful check returns a paginated JSON list of components — if you get a 401, the token is wrong or revoked; a 403 means the user lacks project permissions.
The catch: a token inherits the owning user's full permissions. A token created from an admin account can read and modify every translation on the instance. For CI use, create a dedicated service account with access limited to the projects your pipeline touches, and generate the token from that account. If your instance sits behind a reverse proxy, an IP allowlist for the API path adds a second layer — a leaked token alone shouldn't be enough to write translations from anywhere on the internet.
Endpoint patterns you'll actually use
The API is consistent and predictable. Two families cover most automation work:
/api/components/— list, inspect, and trigger operations on translation components (a component maps roughly to one translatable file in your repo)./api/translations/and string-level endpoints — read and modify individual translation units.
Component URLs follow the pattern /api/components/<project>/<component>/, so once you know your project and component slugs, every other call is mechanical. Pagination is standard: responses include next and previous links, and any script that iterates must follow them or it will silently process only the first page. That last point has caused real bugs — a "sync everything" job that syncs 50 of 900 strings looks successful until someone counts.
Worked example: pushing reviewed strings from CI
A common pipeline task: after a review step elsewhere, mark a set of strings as approved in Weblate. The batch-capable endpoints let you update multiple units per request rather than one call per string.
Conceptually, the flow is:
- GET the translation's string list, filtering by the source text or string state you care about.
- Build a small batch of updates (string ID plus target state/text).
- POST the batch, check the per-item results, and retry failures with backoff.
# Step 1: find untranslated strings in one translation
# Run: CI runner with network access to Weblate; needs the service-account token.
curl -s -H "Authorization: Token $WEBLATE_TOKEN" \
"https://weblate.example.com/api/translations/myapp/web/fr/units/?q=state:empty" \
| jq '.results[] | {id, source, target}'Verify the response shape against your instance before scripting around it — field names and filtering syntax are stable in recent Weblate versions, but self-hosted instances can lag behind or carry extensions. Checking one real response takes two minutes and saves a debugging session.
For writes, keep batches modest. Very large payloads can be rejected or time out, and a half-applied mega-batch is harder to reconcile than a failed small one. Chunking into groups of a few dozen strings, with a retry loop that respects rate limits, is the pattern that survives contact with production. Rate limiting is enforced per token, so a burst-parallel script will throttle itself; sequential-with-retry is usually fast enough and far easier to reason about.
Plural forms: the validation trap
Weblate follows gettext conventions for plurals, and it validates before committing. If a language's plural rule expects two forms (singular and plural) and your API payload supplies one — or three — the update fails with a validation error rather than silently writing a broken catalog.
This is good behavior, but it means automation that synthesizes translations (machine translation prefill is the classic case) must know the target language's plural-form count and emit exactly that many variants. The safe approach: read the existing unit first, mirror its plural structure, and only replace the text values. Never assume English's two-form shape generalizes — Russian needs three forms, Arabic six, Japanese one.
The trade-off: API writes vs. repository truth
Weblate's model is that the version-control repository is the source of truth and Weblate commits changes back to it. API writes go into Weblate's working state and get committed on its schedule. Two implications:
- Your pipeline should treat Weblate as a system that eventually updates the repo, not as the repo itself. If you need strings in a release branch now, pull the committed changes rather than assuming an API write has landed in git.
- Concurrent edits — a translator typing in the UI while your script batches updates — are handled by Weblate's merge logic, but genuinely conflicting changes to the same unit can still surprise you. Prefer updating strings that are unlikely to be under active human edit, e.g. newly added source strings.
Before you ship the integration
Three checks, all cheap: confirm GET /api/components/ returns the schema you scripted against; run one small batch write against a throwaway project and verify the result in the UI; and push a plural-form update for a non-English language to confirm your payload structure passes validation. If all three pass on your instance, the rest of the integration is plumbing.
Start with the read-only endpoints — auditing untranslated strings is useful on day one and can't break anything — then graduate to writes once the token scoping and retry behavior are proven. Your future self, staring at a failed release job, will appreciate the order of operations.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.