Lesson 4 of 6 · about 25 minutes
Your LLM API key
The win Your production URL returns an answer from a language model, and the key that paid for it has never been in Git, in your shell history, or in a browser.
First, recall
You change a variable in the dashboard. When does production see the new value?
Only after a new deployment. Existing deployments keep the values they were created with. (Lesson 3)
What does the NEXT_PUBLIC_ prefix do, and why must an API key never have it?
It inlines the value into JavaScript sent to every visitor at build time. A key with that prefix is published.
Which environment does a push to a branch other than main deploy to?
Preview. (Lesson 2)
What is different about a secret
Lesson 3's value was a Config: anyone with access to the project can read it back. An API key should be a Secret. Once saved, nobody can read it again, including you. Your deployments can still use it, and you can replace it.
Two consequences shape the steps below.
Secrets exist in Production and Preview only. Development cannot hold one. So the laptop gets its own key, a different one. If your laptop key leaks you revoke it and production never notices.
The key is typed at a prompt, never on a command line. In Lesson 3 you passed --value. That writes the value into your shell history, which is fine for an app name and wrong for a key.
Do it
-
Create two keys in your provider's console, named so you can tell them apart later:
beachhead-prodandbeachhead-dev. For Anthropic, see Get an API key. Keep the tab open; each key is shown once. -
Store the production key for Production, then again for Preview. Paste
beachhead-prodwhen prompted. If asked whether to make it sensitive, accept the default, yes.vercel env add ANTHROPIC_API_KEY production vercel env add ANTHROPIC_API_KEY preview -
Store the dev key for Development, in its own command. Paste
beachhead-dev.vercel env add ANTHROPIC_API_KEY development -
Pull, and check Git cannot see it.
vercel env pull git status.env.localnow holds the dev key, andgit statusdoes not mention the file. -
Add the SDK and one route. The SDK reads
ANTHROPIC_API_KEYfrom the environment by itself, so the key never appears in your code.npm install @anthropic-ai/sdkapp/api/ask/route.ts
import Anthropic from "@anthropic-ai/sdk"; export async function GET() { if (!process.env.ANTHROPIC_API_KEY) { return Response.json( { error: "ANTHROPIC_API_KEY is not set in this environment" }, { status: 500 }, ); } // Reads ANTHROPIC_API_KEY from the environment. Never pass the key from the browser. const client = new Anthropic(); try { const message = await client.messages.create({ model: "claude-opus-5-5", max_tokens: 16000, messages: [{ role: "user", content: "Reply with exactly: Beachhead secured." }], }); const answer = message.content .flatMap((block) => (block.type === "text" ? [block.text] : [])) .join(""); return Response.json({ answer }); } catch (error) { if (error instanceof Anthropic.AuthenticationError) { return Response.json({ error: "The provider rejected the API key" }, { status: 500 }); } if (error instanceof Anthropic.APIError) { return Response.json({ error: `Provider error ${error.status}` }, { status: 502 }); } throw error; } } -
Try it locally. Run
npm run dev, then openlocalhost:3000/api/healthandlocalhost:3000/api/ask.{"env":"local","hasLlmKey":true,"hasDatabaseUrl":false} {"answer":"Beachhead secured."} -
Ship it and check Production. The push creates a new deployment, which is what picks up the keys you added in step 2.
git add -A git commit -m "Add LLM smoke test route" git pushOpen your production domain at
/api/health, then/api/ask. An answer here is the win. -
Close the door behind you.
/api/askis public. Anyone who finds the URL can spend your credits by reloading it. Delete the route now that it has done its job, and push.git rm app/api/ask/route.ts git commit -m "Remove public smoke test route" git push
The lesson inside step 8 Hiding a key from the browser is half of protecting it. The other half is controlling who can make your server use it. Before a real feature calls the model, put it behind sign-in or a rate limit, and set a monthly spend limit in the provider's console.
When a key leaks
You rotate it, in this order: create a new key, update the variable on Vercel, redeploy, confirm the new deployment works, and only then revoke the old key. Reverse the last steps and production is down for as long as the redeploy takes. The commands are on the playbook.
Check yourself
You saved ANTHROPIC_API_KEY as a Secret for Production last week. Where can you read its value back today?
Sensitive values “cannot be viewed later in the dashboard or with vercel env ls.” If you lose the key, make a new one at the provider and update the variable. Source
Which environments can hold a Secret (Sensitive) variable?
“You can only create sensitive environment variables in the preview and production environments.” That is why the laptop gets a separate key. Source
A production key has leaked. Which order rotates it without downtime?
The old key must keep working until a deployment carrying the new one is live. Revoking first breaks every running deployment. Source
Why is echo sk-… | vercel env add NAME production discouraged for a secret?
Piping works, and the value is encrypted like any other. The problem is on your side: the command, key included, is saved in your shell history. Source
Read this Sensitive environment variables, then the “Sensitive” section of vercel env for the table of defaults per environment.
Ask your teacher Using OpenAI, Gemini or the Vercel AI Gateway instead? Say which in your Claude Code session and ask for this lesson's route rewritten for it.