Claude API Environment Variables: Best Practices
Storing Claude API keys as environment variables is the right default, but doing it safely requires more than dropping a key into a .env file. The core best practices are: never commit secrets to version control, scope keys per environment (dev/staging/prod), load them through a dedicated config layer rather than scattering process.env calls, and rotate them on a schedule or immediately after any suspected leak.
This article covers the practical setup: naming conventions, .env file structure, framework-specific loading patterns, CI/CD secret injection, and how to handle multiple keys when you're running Claude in several environments or through a gateway like SubToAPI.
Why environment variables matter for API keys
Hardcoding an API key in source code means it ends up in your git history forever, even if you delete it later. Environment variables keep secrets out of your codebase and let you swap credentials per deployment without touching code. For Claude API usage — whether calling Anthropic directly or through a proxy — this is non-negotiable once you're past a weekend prototype.
The goal isn't just "don't commit the key." It's building a system where:
- Keys are easy to rotate without a deploy
- Different environments (local, staging, production) never share credentials
- Accidental logging or error reporting doesn't leak the key
- Team members can onboard without passing secrets over Slack
Naming conventions that scale
Pick a consistent prefix and stick to it. A reasonable pattern:
CLAUDE_API_KEY=sk-ant-xxxxx
SUBTOAPI_KEY=sub_live_xxxxx
CLAUDE_MODEL=claude-3-5-sonnet-20241022
CLAUDE_MAX_TOKENS=4096
Avoid generic names like API_KEY or TOKEN — as soon as you integrate a second service, ambiguous names cause bugs where the wrong key gets passed to the wrong client. Prefix by provider, and suffix by purpose if you have multiple keys per provider (CLAUDE_API_KEY_PROD, CLAUDE_API_KEY_DEV).
Structuring your .env files
Keep separate files per environment and never let .env.production exist on a developer's laptop:
.env.example # committed, no real values
.env.local # local dev, gitignored
.env.staging # loaded by CI/CD, never local
.env.production # loaded by CI/CD, never local
.env.example should list every required variable with placeholder values, so new team members know exactly what to configure:
CLAUDE_API_KEY=sk-ant-your-key-here
CLAUDE_MODEL=claude-3-5-sonnet-20241022
CLAUDE_MAX_TOKENS=1024
Always add .env* (except .env.example) to .gitignore before the first commit, not after.
Loading variables through a config module
Don't call process.env.CLAUDE_API_KEY directly throughout your codebase. Centralize it in one config module so validation and defaults live in one place:
// config.js
function required(name) {
const value = process.env[name];
if (!value) {
throw new Error(`Missing required env var: ${name}`);
}
return value;
}
export const config = {
claudeApiKey: required('CLAUDE_API_KEY'),
model: process.env.CLAUDE_MODEL || 'claude-3-5-sonnet-20241022',
maxTokens: parseInt(process.env.CLAUDE_MAX_TOKENS || '1024', 10),
};
This fails fast at startup instead of throwing an unauthorized error deep in a request handler, and it gives you one place to add validation logic (e.g., checking key format or length).
Handling secrets in CI/CD
CI/CD pipelines are a common leak vector because logs often echo environment variables during debugging. Practical rules:
- Store secrets in your CI provider's encrypted secrets store (GitHub Actions secrets, GitLab CI/CD variables, etc.), never in the pipeline YAML itself
- Mask secret values in build logs — most CI providers do this automatically for variables marked as secret, but double-check with a test run
- Scope CI secrets to the specific jobs that need them rather than exposing them to every step
- Rotate CI-stored keys whenever a contributor with repo access leaves the team
For containerized deployments, inject secrets at runtime via your orchestrator's secret store (Kubernetes Secrets, ECS task definitions, etc.) rather than baking them into the image.
Multiple keys for multiple environments
If you're calling the Claude API directly, you likely need separate API keys for development and production so a bug in staging can't burn through your production rate limit or budget. The same logic applies if you're using a gateway service.
With SubToAPI, each application you register gets its own sub_live_... key, so you can issue separate keys per environment and track usage independently in the dashboard without needing separate Anthropic accounts. A typical setup:
# .env.staging
SUBTOAPI_KEY=sub_live_staging_xxxxx
# .env.production
SUBTOAPI_KEY=sub_live_prod_xxxxx
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Summarize this changelog."}]
}'
This keeps per-environment usage visible without manually tagging every request, and if a staging key leaks, you can revoke it without touching production. See the quickstart guide for the full setup and pricing for plan details if you're evaluating team seats.
Rotation and revocation checklist
Treat key rotation as a routine task, not an emergency-only procedure:
- Rotate keys on a fixed schedule (quarterly is a reasonable baseline for most teams)
- Rotate immediately if a key appears in a log, error report, screen share, or public repo
- Keep the old key active for a short overlap window when rotating in production to avoid downtime, then revoke it
- Document who has access to each environment's secrets store — fewer people with production access means a smaller blast radius if something goes wrong
Avoiding common leaks
A few places environment variables leak that aren't always obvious:
- Error tracking tools (Sentry, Bugsnag) sometimes capture full request objects, including headers with your API key — scrub sensitive headers before reporting
- Client-side bundles: never expose a Claude or SubToAPI key in frontend JavaScript. All API calls should go through a backend you control
- Docker image layers: if you
COPY .envinto an image during build, the key is baked into every layer's history — inject secrets at runtime instead
FAQ
Should I use a .env file or a secrets manager like Vault?
For small teams and single-server deployments, .env files loaded via a library like dotenv are fine, as long as they're gitignored and never shared outside the team. Once you have multiple services, multiple environments, or compliance requirements, move to a dedicated secrets manager (AWS Secrets Manager, HashiCorp Vault, Doppler) for centralized access control and audit logs.
How do I avoid accidentally logging my API key?
Centralize key usage in one config module (as shown above) so it's never interpolated directly into log statements. Also configure your error tracking tool to redact Authorization headers before sending error reports, since full request objects are a common accidental leak point.
What's the difference between dev and prod environment variables for the Claude API?
They should point to entirely separate API keys, ideally with separate rate limits and billing visibility, so a bug or runaway loop in development can't exhaust your production quota or budget. If you're using SubToAPI, each environment can have its own sub_live_... key tracked separately in the dashboard — see /docs for key management details.