The Flowyte API has two authentication modes that both resolve to the same tenant-scoped
identity, so a key can never reach another organization’s data.
Secret API keys (server-to-server)
For your backend and for agents building against the API, use a secret key:
Your API key already determines the organization — no extra headers are needed.
The first key is minted in the dashboard’s Developer page (a chicken-and-egg bootstrap).
Manage your keys — mint, rotate, and revoke — in the dashboard Developer page.
Browser sessions
The dashboard authenticates with a session token. You won’t use this directly when building
against the API — it’s how the builder UI talks to the same endpoints your key does.
The embeddable chat widget authenticates in the browser with a publishable key
(flowyte_pk_…). It is agent-pinned, origin-allowlisted, and limited to public chat — safe to
ship in client-side code.
Scopes
Secret keys carry scopes that gate which operations they can call. Request only what you
need. Common scopes:
The authoritative scope list is the Scope enum in the API Reference.
Message content lives under calls:read, not sms:read. Despite the name, calls:read is
channel-agnostic: an SMS conversation is stored as a conversation like any other, so
GET /conversations/{id}/transcript returns the verbatim text of every message a customer sent or
received. A key holding calls:read can read all of it.sms:read is the compliance surface — registration status, number toggles, the opt-out list and the
contact roster. It grants no message bodies at all.Three consequences worth planning around:
- A key granted “just call reads” can read your customers’ text messages.
calls:read also grants the event receipt (GET /conversations/{id}/receipt) — the forensic
record of everything the agent did on that call: tool calls, knowledge lookups, guardrail
decisions and latency samples. Summary, transcript and receipt are one grant, not three.
- There is currently no way to grant SMS visibility without also granting voice and chat;
calls:read is all-or-nothing across channels.
Reading a conversation also needs analytics:read to find it in the first place — the list endpoint
and the transcript endpoint are separately scoped, so a key with only analytics:read sees
conversations listed but every transcript comes back empty. That is a missing scope, not a bug.
Some endpoints documented in the contract are reserved (planned ahead of release). Calling
one returns a 403, not a 404. If a correctly-scoped call returns 403, check whether the
endpoint is marked reserved.