06Ship
Security
API keys, CORS, and keeping credentials out of config.
Credentials
Provider keys live in .env or the environment, never in config.cuelo.
config.cuelo is meant to be committed. It references secrets as ${VAR}
(store URLs, HTTP tool URLs and headers, API keys), and the schema rejects
literal keys where it can. Commit .env.example. Keep .env in .gitignore,
which cuelo init sets up.
API keys
Without keys the server is open. That's fine on 127.0.0.1 but not on a
network. Turn keys on:
serve:
host: 0.0.0.0
api_keys: ["${CUELO_KEY}", "${CUELO_KEY_OLD}"] # several for rotationCUELO_KEY=sk-cuelo-...The server refuses to start on a non-loopback host (for example 0.0.0.0) when no key is set.
With keys on, every request needs one, except /health and the dashboard's
static page files (the page asks for a key, then sends it). Clients can send it
any of these ways:
| How | For |
|---|---|
Authorization: Bearer <key> | OpenAI SDKs (api_key=), servers |
x-api-key: <key> | servers, curl |
WebSocket subprotocol openai-insecure-api-key.<key> | browsers, which can't set WebSocket headers |
?key=<key> in the URL | links, such as recording downloads |
A missing or wrong key gets HTTP 401 with an OpenAI-style error body, or
WebSocket close code 4401. GET /v1/auth returns 200 or 401, so a client
can tell a refused WebSocket from an engine that isn't running. Keys are
compared in constant time.
Browsers and keys
A key in a browser is visible to whoever uses that browser. For public web apps, keep the Cuelo key on your server. Have your backend open the Realtime connection, or put a gateway in front that issues short-lived tokens.
CORS
Browsers on other origins can only call the API if you allow them:
serve:
cors: ["https://app.example.com"]For the default agent, use CUELO_CORS=https://app.example.com. Empty means
same origin only, which covers the built-in dashboard.
Call data
Calls store audio, transcripts and tool arguments, which may include personal
data. Point store.url at a database you control, set store.audio to
encrypted storage, and delete old sessions according to your retention policy.
Set summary: false if summaries shouldn't be generated.