Public endpoints are the routes under /public/ plus GET /widget.js. They are made to be called from a web page or opened as a link, so they never take a secret API key. Each one is authorised by something that is safe to expose for its purpose:
| Credential | Where it comes from | Endpoints |
|---|---|---|
Public key plus an allowed Origin |
Public keys | Start widget chat |
| Chat token | Returned by Start widget chat | Send widget chat message, End widget chat |
| Share token | Create share link | Get share link, Share link chat, Share link web call |
Signed link (exp and sig) |
Fields on the call object and asset object | Get public call log, Get public KB retrievals, Get public asset |
| None | Widget script |
Public endpoints or a secret key#
Use a secret API key (key_...) from your own servers. It reaches every route its scopes allow, so it must never appear in a web page, an app bundle or a URL.
Use public endpoints when code runs in a visitor's browser or a link is handed to someone outside your workspace:
- The chat widget and your own chat UI on a website use a public key.
- A Voice Orb page uses a share token. See Share a Voice Orb link.
public_log_urlandknowledge_base_retrieved_contents_urlon a call, andurlon an asset, are signed links you can open or pass on without a key.
A public key also works as Authorization: Bearer public_key_... on POST /v3/create-web-call, /create-chat, /create-chat-completion and /end-chat/{chat_id}, from an allowed Origin. Those routes are documented with the rest of the API; see Authentication. Every other authenticated route returns 403 (A public key can only start web calls and chats).
Public key checks#
Start widget chat, and the four authenticated routes above when called with a public key, check the request in this order:
- Origin. The
Originheader must match one of the key'sallowed_domains.*.example.commatches any subdomain andexample.comitself. An entry with a port only matches that port; an entry without one matches any port. A request withoutOriginis refused, so a public key does not work from server code or cURL. Start widget chat answers a bad Origin with403(Origin <origin> is not allowed for this key); the authenticated routes treat the key as invalid and answer401. - Blocked countries. The country is read from
cf-ipcountry,x-vercel-ip-countryorx-country-code, in that order. When it is in the key'sblocked_countriesthe request gets403(Requests from <country> are blocked for this key). With none of those headers the check is skipped. - Per-IP limit. 30 requests per minute from one IP on one key (
PUBLIC_KEY_IP_RATE_PER_MINUTEon the API). Over it:429(Too many requests from this address. Retry in <s>s.). - Key limit. When the key has
rate_limit_per_minute, all requests on the key share that limit. Over it:429(This public key is over its limit of <n> requests per minute.). - reCAPTCHA. When the key has
recaptcha_enabled, the reCAPTCHA v3 token is read fromrecaptcha_tokenin the body, or from thex-recaptcha-tokenheader. A missing token, a failed check or a score belowrecaptcha_thresholdreturns403(reCAPTCHA check failed (score <s> below <threshold>)). - Credit. Start widget chat, create web call, create chat and create chat completion return
402when the workspace is out of credit or over its monthly budget. The authenticated routes also return402when an invoice is overdue.
Real reCAPTCHA verification needs RECAPTCHA_SECRET on the API. Without it, verification is mocked: a missing token scores 0, a token of the form mock:<score> scores that value, and any other token scores 0.9.
Rate limits#
Every public endpoint counts toward the per-IP limit that applies to all routes: 3000 requests per minute by default (RATE_LIMIT_PER_IP). Over it, the API returns 429 with Rate limit exceeded for this IP address. The public key limits above apply on top. Share token and signed link endpoints have no other limit. See Rate limits.
CORS#
Routes under /public/ and /widget.js accept requests from any origin for GET, POST and OPTIONS. They do not use the dashboard session cookie. The Origin allowlist is enforced by the public key itself, not by CORS.
Errors#
Public endpoints use the same error body as the rest of the API.