Vercel Hobby · Beachhead course

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

  1. Create two keys in your provider's console, named so you can tell them apart later: beachhead-prod and beachhead-dev. For Anthropic, see Get an API key. Keep the tab open; each key is shown once.

  2. Store the production key for Production, then again for Preview. Paste beachhead-prod when 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
  3. Store the dev key for Development, in its own command. Paste beachhead-dev.

    vercel env add ANTHROPIC_API_KEY development
  4. Pull, and check Git cannot see it.

    vercel env pull
    git status

    .env.local now holds the dev key, and git status does not mention the file.

  5. Add the SDK and one route. The SDK reads ANTHROPIC_API_KEY from the environment by itself, so the key never appears in your code.

    npm install @anthropic-ai/sdk

    app/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;
      }
    }
  6. Try it locally. Run npm run dev, then open localhost:3000/api/health and localhost:3000/api/ask.

    {"env":"local","hasLlmKey":true,"hasDatabaseUrl":false}
    {"answer":"Beachhead secured."}
  7. 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 push

    Open your production domain at /api/health, then /api/ask. An answer here is the win.

  8. Close the door behind you. /api/ask is 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?

  • In the project dashboard
  • With vercel env ls
  • Nowhere; replace it instead

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?

  • Production and Preview only
  • Production and Development only
  • All three built-in environments

“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?

  • Revoke old, new key, update Vercel, redeploy
  • New key, update Vercel, redeploy, revoke old
  • New key, revoke old, update Vercel, redeploy

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?

  • The value lands in shell history
  • The CLI rejects all piped input
  • Piped values skip encryption at rest

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.