Backend API
Server-to-server reads across a plugin's end users, authenticated with the Plugin ID and Secret.
Read-only endpoints for your own server. Every embedded endpoint answers for one end user; this surface reads every user's work, so it is authenticated with the Plugin Secret and refuses browsers. The integration guide is Backend API.
Paths use the prefix /plugin/v1/backend, not /public/plugin/v1. No CORS headers are sent.
Authentication
X-Plugin-Id: 9f3c1e7a-4b21-4c8e-9a77-1d2f5b6c8e10
X-Plugin-Secret: your_plugin_secretSend both headers on every request; either one alone is refused. A session token is not accepted.
curl https://api.maileditor.net/api/plugin/v1/backend/users \
-H 'X-Plugin-Id: 9f3c1e7a-4b21-4c8e-9a77-1d2f5b6c8e10' \
-H 'X-Plugin-Secret: your_plugin_secret'X-Plugin-Secret is not redacted by logging tools the way Authorization often is. If you log request headers, add it to your scrubbing list. Never put these credentials in a browser or a query string; query-string credentials are refused even when correct.
If your backend needs to act as one user rather than read about all of them, create a session with external_user_id set through the session endpoint and use the embedded endpoints.
Errors
| Status | error | Meaning |
|---|---|---|
| 401 | credentials_required | One or both headers missing |
| 401 | invalid_credentials | Wrong secret, unknown plugin, or a suspended plugin |
| 401 | credentials_in_query | Credentials sent as query parameters |
| 409 | plugin_not_provisioned | The plugin's storage is still being set up. Retry shortly |
| 429 | Rate limited. 60 per minute and 2000 per day per plugin id, failed attempts included |
List users
GET /plugin/v1/backend/users| Query | Notes |
|---|---|
search | Substring of the id |
per_page | Default 25, capped at 100 |
200
{
"message": "End users retrieved successfully",
"result": {
"users": {
"data": [
{
"external_user_id": "user-42",
"template_count": 7,
"first_template_at": "2026-06-01T09:14:22.000000Z",
"last_activity_at": "2026-08-28T16:02:11.000000Z"
}
],
"links": { "…": "…" },
"meta": { "…": "…" }
}
}
}Lists end users who have saved at least one template. A user who opened the editor and saved nothing does not appear, and anonymous sessions are excluded. last_activity_at is their last save. MailEditor holds no name or email; external_user_id is what you sent, echoed back.
List templates
GET /plugin/v1/backend/templates| Query | Notes |
|---|---|
external_user_id | One user's work |
search | On name |
updated_after, updated_before | Any parseable date. For incremental sync |
per_page | Default 25, capped at 100 |
200 returns result.templates, paginated, each entry in the same shape as the embedded list. Without external_user_id you get every end user's templates.
Template details
GET /plugin/v1/backend/templates/{template}200 returns result.template, including its external_user_id.
404 template_not_found for an unknown id or one belonging to another plugin.
Template HTML
GET /plugin/v1/backend/templates/{template}/html200
{
"result": {
"template_id": 41,
"external_user_id": "user-42",
"source": "draft",
"html": "<!doctype html>…"
}
}A draft saved seconds ago reads back here. source is draft until the file has been written, then media. The markup is current either way.

.png)