agent_id — no API key, no server of your own. The platform gates access with three controls: an agent-level public switch (off by default), a required origin allowlist, and rate limiting.
Use this mode for demos, marketing pages, and support bubbles where standing up a backend isn’t worth it. For production apps with their own users, authenticated sessions are usually the better fit — see when to use session tokens instead.
Enable public access
1
Publish the agent
Public sessions always run the agent’s published configuration. An
unpublished agent rejects session creation. See Versions &
publishing.
2
Turn on the public switch
On the agent’s Deploy page in the console, turn on Public access. It
is off by default — until you enable it, sessions require
authentication.
3
Add allowed origins
On the same page, list every web origin that may start sessions under
Allowed origins (
allowed_origins in the API). The allowlist is
required: a request whose Origin doesn’t match any entry is rejected
with 403.Start a session from the browser
PassagentId to the SDK and it creates the session directly with the Fish Audio API, then connects the audio — your page never handles credentials.
Origin matching
An entry matches only on exact scheme + host + port. The host is case-insensitive, and a trailing slash on an entry is tolerated — everything else must match exactly.Rate limiting
Public session creation is rate limited on two dimensions at once: per client IP and per agent. Either limit being exceeded rejects the request. Sessions started on a public agent are billed to the workspace that owns the agent.If legitimate traffic outgrows these limits, or you need per-user quotas, move
to authenticated sessions — your
backend becomes the gate, and the platform-side public limits no longer apply.
Treat public input as untrusted
On the public path, the entire session request originates in the visitor’s browser. The platform enforces its own guardrails — public sessions accept only thelanguage and voice_id overrides, rejecting everything else with 400 — but anything that passes through verbatim is attacker-controllable:
metadatais stored and returned exactly as sent, never interpreted by the platform. When you read it back in conversation history or webhooks, treat it as untrusted data — never as proof of who the visitor is.dynamic_variablesare chosen by the page that starts the session. Don’t inject anything through them that the visitor shouldn’t control. See Dynamic variables.
end_user_id, server-set metadata), create sessions from your backend instead.
Errors
Public-mode failures surface asFishAgentError codes on AgentSession.start():
When to use session tokens instead
Public mode trades control for zero setup. Prefer thesessionToken flow — your backend calls the session endpoint with an API key and hands the token to the browser — when any of these apply:
- The agent should stay private rather than callable by anyone who loads your page.
- You need trusted attribution: an
end_user_idandmetadataset by your server, not the browser. - You want server-side control of
overrides,dynamic_variables, ortool_eventsinstead of accepting the browser’s values. - You already authenticate users and want your own quotas per account, not IP-based platform limits.
{ sessionToken } instead of { agentId }. See Authenticated sessions for the full flow.
Going further
Authenticated sessions
The session token flow in full — request fields, lifetime, errors.
Web SDK
Events, methods, and client tools — identical in both modes.
React SDK
Hooks and components for React apps.
Conversation history
Review what public visitors said to your agent.

