Your key, your traffic: why Brilliant's AI is BYOK
Brilliant has an AI chat built into the canvas. You describe what you want, the agent draws it, and everything it makes is real, editable geometry the moment it lands. The interesting part is what sits behind that chat box: there is no Brilliant model, no Brilliant inference bill, and no Brilliant server in the path between your prompt and a response. You bring your own key, and your traffic goes straight to the provider you picked.
That is a deliberate design decision, not a gap we plan to fill later. This post is about why we made it, how the multi-provider plumbing works, where your keys actually live on disk, and the consent rules that govern anything leaving your machine.
Bring your own key, and what that buys you
"Bring your own key" (BYOK) means every request the chat makes is authenticated with your own API key or your own local Claude Code CLI, and it is sent directly to that provider's endpoint. Nothing is proxied through us. You pay your provider, your prompts reach only that provider, and we never sit in the middle holding a copy.
We think of this as a trust decision more than a billing one. When an AI feature routes through a vendor's servers, you are trusting that vendor with the contents of every prompt, every canvas snapshot, and every key. BYOK removes us from that equation entirely. The people who can see your traffic are you and the model provider you already chose to trust, and no one else. For a tool that can see your work in progress, that felt like the only honest default.
One interface, many backends
Under the hood, the chat is a multi-provider system. Every backend implements the same tiny contract: take a conversation, stream back events in our own normalized format. A provider's entire job is translation: turn app concepts into that backend's request shape, and turn that backend's streamed response back into our normalized events. Everything downstream (the chat UI, session persistence, the tool loop) only ever sees the normalized stream, so it never learns which vendor answered.
Today that covers five first-class provider families, each selectable per session from the model picker:
Anthropic over the Messages API, authenticated with
x-api-key.OpenAI, which speaks two dialects. Most models use Chat Completions; the Codex models and the compute-heavy "pro" configurations route through the newer Responses API instead. Same provider, two request shapes, picked automatically from the model's metadata.
Google Gemini, keyed by query parameter or by Google OAuth.
OpenRouter, which reaches many vendors through one gateway. It reuses our OpenAI provider because it speaks the same Chat Completions dialect, just pointed at a different base URL.
The local Claude CLI, wrapped as a provider that delegates to the
claudebinary as a subprocess. It manages its own history and never touches our keychain, because the CLI holds its own credentials.
On top of those, you can register any OpenAI-compatible endpoint yourself. That is the path for local runtimes like LM Studio (http://localhost:1234) and Ollama (http://localhost:11434), or any custom server. Those local providers are keyless by design: the traffic never leaves your machine, so there is nothing to authenticate.
Above the providers sits the agentic loop. HTTP backends can emit tool calls but cannot run them, so the orchestrator executes each call and feeds the result back as more conversation, turn after turn, until the model stops asking for tools. The tools themselves come in two flavors: file and shell and network tools and the canvas tools that actually draw. The CLI provider skips this loop entirely because it runs its own.
The upshot: adding a provider is almost entirely a matter of mapping its stream onto our events. The rest of the app does not change, and neither does the privacy posture, because every backend is BYOK by the same rule.
Where your keys live
A key you paste has to go somewhere. It goes into your operating system's secure credential store, never into a plaintext config file we manage.
On macOS, keys are written to the login Keychain through the system security tool, under the service name com.brilliant.credentials, with each provider filed as its own account. That is the same mechanism flutter_secure_storage uses internally, and the same class of storage desktop apps reach for when they hold credentials that matter. On Windows, keys go into the Windows Credential Manager as generic credentials, which the OS encrypts at rest for the current user via DPAPI. Both backends share one namespacing scheme so the two platforms stay in lockstep.
If you would rather not paste anything into the app at all, the credential store also reads standard environment variables as a fallback: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY, and OPENROUTER_API_KEY. If one of those is set and the Keychain has no entry, we use it, and it stays wherever you set it.
One rule holds across all of this: a key you paste never appears in a chat transcript, in your conversation history, or in a prompt draft. When you set one up from inside the chat, the pasted value goes straight into the credential store and is never echoed back into the conversation. Your secrets and your prompts live in separate places, and they stay that way.
Nothing leaves without your say-so
BYOK settles where your traffic goes. The consent model settles what travels with it. The rule is simple: outbound context is explicit-consent only, and nothing is attached on its own.
We do not quietly ride along a screenshot of your screen, your system information, your app version, a list of your recent files, or any telemetry. Beyond your actual prompt, the default is to send nothing extra. There are exactly three things that can go out with a message, and you control all three:
First-message canvas context. On the first message of a session, we include a text snapshot of the canvas so the model can see the work in progress: a structural outline in Brilliant's Blueprint format, the design-system state, the component catalog, the element count, and the current selection. It is text, not an image, and no screenshot of your screen is taken for it. Later messages send only what changed. Want to send a prompt with no canvas context at all? Start the chat in an empty workspace.
Attachments you add. Anything attached is something you chose to attach, and each one shows as a removable chip above the input before you send. An element attachment sends that element's Blueprint plus a render of just that element. An image or file attachment sends exactly what you added, nothing more.
The self-review screenshot. After the agent applies a block of changes, we render a screenshot of those changed elements and hand it back to the model so it can check its own spacing, contrast, and alignment. It is a render of the design content only, never your screen or other apps, and text-only models never receive it.
And when no provider is connected at all, the chat runs in Playground mode, which replays bundled demo conversations locally with no network call and no model request whatsoever.
The full, precise version of this policy, including exactly what is never sent, lives in our privacy and consent docs, and it is the contract the code above enforces.
The local bridge runs on loopback
There is one more piece worth being precise about, because "local AI tool" can mean a lot of things. Brilliant embeds a small MCP server so external agents (Claude Code, OpenAI's Codex, and other MCP clients) can drive the canvas the same way the built-in chat does. That server is deliberately local-only.
It binds to 127.0.0.1, the loopback address, on port 3333 (falling back to 3334 or 3335 if that port is taken), and serves a single /mcp endpoint. Loopback means the socket is reachable only from your own machine. A laptop on the same coffee-shop wifi cannot see it, because loopback traffic never touches the network interface. The access boundary is the bind address itself: agents you run locally can connect, and nothing off-box can.
That is how an external coding agent ends up editing your Brilliant canvas without any of it going through us. The agent runs on your machine, talks to a loopback port on your machine, and if that agent calls a model, it does so with your key by the same BYOK rule as everything else.
Why we built it this way
We could have shipped a hosted model, put a proxy in the middle, and billed for inference. Plenty of tools do. We chose not to, because the moment your design work and your prompts flow through someone else's servers, you have to trust that someone. BYOK collapses the list of parties who can see your work down to two: you, and the provider you already picked.
Multi-provider support keeps that from being a cage. Use Anthropic today, a local Ollama model tomorrow, OpenRouter for something exotic next week, all behind the same chat box and the same consent rules. Your keys sit in the OS vault. Your canvas stays yours until you decide, per item, to share a piece of it. That is the whole idea, and it is wired all the way down.
Want the specifics on managing keys and switching models? See Providers and models. Curious what the agent can actually do once it is connected? See What the AI can do.