REST API Reference
All endpoints live under the ssc/v1 namespace (/wp-json/ssc/v1/…). There are two tiers: visitor endpoints (public, used by the chat widget) and admin endpoints (require the manage_options capability).
When Ultra Ajax is enabled, the visitor endpoints (
/session,/send,/poll) are served by the mu-plugin before full WordPress loads, but the request/response shapes are identical to the REST routes documented here. Admin routes always go through the full REST API.
Authentication model
| Tier | Auth |
|---|---|
| Visitor endpoints | Public (permission_callback is open). Identity is the ssc_visitor_hash cookie; each request resolves the caller’s own conversation from that cookie only. Protected by per-IP rate limiting + a honeypot field. |
| Admin endpoints | current_user_can('manage_options') + the standard WordPress REST nonce (X-WP-Nonce). |
| Discord inbound | Shared secret in the X-SSC-Secret header, compared with hash_equals(). |
Rate limits (per IP)
| Endpoint | Limit |
|---|---|
/send | 15 / minute |
/session | 10 / minute |
/auto-reply | 3 / minute |
/poll | not limited (read-only, frequent) |
Exceeding a limit returns HTTP 429.
The visitor tier is public by design, so its safety rests entirely on three things: the caller’s conversation is resolved from the ssc_visitor_hash cookie only (no visitor endpoint accepts a conversation ID), the per-IP rate limits above, and the honeypot on /send. There is deliberately no way for a visitor request to read or write another visitor’s conversation.
Visitor endpoints
POST /session
Create or resume the caller’s conversation. Returns: { conversation_id, visitor_hash, messages[], status }
POST /send
Send a visitor message. Params: message (string), page_url (string, optional), website_url (honeypot – leave empty; if filled, the request is silently dropped). Returns: { message_id, conversation_id }. Requires a session cookie (else 403). Empty messages are rejected (400).
GET /poll
Fetch messages newer than a given id. Params: since_id (int). Returns: { messages[], conversation_id }. Requires a session cookie (else 403).
POST /email
Attach the contact details the visitor left on the prompt to their conversation: an email (for offline reply notifications) and/or a phone number. Params: email (string, optional), phone (string, optional; digits, spaces and +()-, 7-30 characters after anything else is stripped). At least one is required. Returns: { success: true }. A request with neither, an invalid address or an invalid phone number is rejected (400) and nothing is saved. The phone is stored as phone in the conversation’s metadata JSON, and admin conversation responses include it as visitor_phone.
POST /auto-reply
Request an LLM-classified canned reply when no admin has responded. Params: question (string). Returns: { auto_replied: bool, reason?, message_id?, canned_id? }. No-ops (with a reason) when the LLM isn’t configured, there’s no visitor message yet, the conversation was already auto-replied to, or the daily cap is reached.
Admin endpoints
All require manage_options + nonce.
| Method & route | Purpose | Key params |
|---|---|---|
GET /admin/conversations | List conversations | status, search, assigned_to, per_page, page |
GET /admin/conversation/{id} | One conversation + messages | since_id (optional) |
POST /admin/reply | Reply to a conversation | conversation_id, message |
POST /admin/close/{id} | Close a conversation | – |
POST /admin/assign/{id} | Assign/unassign | assigned_to (user id, or 0/empty to unassign) |
GET /admin/canned | List canned responses | search, category, per_page, page |
POST /admin/canned | Create a canned response | question, response, category, source_message_id |
PUT/PATCH /admin/canned/{id} | Update a canned response | question, response, category |
DELETE /admin/canned/{id} | Delete a canned response | – |
POST /admin/discord/test | Test the Discord bot connection | – |
Typical responses are { success: true }, an object with an id/message_id, or a list payload { items[], total, per_page, page, total_pages }. Unknown ids return 404; missing required params return 400.
Discord inbound (add-on route)
POST /discord/incoming
Used by the companion Discord bot to relay a Discord reply into a conversation. Auth: X-SSC-Secret header (shared secret). Params: thread_id, author_name, message. Returns: { success: true, message_id }, or 401 (bad secret) / 404 (unknown thread).
The WhatsApp channel (v1.11+) adds a parallel add-on endpoint, POST /whatsapp/incoming, that Meta’s Cloud API calls directly – no companion bot. It handles both the verify-token handshake and message delivery, authenticated by an X-Hub-Signature-256 HMAC rather than a shared secret. See the WhatsApp Integration Setup Guide for the webhook and signature details.
Example calls
Visitor send (browser, with nonce optional on public routes):
curl -X POST https://example.com/wp-json/ssc/v1/send \
-H 'Content-Type: application/json' \
-b 'ssc_visitor_hash=<cookie>' \
-d '{"message":"Hi there","page_url":"https://example.com/pricing"}'
Admin list (must be authenticated as an admin with a valid nonce):
curl https://example.com/wp-json/ssc/v1/admin/conversations?status=waiting \
-H 'X-WP-Nonce: <wp_rest nonce>' \
--cookie-jar/--cookie <wp-auth-cookies>
The
tests/test-rest-visitor-flow.php,tests/test-rest-admin-flow.php, andtests/test-fast-ajax-path.phpfiles exercise these endpoints and serve as an executable spec for the exact shapes and status codes.
Further reading
ssc_register_rest_routes/discord/incoming route
Comments