How to Use an LLM API Key the Right Way
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:
- 401/403 — the key is invalid, revoked, or missing required permissions. Don't retry; surface the error and check your key configuration.
- 429 — you've hit a rate limit. Back off and retry with exponential delay rather than hammering the endpoint.
- 5xx — the provider is having issues. Retry a small number of times with backoff, then fail gracefully.
- Timeouts — LLM responses, especially longer ones, can take several seconds. Set a reasonable client timeout and consider streaming (see below) for anything user-facing.
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
- Committing
.envfiles to version control. - Logging full request payloads that include the
Authorizationheader. - Sharing one key across a whole team instead of issuing individual or per-app keys.
- Ignoring rate-limit responses and retrying immediately in a tight loop.
- Hardcoding the key in client-side JavaScript, where anyone can read it from the browser's network tab.
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.