← Blog

How to Use an LLM API Key the Right Way

2026-09-04 · 5 min read · SubToAPI Team

Once you have an LLM API key, the actual work is making requests with it correctly, keeping it out of places it shouldn't be, and handling the responses and errors your app will inevitably hit. This guide walks through that process end to end: where to store the key, how to attach it to a request, what a typical call looks like, and the mistakes that cause outages or leaked credentials.

If you're still choosing a provider or generating your first key, that's a separate step. This article assumes you already have a key string in hand — something like sk-... or sub_live_... — and need to know what to do with it.

Step 1: Store the Key as an Environment Variable

Never paste an API key directly into your source code. Even in a "quick script," a hardcoded key ends up in git history, screenshots, or shared terminals sooner than you expect.

On macOS/Linux, add it to your shell profile or a local .env file:

export LLM_API_KEY="sk-your-key-here"

If you're using a .env file with a framework like Next.js, Express, or Django, make sure .env is listed in .gitignore before you commit anything. In CI/CD pipelines, store the key as a secret in your provider's settings (GitHub Actions secrets, Vercel environment variables, etc.) — never in the workflow YAML itself.

Step 2: Attach the Key to Your Requests

Most LLM APIs authenticate over HTTPS using a bearer token or a custom header. Check your provider's docs for the exact header name, since this varies slightly between vendors.

A generic example with curl:

curl https://api.example.com/v1/chat \
  -H "Authorization: Bearer $LLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "your-model-id",
    "messages": [
      {"role": "user", "content": "Summarize this article in two sentences."}
    ]
  }'

In JavaScript, read the key from process.env rather than a config object that might get logged or serialized:

const response = await fetch("https://api.example.com/v1/chat", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.LLM_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "your-model-id",
    messages: [{ role: "user", content: "Summarize this article in two sentences." }],
  }),
});

const data = await response.json();
console.log(data);

The pattern is identical across providers: a bearer token in the Authorization header, JSON body, JSON response. What differs is the exact request shape — model names, message formatting, and response fields.

Step 3: Handle Errors and Rate Limits

A working integration isn't just the happy path. At minimum, handle these cases:

async function callLLM(payload, retries = 3) {
  for (let attempt = 0; attempt < retries; attempt++) {
    const res = await fetch("https://api.example.com/v1/chat", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.LLM_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(payload),
    });

    if (res.ok) return res.json();
    if (res.status === 429 && attempt < retries - 1) {
      await new Promise(r => setTimeout(r, 2 ** attempt * 1000));
      continue;
    }
    throw new Error(`LLM request failed: ${res.status}`);
  }
}

Step 4: Use Streaming for Interactive Apps

If your app displays output to a user in real time (a chat UI, a live assistant), don't wait for the full response before rendering anything. Most LLM APIs support server-sent events or chunked streaming so tokens appear as they're generated. This is usually a flag on the request ("stream": true) plus a different way of reading the response body — an event loop instead of a single await response.json().

Step 5: Separate Keys by Environment and Purpose

Use different keys for development, staging, and production. This limits the blast radius if a development key leaks in a test log, and it lets you track usage and spend per environment. If your provider supports scoped or per-application keys, use one key per application rather than one key shared across every service you run — it makes revoking access for a single compromised service much cleaner.

This is one of the practical gaps SubToAPI addresses if you're building on top of a Claude subscription rather than a pay-per-token API account: it issues distinct sub_live_... application keys from a single dashboard, so each app or environment gets its own credential without you having to manage separate provider accounts. Requests go to standard HTTPS endpoints and support streaming and tool use the same way any LLM API call does — see the quickstart for the exact request format.

Step 6: Rotate and Revoke Keys When Needed

Treat API keys like passwords with an expiration date. Rotate them periodically, and revoke immediately if a key appears in a public repository, a shared log, or a support ticket. Most dashboards let you generate a new key and delete the old one without downtime if you update your environment variables and redeploy in the same window.

Common Mistakes to Avoid

Questions

Do I put the API key in the request URL or the header? Always the header. Query-string keys get logged by proxies, browsers, and server access logs, which defeats the purpose of keeping them private.

Can I use the same LLM API key across multiple projects? You can, but it's not recommended. Separate keys per project or environment make usage tracking and revocation much simpler if something goes wrong.

What should I do if my API key stops working? Check for typos, confirm the key hasn't been revoked or rotated, and verify you're using the correct header format for that provider. If it's a billing or plan issue, your provider's dashboard will usually show the reason directly.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →