External Widget API Reference
Base URL
The widget API is served from your Salesforce Site, under the path /services/apexrest/redaai/chat/v1/:
https://<your-org>.my.salesforce-sites.com/chat/services/apexrest/redaai/chat/v1/
The exact host depends on your Site domain. Get it from Setup › Sites; the loader script you embed on your website uses the same path under the data-endpoint attribute.
Authentication
Every request requires an API key. Pass it either as an HTTP header or in the request body — the API checks both:
| Where | Name |
|---|---|
| HTTP header | X-REDAAI-API-Key |
| Body field | clientKey |
Salesforce Sites can strip custom headers in some configurations, which is why the body fallback exists. Use either; the body form is more reliable across CDNs and proxies, and is what the shipped widget loader uses by default.
After /init, every subsequent request also needs the session token returned by /init:
| Where | Name |
|---|---|
| HTTP header | X-REDAAI-Session |
| Body field | sessionToken |
See API Key Management for generating, rotating and revoking keys.
CORS
The API echoes Access-Control-Allow-Origin for browser callers. The agent definition can restrict callers via the Allowed Origins field (comma-separated origin list); leave it blank to allow any origin. When set, only the listed origins succeed; others receive an ORIGIN_NOT_ALLOWED error.
Changed in V1.13. The origin is taken from the request’s Origin header, not from the request body. A request with no Origin header — any server-to-server call — is therefore rejected when Allowed Origins is set. See POST /chat/v1/init.
Endpoints
Five endpoints, all POST. Pre-flight OPTIONS requests are handled automatically.
| Path | Purpose |
|---|---|
POST /chat/v1/init | Start a new chat session and return a session token. |
POST /chat/v1/message | Send a user message and receive the agent’s reply. |
POST /chat/v1/action | Execute a confirmed action that the agent proposed. |
POST /chat/v1/history | Fetch the messages for the active session’s conversation. |
POST /chat/v1/continue | Ask the agent to narrate the outcome of actions it auto-executed on the previous turn. No new user message is sent. |
POST /chat/v1/init
Validates the API key, checks the caller’s origin, and creates a session. Returns the session token plus the agent metadata your widget needs to render the welcome state.
Request body
{
"clientKey": "<your api key>"
}
Changed in V1.13. The caller’s origin is now taken from the request’s Origin header — set by the browser and not overridable by page scripts. An origin field in the request body is still accepted for backward compatibility but is ignored: a value supplied by the caller cannot be used to restrict the caller. Widgets that still send it keep working unchanged.
This matters for non-browser callers. A server-to-server request carries no Origin header, so it is rejected with ORIGIN_NOT_ALLOWED when the agent has Allowed Origins set. If you call this API from your own backend, leave Allowed Origins blank and restrict access another way. See Configuring Allowed Origins.
Response (200 OK)
{
"sessionToken": "<opaque session token>",
"agentName": "support-assistant",
"displayName": "Support Assistant",
"welcomeMessage": "Hi! How can I help today?",
"mascotImage": "https://…/mascot.png",
"maxMessageLength": 2000
}
sessionToken— pass on every subsequent request.agentName— the agent’s identifier, useful for analytics on the client side.displayName— the human-readable name to show in the widget header and welcome hero.welcomeMessage— the agent’s configured external welcome text. Empty string when not configured.mascotImage— the mascot URL when one is configured. Empty string when not.maxMessageLength— the length cap for user messages, in characters.
POST /chat/v1/message
Sends a single user message to the agent. Returns the agent’s reply once the agentic loop completes.
Request body
{
"clientKey": "<your api key>",
"sessionToken": "<from /init>",
"message": "What's open on the Acme account?"
}
Messages exceeding maxMessageLength (returned by /init) are rejected with INVALID_INPUT.
Response (200 OK)
{
"response": "<agent reply text>",
"creditsUsed": 1.42,
"conversationId": "a0Q…",
"pendingActions": [],
"continuationPending": true,
"continuationDepth": 0
}
response— the agent’s reply, including any embedded SUGGESTIONS or ACTION markers the widget renderer parses.creditsUsed— credits consumed by this turn.conversationId— the Salesforce ID of the conversation record. Useful for cross-referencing with the Conversation transcript in Salesforce.pendingActions— actions awaiting client confirmation. On the External Widget channel this is always empty, because confirmation cards exist only on the Internal channel. See POST /chat/v1/action below.continuationPending—truewhen the agent executed one or more actions this turn and has not yet narrated the outcome. Call/chat/v1/continueto get that narration.continuationDepth— how many continuation turns have already run. Pass it back on the next/continuecall.
POST /chat/v1/action
Executes a named Action tool on demand. Only Action tools assigned to the agent and scoped to the External Widget channel are accepted.
The External Widget channel does not use confirmation cards. Those exist only on the
Internal channel. When a widget agent decides to take an action, REDA AI executes it immediately,
captures the real outcome, and flags continuationPending so the agent can narrate what it did
on the next /continue call. Nothing waits for a click.
So this endpoint is not the second half of a confirmation handshake — it is here for widgets that drive their own action UI (your own buttons, your own confirmation step) and want to trigger a specific tool themselves.
Assign Action tools to a public widget agent deliberately. Any Action tool the agent can reach will run without a human gate. Give a customer-facing agent only the tools you are content to have executed on a visitor’s say-so.
Request body
{
"clientKey": "<your api key>",
"sessionToken": "<from /init>",
"actionTool": "create_record",
"params": {
"object_name": "Case",
"field_values": { "Subject": "Issue with login", "…": "…" }
}
}
Response (200 OK)
{
"success": true,
"result": "<tool execution result, JSON-serialised>"
}
The shape of result matches the underlying tool’s result. For built-in action tools, that means a structured payload with at least a success flag, a message, and (when applicable) a recordId.
POST /chat/v1/continue
Asks the agent to narrate the outcome of actions it executed on the previous /message turn. No
new user message is sent. Call it whenever a response comes back with
continuationPending: true.
Request body
{
"clientKey": "<your api key>",
"sessionToken": "<from /init>",
"continuationDepth": 0
}
Response (200 OK)
{
"response": "<agent narration of what happened>",
"creditsUsed": 0.68,
"conversationId": "a0Q…",
"continuationPending": false,
"continuationDepth": 1
}
continuationPending stays true if the agent chained another action while narrating,
so treat this as a loop: keep calling /continue, passing back the returned
continuationDepth, until it returns false. The agent’s Max
Continuation Depth setting bounds the chain, so it always terminates.
A continuation is a real model call. It consumes credits and runs the same validation as
/message — master switch, API key, session, per-session and per-hour rate limits, and the
daily credit cap — so it can return 429 with RATE_LIMITED or
CREDIT_LIMIT. It also refreshes the session’s idle timer, exactly as a message does.
POST /chat/v1/history
Returns up to 100 messages from the session’s conversation, oldest first. Useful when the widget reconnects after a refresh and needs to restore the visible thread.
Request body
{
"clientKey": "<your api key>",
"sessionToken": "<from /init>"
}
Response (200 OK)
{
"messages": [
{ "role": "user", "content": "…", "timestamp": "2026-04-25T08:55:12.000Z" },
{ "role": "assistant", "content": "…", "timestamp": "2026-04-25T08:55:14.000Z" }
]
}
Error Responses
All errors share the same shape:
{
"error": "<localised user-facing message>",
"code": "<machine-readable code>"
}
Use code for programmatic decisions in your client; show error to the end user.
| HTTP | Code | Meaning |
|---|---|---|
| 403 | DISABLED | The External Bot master switch is off. |
| 401 | INVALID_API_KEY | API key is missing, malformed, or revoked. |
| 403 | ORIGIN_NOT_ALLOWED | The caller’s origin is not in the agent’s Allowed Origins list. |
| 401 | SESSION_EXPIRED | The session token is invalid or has expired (per Session Timeout Minutes). |
| 429 | RATE_LIMITED | The session or per-agent rate limit was exceeded. |
| 429 | MAX_MESSAGES | The session reached its Max Session Messages cap. |
| 429 | CREDIT_LIMIT | The org’s External Daily Credit Cap is exhausted, or the licence credit budget is. |
| 400 | INVALID_INPUT | Body parameter missing, message too long, or unknown action tool. |
| 404 | GENERIC_ERROR | Unknown sub-path under /chat/v1/. |
| 500 | GENERIC_ERROR | An unexpected error. Details are recorded in the Incident Log; the response intentionally hides them. |
Rate Limits
Each call passes through several rate limiters in order. The first two and the message cap are set per agent on the Agent Definition; the daily credit cap is org-wide, a Platform Setting shared by every external agent, so one busy bot can exhaust the day’s budget for all of them. Setting it to 0 means unlimited — not recommended on a public-facing widget.
| Limit | Source | Default |
|---|---|---|
| Per session | Agent Definition › Rate Limit Per Session | 10 calls per session |
| Per hour | Agent Definition › Rate Limit Per Hour | 500 calls per agent per hour |
| Daily credit cap | Platform Setting › External Daily Credit Cap | 500 credits per day |
| Max messages per session | Agent Definition › Max Session Messages | 50 messages |
See Platform Settings Reference for daily credit cap details.
Session Lifecycle
- Creation:
/initcreates a session bound to the calling client’s IP and the validated origin. - Validity: sessions stay valid as long as they’re used. Activity on
/message,/actionor/historyrefreshes the session. - Timeout: idle sessions expire after Session Timeout Minutes on the agent (default 30). Calls with an expired session token receive
SESSION_EXPIRED; the widget should call/initagain. - Hard limits: a session ends when it hits Max Session Messages. Subsequent calls receive
MAX_MESSAGESuntil a new session is created.
Required Configuration on the Agent
Several Agent Definition fields shape the widget’s behaviour:
| Field | Purpose |
|---|---|
| Channel | Must be External Widget for an external-facing agent. |
| External API Key | The key the widget passes as clientKey. Generated from the Admin tab. |
| Allowed Origins | Comma-separated list of permitted origins (e.g. https://www.example.com). Blank = any origin allowed. |
| Max Message Length | Cap on user-message length in characters. |
| Session Timeout Minutes | Idle timeout for sessions. |
| Rate Limit Per Session | Per-session call cap. |
| Rate Limit Per Hour | Per-agent hourly cap across all sessions. |
| Max Session Messages | Hard cap on messages per session. |
| External Welcome Message | Returned by /init as welcomeMessage. |
| Mascot Image | Returned by /init as mascotImage. |
Security Posture
- Guest user execution. The API runs as the Salesforce Site’s Guest User. CRUD and FLS on that user’s profile govern what the agent and any tools can read or write.
- API-key scoped to one agent. Each external agent has its own key; revoke and rotate per agent without affecting others.
- Origin-allowlisted. Set Allowed Origins on every production agent. Blank means any origin can call — convenient for testing, risky for production.
- Generic error messages. The API never echoes internal errors to clients. Hard failures are recorded in the Incident Log for admin investigation.
- Daily credit cap. The External Daily Credit Cap is the org-wide ceiling that prevents a runaway public bot from exhausting the licence budget.
Best Practices
Use the shipped loader unless you have a reason not to. The loader handles cache-busting, header fallbacks, error rendering and the data attributes from Widget UI Customisation. Building your own client only makes sense for non-browser callers (mobile apps, server integrations).
Treat code as the source of truth. The error string is localised for end users and may change between versions. The code is stable.
Restart sessions on SESSION_EXPIRED, not on every error. The widget loader does this; custom clients should too. Don’t treat RATE_LIMITED or CREDIT_LIMIT as session problems — they need different recovery (back-off and admin attention respectively).
Set Allowed Origins for every public-facing agent. Leaving it blank in production removes a defence-in-depth layer.
Watch the daily credit cap. It’s the safety net for public-facing bots; tune it after observing real traffic.
Changes included in 1.16000
The per-agent hourly rate limit also applies during the final UTC hour of the day. External and Community APIs use a stable credit-limit error code so the intended HTTP 429 response no longer depends on the translated message.
Frequently Asked Questions
No. The four endpoints are the public contract. You can build your own client — mobile, desktop, voice, custom web — against the same API.
Don’t. Sessions are designed for sequential turns. Parallel calls on the same session can race against the rate limiters and the Conversation record. Wait for each /message response before sending the next.
For the configured Session Timeout Minutes (default 30) of inactivity. Each successful call refreshes the timer. Sessions also end when Max Session Messages is reached.
Because Salesforce Sites infrastructure can strip custom headers in certain proxy and CDN configurations. The body fallback is more reliable; the header form is convenient when you control the network path.
The four endpoints documented here cover the conversational flow. File uploads from external widgets follow the platform’s file-handling conventions; see the agent’s Allow File Upload setting and the File Upload guide.
The conversation persists in Salesforce. New sessions for the same end-user start a new conversation; the platform doesn’t automatically thread sessions together for guest users.
Generate the new key, update your loader script, and revoke the old key. Sessions that started under the old key continue as long as they don’t make a new call after revocation; new calls with the old key fail with INVALID_API_KEY. See API Key Management.
Next Steps
Read API Key Management for key issuance, or Community API Reference for the authenticated-portal counterpart.