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

LevelCapabilityUsed By
PublicNoneGET /languages, GET /languages/{slug}
Translateperflocale_translateTranslation CRUD, strings list, workflow
Machine Translationperflocale_use_mtPOST /machine-translate
Languagesperflocale_manage_languagesLanguage create/update/delete, MT test
Import/Exportperflocale_import_exportImport, XLIFF export/import
Adminmanage_optionsString 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: 50000 characters.
  • 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 via perflocale/mt/rate_limit).
  • When source_lang equals target_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" }