Contents
REST API - Plugixa Chat
Everything the admin app does, it does over REST, under the namespace
plugixa-chat/v1. Anything the screens can do is therefore scriptable.
The base address is /wp-json/plugixa-chat/v1/.
This is the API the plugin’s own screens and widget use. It is not a separately versioned public API, and the plugin makes no promise that routes, parameters or response shapes stay the same between versions. Pin the plugin version you build against, and test after updating.
Authentication
Most routes need a logged-in user. The plugin adds no authentication of its own for them. It relies on whatever WordPress provides, and then checks the capabilities of the user WordPress identified.
| Caller | How |
|---|---|
| The admin app | The logged-in cookie plus a REST nonce in the X-WP-Nonce header, the normal WordPress way |
| A script or another system | Any method WordPress accepts for REST requests, such as an application password for an account that holds the capabilities below |
| The widget on your site | The public routes, with a page token |
Permissions
Each route requires one of the plugin’s capabilities.
| Called in the tables below | Capability required |
|---|---|
| View | plugixa_chat_view |
| Create | plugixa_chat_create |
| Edit | plugixa_chat_edit |
| Delete | plugixa_chat_delete |
| Export | plugixa_chat_export |
| Settings | plugixa_chat_manage_settings |
Two more capabilities exist:
| Capability | Meaning |
|---|---|
plugixa_chat_manage |
May open the admin app. It is never granted directly: anyone who holds any other capability in this list has it automatically |
plugixa_chat_reply |
Defined for answering visitor conversations. No route in 1.0.0 requires it |
Administrators hold all of them from activation, and an account that can manage options is always treated as holding them. No other role is given any. They are ordinary WordPress capabilities, so a role editor can grant them to a role or to one person.
A caller without the capability gets the error code plugixa_chat_forbidden
with status 401 when not logged in, or 403.
The verdict can be changed with the plugixa_chat_user_can filter. See
Hooks reference.
Response shape
A successful response wraps its payload:
{
"success": true,
"data": { },
"meta": { "total": 12, "pages": 1 }
}
meta is present on lists.
A refusal is a standard WordPress REST error with a code starting
plugixa_chat_, a readable message and an HTTP status. Validation errors
on a form carry the messages per field in data.fields.
Lists
Every list route accepts the same parameters.
| Parameter | Values | Default |
|---|---|---|
page |
1 or more | 1 |
per_page |
1 to 200 | 50 |
search |
Text | Empty |
orderby |
A sortable column of that record | id |
order |
asc or desc |
desc |
view |
active or trash |
active |
Records and the trash
Widgets, leads, and the Pro records are stored the same way and share a set of routes. Deleting a record moves it to the trash. A trashed record can be restored, or purged, which deletes it for good.
For a record at /{base}:
| Method | Route | Does | Permission |
|---|---|---|---|
GET |
/{base} |
Lists records | View |
POST |
/{base} |
Creates one. Answers 201 | Create |
GET |
/{base}/{id} |
Reads one | View |
PUT, PATCH |
/{base}/{id} |
Updates one | Edit |
DELETE |
/{base}/{id} |
Moves one to the trash | Delete |
POST |
/{base}/{id}/restore |
Restores one from the trash | Delete |
DELETE |
/{base}/{id}/purge |
Deletes one for good | Delete |
A record that does not exist answers plugixa_chat_not_found with status
404.
| Base | Record | Edition |
|---|---|---|
widgets |
Widgets | Free |
leads |
Leads | Free |
agents |
Agents | Pro PRO |
webhooks |
Webhooks | Pro PRO |
crm |
CRM connections | Pro PRO |
experiments |
A/B tests | Pro PRO |
Routes
Core
| Method | Route | Does | Permission |
|---|---|---|---|
GET |
/bootstrap |
The plugin version, your permissions, and the parts of the plugin that are loaded | View |
GET |
/settings |
The settings, which secrets are set (never their values), and the choices the screen offers | Settings |
PUT, PATCH |
/settings |
Saves settings. Send only the keys you want to change. Unknown keys are ignored. Secrets go in a secrets object |
Settings |
GET |
/context |
Public. See Public routes | None |
The setting keys are: date_format, time_format, records_per_page,
ip_mode, retention_days, render_ssr, shadow_dom, fail_mode,
lead_notify, lead_recipients, turnstile_site_key, track_events,
woo_button, woo_widget, woo_channel, woo_text, woo_message, and in
Pro agent_rotation. The one secret the free edition uses is
turnstile_secret.
Widgets
The shared record routes at /widgets, plus:
| Method | Route | Does | Permission |
|---|---|---|---|
POST |
/widgets/{id}/duplicate |
Copies a widget, with its channels and rules, as an inactive widget | Create |
GET |
/channels |
The channel catalogue and its groups | View |
GET |
/content |
Searches pages, posts or terms for the targeting pickers. kind is post or term; q is the search text; ids is a comma-separated list to look up by ID; type narrows to one post type or taxonomy. Up to 20 results for a search |
View |
A widget is read and written as one object:
{
"name": "Main widget",
"status": "active",
"appearance": {
"position": "right",
"layout": "vertical",
"size": 56,
"color": "#0f766e",
"icon_color": "#ffffff"
},
"channels": [
{
"slug": "whatsapp",
"value": "+15550001234",
"label": "",
"message": "Hi! I'm on {title}",
"is_active": true,
"devices": "all"
}
],
"rules": [
{ "kind": "url", "mode": "hide", "operator": "contains", "values": [ "/checkout" ] }
]
}
Things to know:
- On an update, sending
channelsreplaces all of the widget’s channels, and sendingrulesreplaces all of its rules. Leave a key out to keep what is stored. statusisactiveorinactive.- A
nameis required:plugixa_chat_widget_name_required, status 400. - Values are cleaned on the way in: sizes and spacings are brought into range, colours must be hex, an unknown channel slug or rule kind is dropped, and so is a rule with no values.
- A list response’s
metaalso carrieslimitandused.limitis 0 unless a ceiling was set with theplugixa_chat_max_widgetsfilter. At the ceiling, creating, duplicating or restoring answersplugixa_chat_widget_limit, status 409.
Rule kinds are template, url, post, term, post_type, device,
logged_in and referrer, and in Pro schedule, country and language.
Rule kinds the builder does not offer
The API accepts three more kinds. None can be added in the builder.
| Kind | Values | Behaviour |
|---|---|---|
visits |
A number, as a string | Matches once this browser’s count has reached the number. The count is kept in the browser’s local storage as plugixa_chat_visits, goes up by one each time the rule is checked on a page, and is never sent anywhere. A browser that blocks storage always counts as 1 |
ab |
test:from-to |
Written by Pro’s A/B tests. Do not write it yourself |
role |
Text | Stored, but version 1.0.0 of the widget script has no check for it. It is answered as a check that could not be made, by the If a visitor check fails policy and the plugixa_chat_unevaluable_rule filter |
Leads
The shared record routes at /leads, with these differences:
| Method | Route | Does | Permission |
|---|---|---|---|
GET |
/leads |
Also accepts status (new or read) and widget_id |
View |
POST |
/leads |
Refused. Answers plugixa_chat_leads_read_only, status 405. Leads are created only by the contact form |
- |
PUT, PATCH |
/leads/{id} |
Changes the status only. Send {"status": "read"} or {"status": "new"} |
Edit |
GET |
/leads/unread |
The number of new leads, as {"count": 3}. Accepts widget_id |
View |
GET |
/leads/export |
The leads as a CSV file. Accepts status |
Export |
GET |
/leads/{id}/answers |
The answers to custom form fields for one lead PRO | View |
POST |
/leads/submit |
Public. See Public routes | None |
A lead never includes the stored IP value.
Analytics
| Method | Route | Does | Permission |
|---|---|---|---|
GET |
/analytics/summary |
Totals, the follow-through rate, a point per day and the top ten channels. days is 1 to 365, default 30. widget narrows to one widget, default 0 for all |
View |
POST |
/events |
Public. See Public routes | None |
{
"success": true,
"data": {
"days": 30,
"totals": { "view": 1840, "open": 212, "click": 97 },
"clickRate": 45.8,
"series": [ { "date": "2026-10-05", "view": 61, "open": 8, "click": 3 } ],
"channels": [ { "channel": "whatsapp", "hits": 70 } ],
"widget": 0
}
}
Pro routes
PRO These exist only on a site with the Pro edition. Each record also has the shared record routes at its base.
| Method | Route | Does | Permission |
|---|---|---|---|
POST |
/webhooks/{id}/test |
Posts a sample lead to the webhook now and returns the result | Edit |
GET |
/crm/providers |
The services, and whether each is available | View |
POST |
/crm/lists |
Fetches the lists for a service. Send provider and api_key, or the id of a saved connection to use its key |
Edit |
POST |
/crm/{id}/check |
Tests a connection’s key and list | Edit |
POST |
/experiments/{id}/start |
Starts a draft test | Edit |
POST |
/experiments/{id}/end |
Ends a running test. keep is a or b |
Edit |
GET |
/experiments/{id}/results |
The figures for both widgets | View |
POST |
/orders/lookup |
Public. Registered only while WooCommerce is active. See Public routes | None |
Deleting a CRM connection also erases its API key. An API key is never
returned by any route; a connection carries has_key instead. A running A/B
test cannot be deleted: plugixa_chat_test_running, status 409.
Public routes
Four routes accept a visitor who is not logged in. They are what the widget calls.
| Method | Route | Does | Limit per visitor |
|---|---|---|---|
GET |
/context |
Answers the questions a cached page cannot: the site’s current time, the visitor’s country, whether they are logged in, and fresh tokens | None |
POST |
/events |
Records analytics events. Answers 204 with no body | 60 every five minutes |
POST |
/leads/submit |
Stores a contact form message. Answers 201 | 5 every five minutes |
POST |
/orders/lookup PRO |
Looks up a WooCommerce order | 10 every five minutes |
The page token
The three POST routes need a token. It is not a WordPress nonce, because
the page that carries it is often served from a cache and shared by every
visitor.
- The token is printed into every page that loads the widget, in the
tokensobject of the JSON block with the IDplugixa-chat-config. There is one foreventsand one forlead. The order lookup uses theleadtoken. - Send it in the
X-Plugixa-Chat-Tokenheader, or astokenin the JSON body. - A token is valid for between 12 and 24 hours.
/contextreturns fresh ones. - When the request carries an
OriginorRefererheader, its host must be the site’s own.
| Error code | Status | Meaning |
|---|---|---|
plugixa_chat_missing_token |
403 | No token was sent |
plugixa_chat_stale_token |
403 | The token has expired. Fetch /context and try again |
plugixa_chat_bad_origin |
403 | The request came from another site |
plugixa_chat_rate_limited |
429 | The visitor’s limit for this route is used up |
The token says “this came from a page this site rendered recently”. It is not authentication. The limits, and the contact form’s own spam checks, are what make replaying it pointless.
GET /context
{
"success": true,
"data": {
"now": { "minutes": 570, "weekday": 1, "date": "2026-10-05", "timezone": "Europe/Paris" },
"country": "FR",
"loggedIn": false,
"tokens": { "events": "...", "lead": "..." },
"failMode": "open"
}
}
minutes is minutes since midnight in the site’s timezone, and weekday is
0 for Sunday to 6 for Saturday. country is empty when it is not known. The
response is marked private and may be reused by the browser for five
minutes.
POST /leads/submit
{
"widget": 2,
"fields": { "name": "Jane Doe", "email": "jane@example.com", "message": "Hello" },
"consent": true,
"subscribe": false,
"website": "",
"elapsed": 8400,
"page": "https://example.com/contact/"
}
| Key | Meaning |
|---|---|
widget |
The ID of an active widget that has a Contact form channel |
fields |
name, email, phone, message, and in Pro the custom fields f1, f2 and so on |
consent, subscribe |
The two checkboxes, as booleans |
website |
The trap field. It must be empty |
elapsed |
Milliseconds since the form was opened. Under 3000 is treated as a program |
page |
The page address. Kept only when it is on this site |
turnstile |
The Turnstile token, when Turnstile is on |
A submission caught by the trap field or the timer is answered exactly like a real one, and is not stored.
| Error code | Status | Meaning |
|---|---|---|
plugixa_chat_no_form |
404 | The widget is not active or has no contact form |
plugixa_chat_invalid_lead |
400 | A field is missing or invalid. data.fields has a message per field |
plugixa_chat_turnstile_failed |
400 | The Turnstile check was missing or failed |
plugixa_chat_lead_not_saved |
500 | The message could not be written |
POST /events
{
"token": "...",
"events": [
{ "w": 2, "c": "", "t": "view" },
{ "w": 2, "c": "", "t": "open" },
{ "w": 2, "c": "whatsapp", "t": "click" }
]
}
w is the widget’s ID, c the channel’s slug and t one of view, open
or click. At most 200 events are read from one request. Anything else in
an event is ignored.
POST /orders/lookup
PRO
{
"fields": { "email": "jane@example.com", "order": "1042" },
"website": ""
}
A match answers with lines, an array of sentences, and link, which is
null or an object with url and label. No match answers
plugixa_chat_order_not_found, status 404. A missing email or number
answers plugixa_chat_order_invalid, status 400.
What to do next
- React to events inside WordPress with the Hooks reference.
- Receive leads in another system with Webhooks PRO.