Developers

REST API Reference

Published · Last updated

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

TierAuth
Visitor endpointsPublic (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 endpointscurrent_user_can('manage_options') + the standard WordPress REST nonce (X-WP-Nonce).
Discord inboundShared secret in the X-SSC-Secret header, compared with hash_equals().

Rate limits (per IP)

EndpointLimit
/send15 / minute
/session10 / minute
/auto-reply3 / minute
/pollnot limited (read-only, frequent)

Exceeding a limit returns HTTP 429.

Note

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 & routePurposeKey params
GET /admin/conversationsList conversationsstatus, search, assigned_to, per_page, page
GET /admin/conversation/{id}One conversation + messagessince_id (optional)
POST /admin/replyReply to a conversationconversation_id, message
POST /admin/close/{id}Close a conversation–
POST /admin/assign/{id}Assign/unassignassigned_to (user id, or 0/empty to unassign)
GET /admin/cannedList canned responsessearch, category, per_page, page
POST /admin/cannedCreate a canned responsequestion, response, category, source_message_id
PUT/PATCH /admin/canned/{id}Update a canned responsequestion, response, category
DELETE /admin/canned/{id}Delete a canned response–
POST /admin/discord/testTest 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).

WhatsApp has its own inbound route

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, and tests/test-fast-ajax-path.php files exercise these endpoints and serve as an executable spec for the exact shapes and status codes.

Further reading

how the visitor and fast paths share one set of handlers
registering your own routes via ssc_register_rest_routes
what happens when the visitor endpoints bypass full WordPress
the client of the /discord/incoming route

Comments

▲
▼
1/1