Skip to Content
cuelo.

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:

config.cuelo
serve:
  host: 0.0.0.0
  api_keys: ["${CUELO_KEY}", "${CUELO_KEY_OLD}"]   # several for rotation
.env
CUELO_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:

HowFor
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 URLlinks, 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:

config.cuelo
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.

On this page