Skip to main content

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.