A published support assistant over HTTP

Assistant chat API

A published assistant answers your customers from its workspace's sources, through one endpoint you can call from your own site or any HTTP client. Every answer lists the sources it cites. Calls spend your own model key, so every key has a daily cap. It answers with the published version's prompt, tone, refusal and handoff message, the same instructions a test chat on that version uses.

The public key

Each published assistant can have public keys (prefix flk_pub_). A public key reaches this endpoint and nothing else: it is not an organization API key, and every other /api/v1 route refuses it. Anyone who sees your site's source can read the key, so treat it as public and rely on its limits:

  • a daily cap (200 calls by default), counted per assistant per UTC day across every node before the model is called;
  • a per-visitor daily token budget and a short per-visitor rate limit;
  • an optional list of allowed sites. It is advisory: it stops casual copying of your snippet, but any non-browser client can send whatever Origin it likes. The cap and revoking the key are the real controls.

Ask a question

start a conversation
$ curl -N https://flickercloud.com/api/v1/assistants/$ASSISTANT_ID/chat \
  -H "Authorization: Bearer $FLICKER_PUBLIC_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "How do returns work?"}'

The key may also travel in an X-Assistant-Key header. A message is at most 2000 characters and 7 KB, and the whole request body at most 16 KB. Browsers may call the endpoint directly: it answers the CORS preflight for any origin and never accepts cookies.

The website widget

To put the assistant on a site without writing code, add one script tag with your assistant's id and a public key. It adds a chat bubble (a bottom sheet on a phone) that shows your starter questions, streams a quiet progress line, and answers with its sources. It runs in a shadow root so your page's CSS cannot break it, sets no cookies, loads nothing from third parties, and keeps only the conversation id, in sessionStorage.

embed
<script src="https://flickercloud.com/widget.js" data-key="flk_pub_..." data-assistant="ID" async></script>

Optional attributes: data-accent (a hex colour), data-position="left", and data-title (defaults to the assistant's name). The script talks only to the origin it was loaded from.

If your site sends a Content-Security-Policy, allow this much and nothing more:

minimum CSP additions
script-src https://flickercloud.com
connect-src https://flickercloud.com

The widget injects its own styles into its shadow root, so a policy that forbids inline styles also needs style-src 'unsafe-inline' or the widget will render unstyled. To try it before you embed it, open /widget/test?key=flk_pub_...&assistant=ID on this site. The widget reads GET /api/v1/assistants/:id (same key; returns the name and starter questions of the published version) and then uses the chat endpoint below. Everything under The public key applies: the daily cap, the visitor limits and the advisory site list.

The streamed reply

Once the assistant starts working the reply is 200 with text/event-stream: one step event per pipeline step as it finishes (its name only), then one answer event.

events
event: step
data: {"step":"search"}
 
event: answer
data: {"answer":"Returns are accepted within 30 days [1].","citations":[{"position":1,"title":"Returns policy","url":null,"snippet":"..."}],"conversation_id":"..."}

The answer text arrives whole in the answer event, not word by word. Its [n] markers point at citations by position. If the assistant fails after streaming began, the last event is error with the same error and message fields as below. Lines starting with : are keep-alives; ignore them.

Conversations

Send the conversation_id from an answer with the next message to continue that conversation. The id is opaque and only works with the key that started it. A conversation holds up to 20 questions, one at a time; after that, start a new one. Each question is answered from your sources on its own: earlier turns are stored for you, not sent to the model.

A conversation id belongs to the key that started it. After you rotate or revoke that key, or after Flicker rotates its own signing secret, the old id answers 404 conversation_not_found: drop it and start a new conversation by sending the next message without one.

Visitors' conversations are third-party text. Each assistant keeps them for its retention setting (30 days by default, at most 365) and they are deleted daily after that.

Errors

A request refused before the assistant runs gets a JSON body {"error": code, "message": text} and no key or internal id:

  • 401 unauthorized: missing, revoked or expired key (one answer for all).
  • 403 origin_not_allowed: the key's allowed-sites list refused the Origin.
  • 404 not_found / conversation_not_found: not this key's assistant, not published, or an unknown conversation.
  • 409 conversation_busy: the conversation is still answering.
  • 413 too_large, 422 invalid_message, message_too_long, conversation_too_long.
  • 429 rate_limited, visitor_limit_reached, daily_cap_reached, each with a Retry-After header in seconds (the last two: until midnight UTC).
  • 502 unavailable, 504 timeout.

A call refused by the daily cap costs nothing. A call that reached the model counts against the cap even if the model then fails, so an outage cannot become free retries.