REST API
PerfLocale exposes a RESTful API under the perflocale/v1 namespace for managing languages, translations, strings, imports, and more.
All endpoints require authentication via WordPress cookies or application passwords. Write operations require appropriate capabilities.
Base URL: /wp-json/perflocale/v1/
Authentication & Permissions
| Level | Capability | Used By |
|---|---|---|
| Public | None | GET /languages, GET /languages/{slug} |
| Translate | perflocale_translate | Translation CRUD, strings list, workflow |
| Machine Translation | perflocale_use_mt | POST /machine-translate |
| Languages | perflocale_manage_languages | Language create/update/delete, MT test |
| Import/Export | perflocale_import_export | Import, XLIFF export/import |
| Admin | manage_options | String scan, webhooks |
All write endpoints are protected by WordPress REST nonce verification.
Restricting Public Reads
The language endpoints (GET /languages, GET /languages/{slug}) are public by default because every field they expose - slug, locale, name, native name, flag, RTL flag, default flag - is already rendered to anonymous visitors via the language switcher, hreflang tags, and URL structure. There is nothing private to protect.
Site owners who still want to require authentication for these reads can hook the perflocale/api/languages_public filter. When it returns false, both endpoints require the read capability (any logged-in user). Write endpoints are unaffected - they always require perflocale_manage_languages.
// Drop this in a MU-plugin or functions.php to lock down /languages.
add_filter( 'perflocale/api/languages_public', '__return_false' );
The GET /config endpoint is already opt-in: the route is only registered when Edge Worker Integration is enabled in Settings → Advanced. On a fresh install it responds with 404.
Languages
List Languages
GET /perflocale/v1/languages
Returns all configured languages. Public by default - no authentication required. Can be gated behind login via the perflocale/api/languages_public filter.
Response:
{
"success": true,
"data": {
"languages": [
{
"id": 1,
"slug": "en",
"locale": "en_US",
"name": "English",
"native_name": "English",
"flag": "en",
"is_default": false,
"is_active": true,
"text_direction": "ltr"
}
]
}
}
Get Single Language
GET /perflocale/v1/languages/{slug}
Parameters:
slug(string, required) - Language slug (e.g.en,fr,de).
Create Language
POST /perflocale/v1/languages
Requires: perflocale_manage_languages
Body (JSON):
{
"slug": "fr",
"locale": "fr_FR",
"name": "French",
"native_name": "Français",
"flag": "fr",
"is_active": true,
"text_direction": "ltr"
}
Update Language
PUT /perflocale/v1/languages/{slug}
Requires: perflocale_manage_languages
Body: Same fields as create (partial updates supported).
Delete Language
DELETE /perflocale/v1/languages/{slug}
Requires: perflocale_manage_languages
Translations
Get Translations for a Post
GET /perflocale/v1/translations/post/{id}
Requires: edit_post capability for the specific post.
Response:
{
"success": true,
"data": {
"languages": [
{
"slug": "en",
"language_id": 1,
"name": "English",
"native_name": "English",
"is_current": true,
"is_default": false,
"has_translation": true,
"post_id": 42,
"status": "published",
"status_label": "Published",
"edit_url": "https://example.com/wp-admin/post.php?post=42&action=edit",
"workflow": null,
"assigned_to": 0,
"priority": "normal",
"deadline": ""
},
{
"slug": "de",
"language_id": 3,
"has_translation": false,
"post_id": null,
"status": null
}
]
}
}
Also supports terms:
GET /perflocale/v1/translations/term/{id}
Create Translation
POST /perflocale/v1/translations/post/{id}
Requires: edit_post capability for the source post.
Body:
{
"target_lang": "de",
"copy_content": true
}
Response:
{
"success": true,
"data": {
"post_id": 99,
"edit_url": "https://example.com/wp-admin/post.php?post=99&action=edit"
}
}
Update Translation
PUT /perflocale/v1/translations/post/{id}/{lang}
Requires: edit_post capability for the translated post.
Body (partial updates supported):
{
"title": "Translated Title",
"content": "<p>Translated content.</p>",
"excerpt": "Short description",
"status": "publish"
}
Delete Translation
DELETE /perflocale/v1/translations/post/{id}/{lang}
Requires: delete_post capability for the translated post.
Permanently deletes the translated post and removes the translation link.
Workflow
Only available when the workflow feature is enabled in settings.
Update Workflow Status
PUT /perflocale/v1/workflow/{post_id}/{language_id}
Requires: perflocale_manage_translations
Body:
{
"status": "assigned",
"assigned_to": 5,
"priority": "high",
"deadline": "2026-04-15",
"notes": "Please translate by end of week."
}
Available statuses: unassigned, assigned, in_progress, review, approved, published
Get Translators
GET /perflocale/v1/workflow/users
Returns users with perflocale_translate or perflocale_manage_translations capabilities.
Response:
{
"success": true,
"data": {
"users": [
{ "id": 5, "name": "Jane Translator" }
]
}
}
Strings
List Translatable Strings
GET /perflocale/v1/strings
Requires: perflocale_translate
Query Parameters:
domain(string) - Filter by text domain (e.g.woocommerce).per_page(int) - Results per page. Default: 50, max: 100.offset(int) - Pagination offset. Default: 0.
Response:
{
"success": true,
"data": {
"strings": [
{
"id": 1,
"domain": "woocommerce",
"context": "",
"original": "Add to cart",
"file_path": "templates/single-product/add-to-cart.php",
"line_number": 42
}
]
}
}
Scan for Strings
POST /perflocale/v1/strings/scan
Requires: manage_options
Body:
{
"target": "plugins",
"domain": "woocommerce"
}
Available targets: theme, plugins, parent
Machine Translation
Only available when machine translation is enabled in settings.
Translate a Post
POST /perflocale/v1/machine-translate
Requires: perflocale_use_mt
Body:
{
"post_id": 42,
"target_lang": "de",
"provider": "deepl"
}
Available providers: deepl, google, microsoft, libretranslate (depends on configuration).
Translate a Single Text Block
POST /perflocale/v1/block-translate
Requires: perflocale_use_mt
Stateless single-string translation - no post, no group, no DB writes. Powers the Gutenberg block-toolbar “Translate with MT” action but usable by any caller that wants to translate a snippet without touching the post store.
Body:
{
"text": "Hello, world.",
"source_lang": "en",
"target_lang": "de",
"provider": "deepl"
}
Response:
{
"success": true,
"data": {
"translated": "Hallo, Welt.",
"provider": "deepl"
}
}
- Maximum input length:
50000characters. - Provider response is passed through the same
wp_kses_post-based allowlist used by post translation, so HTML snippets are safe to apply to block attributes. - Shares the per-user hourly budget with
/machine-translate(500/hour by default, filterable viaperflocale/mt/rate_limit). - When
source_langequalstarget_lang, returns the input unchanged.
Import / Export
Upload Import File
POST /perflocale/v1/import/upload
Requires: perflocale_import_export
Body: multipart/form-data with file field import_file.
Returns a token for batch processing.
Process Import Batch
POST /perflocale/v1/import/batch
Requires: perflocale_import_export
Body:
{
"token": "abc123def456",
"table": "languages",
"offset": 0,
"replace": false
}
Processes 100 rows per batch. Call repeatedly with increasing offset until complete.
Cleanup Import
POST /perflocale/v1/import/cleanup
Requires: perflocale_import_export
Removes temporary import files.
XLIFF
Export XLIFF
POST /perflocale/v1/xliff/export
Requires: perflocale_import_export
Body:
{
"post_ids": [42, 43, 44],
"source_lang": "en",
"target_lang": "de"
}
Returns XLIFF 1.2 XML content.
Import XLIFF
POST /perflocale/v1/xliff/import
Requires: perflocale_import_export
Body:
{
"xliff": "<?xml version=\"1.0\"?>...",
"target_lang": "de"
}
Webhooks
Register Webhook
POST /perflocale/v1/webhooks
Requires: manage_options
Body:
{
"url": "https://example.com/webhook",
"events": ["translation.created", "translation.updated"],
"secret": "my-secret-key"
}
Available events: translation.created, translation.updated, content.changed
Delivery headers:
X-PerfLocale-Signature-sha256=<hmac>for payload verification.X-PerfLocale-Event- event name (e.g.translation.created).X-PerfLocale-Delivery-ID- unique UUID per delivery attempt.X-PerfLocale-Attempt- delivery attempt number (1, 2, or 3).
Retry behaviour: If your endpoint returns a non-2xx status or is unreachable, PerfLocale retries automatically via WP-Cron - after 30 seconds (attempt 2) and after 5 minutes (attempt 3). After three failures the event is written to the perflocale_webhook_failures WordPress option so admins can inspect it. A successful delivery clears any prior failures for that webhook.
List Webhooks
GET /perflocale/v1/webhooks
Requires: manage_options
Delete Webhook
DELETE /perflocale/v1/webhooks/{id}
Requires: manage_options
Error Responses
All error responses follow a consistent format:
{
"code": "error_code",
"message": "Human-readable error description.",
"data": {
"status": 400
}
}
Common error codes:
rest_forbidden(403) - Insufficient permissions.invalid_type(400) - Invalid object type parameter.not_found(404) - Translation or resource not found.missing_lang(400) - Required language parameter missing.create_failed(500) - Server error during creation.rate_limited(429) - Machine-translation per-user hourly cap reached.
Edge Configuration
Public JSON describing URL mode, default language, detection order, and the full active-language matrix. Used by Cloudflare Workers / Vercel Edge / Netlify Edge to pre-route visitors before PHP.
Availability: only registered when the Edge Worker Integration toggle is on (Settings → Advanced) or the perflocale/edge/enabled filter returns true.
GET /perflocale/v1/config
Public endpoint, no authentication. Response is sent with Cache-Control: public, max-age=300, s-maxage=3600, stale-while-revalidate=86400 and an ETag so edges can revalidate cheaply.
{
"version": "1.0.0",
"url_mode": "subdirectory",
"url_prefix_type": "slug",
"default_slug": "en",
"hide_default_prefix": true,
"excluded_paths": [ "/wp-json/", "/wp-admin/", "/wp-login.php" ],
"detection_order": [ "url", "cookie", "browser", "default" ],
"edge_hint_header": "X-PerfLocale-Lang",
"edge_hint_cookie": "perflocale_edge_lang",
"languages": [
{
"slug": "en",
"locale": "en_US",
"hreflang": "en-us",
"prefix": "en",
"domain": "",
"text_direction": "ltr",
"is_default": true
}
]
}
A reference Cloudflare Worker implementation ships in assets/js/edge-helper.js inside the plugin folder.
Translation Memory
Programmatic access to the fuzzy translation-memory store. All endpoints require the perflocale_translate capability except DELETE which is admin-only.
POST /perflocale/v1/translation-memory/suggest
Returns the best exact + fuzzy matches for a snippet. Fuzzy ranking uses similar_text() on up to 100 LIKE-filtered candidates, returning the top N by similarity then usage count.
POST /wp-json/perflocale/v1/translation-memory/suggest
{
"text": "Thanks for subscribing!",
"source_lang": "en",
"target_lang": "fr",
"limit": 5
}
// Response
{
"exact": "Благодарим за абонамента!",
"suggestions": [
{
"source": "Thanks for subscribing",
"target": "Благодарим за абонамента",
"similarity": 94.5,
"usage_count": 12
}
]
}
GET /perflocale/v1/translation-memory
Paginated browse/search of stored entries.
Query params: source_lang, target_lang, search, page (default 1), per_page (default 50, max 200).
DELETE /perflocale/v1/translation-memory/{id}
Remove one TM entry. Requires manage_options capability.
Glossary
Auto-glossary scanner + candidate review. All endpoints require perflocale_manage_glossary. Only registered when Translation Glossary is enabled (Settings → Addons → Machine Translation).
POST /perflocale/v1/glossary/scan
Run a synchronous scan over published content. Extracts capitalized multi-word phrases that appear ≥ 3 times, filters stop-words and already-in-glossary terms, caches the result in a transient (1 day).
POST /wp-json/perflocale/v1/glossary/scan
{ "limit": 500 }
// Response
{
"count": 42,
"candidates": [
{ "term": "Acme Studio", "occurrences": 18, "example_post_id": 1234 }
]
}
Post count is clamped to [10, 5000] and further filterable via perflocale/glossary/scan_limit.
GET /perflocale/v1/glossary/candidates
Return the cached candidates from the last scan without re-scanning.
POST /perflocale/v1/glossary/candidates/accept
POST /wp-json/perflocale/v1/glossary/candidates/accept
{
"term": "Acme Studio",
"target": "Acme Studio",
"source_lang": "en",
"target_lang": "fr",
"case_sensitive": true
}
Accepts a candidate into the glossary (via Glossary::add()) and prunes it from the candidate cache.
POST /perflocale/v1/glossary/candidates/reject
Remove a candidate from the cache without adding to the glossary.
POST /wp-json/perflocale/v1/glossary/candidates/reject
{ "term": "Not A Brand" }