REST API Reference
All /api/* endpoints require the X-API-Key header except /api and /api/project.
X-API-Key: YOUR_24_CHAR_API_KEY
Project
GET /api
Health check. No auth required.
{ "name": "loco", "status": "running", "version": "1.0.0" }
GET /api/project
Get project info. No auth required.
{ "id": 1, "name": "My Project", "api_key": "...", "settings": {}, "created_at": "..." }
PATCH /api/project
Update project name.
// body
{ "name": "New Name" }
POST /api/project/regenerate-key
Generate a new API key. All existing clients will need to update their key.
PATCH /api/project/settings
Update project settings.
// body — all fields optional
{
"settings": {
"languages": ["fr", "zh-CN"],
"bluehiveApiKey": "BHSK-...",
"blockedTags": ["nav", "footer"],
"inlineTags": ["strong", "em"],
"domContextSelectors": ["[data-section]"],
"contextDepth": 3,
"domContextEnabled": true,
"screenshotsEnabled": false
}
}
GET /api/project/crawler-config
Get the config the CDN client uses for text discovery.
{
"blockedTags": [...],
"inlineTags": [...],
"domContextSelectors": [...],
"contextDepth": 3,
"domContextEnabled": true,
"screenshotsEnabled": false
}
POST /api/project/reset-database
Delete all phrases and translations for this project. Irreversible.
Text Nodes (Phrases)
POST /api/textnodes
Register discovered text nodes. Called automatically by loco.js.
// body
{ "keys": [{ "key": "Hello", "context": "" }], "url": "https://example.com" }
// response
{ "ok": true, "registered": 1, "keyMap": { "5 tasks": "{{number:0}} tasks" } }
keyMap contains remappings from concrete keys to slot patterns.
GET /api/translations/counts
Text node counts by status.
{ "pending": 42, "approved": 1800, "trash": 5 }
PATCH /api/textnodes/status
Bulk status update.
// body
{ "ids": [1, 2, 3], "status": "approved" }
// status: "pending" | "approved" | "trash"
GET /api/textnodes/untranslated-ids?lang=fr
IDs of approved phrases that have no translation for lang.
{ "ids": [5, 12, 99], "total": 3 }
POST /api/textnodes/bulk-delete
Delete phrases and their translations.
// body
{ "ids": [1, 2, 3] }
DELETE /api/textnodes/:id
Delete a single phrase and its translations.
PATCH /api/phrases/:id
Edit a phrase's key and variable slots.
// body
{ "key": "Updated text {{number:0}}", "varSlots": [{ "type": "number", "index": 0 }] }
POST /api/textnodes/clear-pending
Delete all pending (unapproved) phrases.
Translations
GET /api/translations?lang=fr
Fetch approved translations for a language. Used by loco.js at runtime.
[
{ "key": "Hello", "context": "", "value": "Bonjour" },
{ "key": "Delete", "context": "button", "value": "Supprimer" }
]
GET /api/translations/all
Paginated translation list for the dashboard.
Query params: lang, status (pending/approved/trash), search, search_field, translated (yes/no), page, limit
{ "rows": [...], "total": 1800, "page": 1, "limit": 50 }
POST /api/translations
Bulk upsert translations.
// body
{
"lang": "fr",
"translations": [
{ "key": "Hello", "context": "", "value": "Bonjour" }
]
}
DELETE /api/translations/:id
Delete a single translation by ID.
Languages
GET /api/languages
List languages configured for this project.
[{ "code": "fr", "name": "Français" }, { "code": "zh-CN", "name": "Simplified Chinese" }]
Export
GET /api/export
Export all approved translations (all languages).
GET /api/export/:lang
Export approved translations for a single language.
Both return:
{
"languages": ["fr", "zh-CN"],
"languageNames": { "fr": "Français", "zh-CN": "Simplified Chinese" },
"translations": {
"fr": [{ "key": "Hello", "context": "", "value": "Bonjour" }]
},
"timestamp": 1715000000000
}
AI Jobs
POST /api/ai-jobs
Create a bulk AI translation job.
// body
{ "key_ids": [1, 2, 3], "lang": "fr", "prompt_id": "uuid", "include_screenshots": false }
// response
{ "jobId": "job_abc123", "total": 3 }
GET /api/ai-jobs
List active jobs.
{ "jobs": [...], "stats": { "running": 1, "pending": 0, "total": 1 } }
GET /api/ai-jobs/:jobId
Job status and progress.
{
"id": "job_abc123", "status": "running",
"total": 150, "completed": 42, "failed": 0,
"estimatedTimeRemaining": 32000
}
DELETE /api/ai-jobs/:jobId
Cancel a running job.
GET /api/ai-jobs/log
Recent job history (last 50 entries).
Analytics
GET /api/analytics
{
"totalPhrases": 2150, "approvedPhrases": 1800, "pendingPhrases": 300, "trashPhrases": 50,
"uniqueUrls": 47, "totalTranslations": 9800, "languageCount": 5, "phrasesWithSlots": 320
}
GET /api/analytics/languages
Translation coverage per language.
[{ "lang": "fr", "translated": 1950, "coverage": 91 }]
GET /api/analytics/urls?search=&limit=50&offset=0
Phrase count per URL.
{ "total": 47, "rows": [{ "url": "https://...", "phraseCount": 184, "approved": 160, "pending": 24 }] }
GET /api/analytics/url-detail?url=https://...
Details for a specific URL.
{ "url": "...", "totalPhrases": 184, "approved": 160, "pending": 24, "pendingKeyIds": [...] }
GET /api/analytics/activity
Phrase discovery count per day (last 30 days).
[{ "day": "2026-05-19", "count": 42 }]
GET /api/analytics/phrase-detail?id=5
All translations for a single phrase.
{
"id": 5, "key": "Delete", "context": "button", "status": "approved",
"translations": [{ "lang": "fr", "value": "Supprimer" }]
}
Backups
GET /api/backups
List all backup files.
[{ "filename": "backup-2026-05-19.zip", "size": 204800, "created": "2026-05-19T12:00:00Z" }]
POST /api/backups
Create a new backup immediately.
DELETE /api/backups/:filename
Delete a backup file.
GET /api/backup
Download the current database as a binary SQLite file.