Early Bird · 15% off all templates with code

Guide7 min readby Minnie

I Built an AI SaaS That Runs With Zero API Keys (Here's the Trick)

The demo of an AI product usually breaks the moment someone tries it without a key. Here's a pattern that lets an AI SaaS run, demo, and test with no API key at all - then swap to a real model in one function.

Here's a problem every AI product hits: the demo only works if someone plugs in an API key. Share the repo, and the first thing a new developer sees is a wall of errors because ANTHROPIC_API_KEY isn't set. Put it in a template, and buyers can't run it until they've signed up for a provider. Deploy a preview, and it costs you tokens every time a stranger clicks around. There's a pattern that solves all of this: build the AI feature so it runs with zero keys, and make going live a one-function swap.

The trick: a mock engine that streams like the real thing

The key insight is that your UI shouldn't care where the tokens come from. If the streaming interface consumes a stream of text chunks, it doesn't matter whether those chunks are generated by a model or by a function that returns canned, context-aware replies and streams them token by token. So you build the mock to behave exactly like the real thing: keyword-match the prompt to a sensible answer, then stream it out with small delays so it feels alive.

// A keyless engine that streams like a real model.
const REPLIES: { match: RegExp; reply: string }[] = [
  { match: /metric|summary|how.*doing/i, reply: 'MRR is up 8.2% this week...' },
  { match: /invite|teammate/i, reply: 'To invite a teammate: open Settings...' },
];

const FALLBACK = 'In this demo I answer from a small set of canned replies so it runs with no API key.';

export function generateReply(prompt: string): string {
  return REPLIES.find((r) => r.match.test(prompt))?.reply ?? FALLBACK;
}

Then the UI streams that string one token at a time - the same loop it would use for a real model - so the mock is visually indistinguishable from a live assistant:

const tokens = generateReply(prompt).split(/(\s+)/);
let out = '';
for (const token of tokens) {
  out += token;
  setMessage(out);              // re-render as it "streams"
  await new Promise((r) => setTimeout(r, 20));
}

Why this is worth doing

Making the AI keyless by default isn't a shortcut - it removes friction at every point where someone first meets your product.

  • Demos never break - anyone can click the live preview and the assistant just works, at zero token cost to you
  • Templates and open-source repos run on first clone, before any signup
  • Onboarding is instant - new devs see the feature working before they wire anything
  • Tests are deterministic - no network, no key, no flaky model responses in CI
  • Design and iteration happen offline - you can build the whole UI on a plane

The one-function swap to go live

Because the entire model lives behind a single function, going from demo to production is a swap, not a rewrite. Replace the body of generateReply with a real streaming call (Claude via the Vercel AI SDK, behind a route handler so your key stays server-side) and the streaming UI never changes - it's still consuming a token stream, just a smarter one.

The principle underneath: isolate the model behind one seam. Whether the tokens come from a canned reply or a frontier model is an implementation detail the UI should never know about. Get that boundary right and the same codebase runs as a keyless demo and a production product.

Keel is built exactly this way - a keyless mock engine powers the assistant, Ask AI slide-over, and Cmd+K bar out of the box, then swaps to Claude via the Vercel AI SDK in one function. From $79, one-time.

See Keel