Security Best Practices
API keys are bearer tokens — anyone who possesses one can make requests charged to your balance. Treating them with the same care as passwords is not optional. This page covers everything you need to keep your keys safe in development, staging, and production.
Never hardcode keys in source code
The single most common API key leak is a key pasted directly into source code and then committed to a repository. Even in private repositories this is dangerous: repositories get forked, made public by accident, or accessed by teammates who should not have production credentials.
Wrong — never do this:
// ❌ Hardcoded key — will end up in git history forever
const client = new OpenAI({
baseURL: "https://api.solrouter.io/ai",
apiKey: "sr_a8f3k2p9qx7rnv4t1mbwzc6yjdshe05",
});
Right — always load from the environment:
// ✅ Key lives in the environment, never in source code
const client = new OpenAI({
baseURL: "https://api.solrouter.io/ai",
apiKey: process.env.SOLROUTER_API_KEY,
});
The same rule applies to Python, Go, Ruby, and every other language. If you catch yourself typing sr_ inside a source file, stop and move the value to an environment variable instead.
Note: git history is permanent. If you accidentally commit a key, assume it is compromised and revoke it immediately — even if you delete the file in the next commit, the key is still visible in the repository history.
Use environment variables
Environment variables are the standard, cross-platform way to inject secrets into a running process without putting them in source code.
Local development with a .env file
Create a .env file in your project root and store the key there:
SOLROUTER_API_KEY=sr_YOUR_API_KEY
Load it at runtime:
Node.js 20.6+
node --env-file=.env index.js
Node.js with dotenv
import "dotenv/config"; // must be first import
const apiKey = process.env.SOLROUTER_API_KEY;
if (!apiKey) throw new Error("SOLROUTER_API_KEY is not set");
Python with python-dotenv
from dotenv import load_dotenv
import os
load_dotenv()
api_key = os.environ["SOLROUTER_API_KEY"] # raises KeyError if missing
Fail loudly if the key is missing
Always validate that the key is present at startup rather than letting it fail later on the first API call:
const apiKey = process.env.SOLROUTER_API_KEY;
if (!apiKey) {
throw new Error(
"SOLROUTER_API_KEY environment variable is not set. " +
"Add it to your .env file or set it in your deployment environment."
);
}
import os
api_key = os.environ.get("SOLROUTER_API_KEY")
if not api_key:
raise RuntimeError(
"SOLROUTER_API_KEY environment variable is not set. "
"Add it to your .env file or set it in your deployment environment."
)
Never commit .env to git
A .env file containing a real key is just as dangerous as a hardcoded key if it ends up in a repository. Add it to .gitignore before you ever create the file:
# Add to .gitignore
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
echo ".env.*.local" >> .gitignore
A minimal .gitignore entry for secrets:
# Environment variable files — never commit these
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
It is safe (and useful) to commit a .env.example file that lists every required variable with a placeholder value so other developers know what to configure:
# .env.example — safe to commit, no real secrets
SOLROUTER_API_KEY=sr_YOUR_KEY_HERE
DATABASE_URL=postgresql://localhost:5432/myapp
Use separate keys per project and environment
Never share a single API key across multiple projects or environments. Create one key per application per environment:
| Environment | Key name | Purpose |
|---|---|---|
| Local development | my-app-dev | Your personal dev machine |
| CI / automated tests | ci-integration-tests | Test runner in GitHub Actions / GitLab CI |
| Staging | my-app-staging | Pre-production environment |
| Production | my-app-production | Live traffic |
Why this matters:
- If a dev key leaks, production is unaffected — you revoke only the compromised key
- Usage logs in the dashboard are tagged by key name, so you can see exactly which environment consumed which tokens
- Staging and dev keys can be rotated freely without any production risk
- You can set up alerts or budget limits differently per environment
One key per service (least privilege)
In a microservices architecture, each service should have its own dedicated API key rather than sharing one across the whole system:
# Each service gets its own key
AUTH_SERVICE_SOLROUTER_KEY=sr_...
SUMMARIZER_SERVICE_SOLROUTER_KEY=sr_...
EMBEDDINGS_WORKER_SOLROUTER_KEY=sr_...
If the summarizer service is compromised, you revoke only SUMMARIZER_SERVICE_SOLROUTER_KEY. The auth service and embeddings worker continue operating normally, and you can clearly see in your usage logs which service was making requests.
CI/CD secrets
Never put real API keys in CI configuration files (.github/workflows/*.yml, .gitlab-ci.yml, Jenkinsfile, etc.). Use each platform's native secret management instead.
GitHub Actions
Add the secret in Repository → Settings → Secrets and variables → Actions → New repository secret, then reference it in your workflow:
# .github/workflows/integration-test.yml
name: Integration Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run integration tests
env:
SOLROUTER_API_KEY: ${{ secrets.SOLROUTER_API_KEY }}
run: npm test
The secret value is masked in all log output automatically.
Vercel
Set environment variables in Project → Settings → Environment Variables. Select which environments (Production, Preview, Development) each variable applies to:
# Vercel CLI — set for all environments
vercel env add SOLROUTER_API_KEY production
vercel env add SOLROUTER_API_KEY preview
In Next.js, the variable is automatically available in server-side code (Route Handlers, Server Actions, getServerSideProps) without any additional setup.
Railway
Set secrets in Service → Variables in the Railway dashboard, or via the CLI:
railway variables set SOLROUTER_API_KEY=sr_...
Fly.io
fly secrets set SOLROUTER_API_KEY=sr_...
Fly secrets are encrypted at rest and injected as environment variables at runtime.
Docker and docker-compose
Pass secrets as environment variables at runtime — never bake them into the image:
# docker-compose.yml
services:
api:
image: my-app:latest
environment:
# Reference from the host shell environment — never hardcode here
SOLROUTER_API_KEY: ${SOLROUTER_API_KEY}
# Run with the key from your shell environment
SOLROUTER_API_KEY=sr_... docker compose up
Use a secrets manager in production
For production workloads, dedicated secrets managers provide audit logs, fine-grained access control, and automatic rotation capabilities that plain environment variables cannot match.
| Platform | Service | How to inject |
|---|---|---|
| AWS | Secrets Manager / Parameter Store | Via Lambda env vars, ECS task definitions, or the SDK |
| GCP | Secret Manager | Mounted as env vars in Cloud Run or accessed via the SDK |
| Azure | Key Vault | Referenced in App Service configuration or via managed identity |
| HashiCorp Vault | Vault KV | Retrieved at startup via the Vault SDK or agent sidecar |
| Infisical | Infisical | CLI injection or SDK with end-to-end encryption |
Example — reading a secret from AWS Secrets Manager at startup:
import {
SecretsManagerClient,
GetSecretValueCommand,
} from "@aws-sdk/client-secrets-manager";
const secretsClient = new SecretsManagerClient({ region: "us-east-1" });
async function getSolRouterKey(): Promise<string> {
const response = await secretsClient.send(
new GetSecretValueCommand({ SecretId: "solrouter/api-key" })
);
if (!response.SecretString) throw new Error("Secret not found");
return response.SecretString;
}
const apiKey = await getSolRouterKey();
What to do if a key is leaked
If you believe a key has been exposed — found in a public repository, shared in a chat, included in a log file, or leaked in any other way — treat it as compromised and act immediately:
Step 1 — Revoke the key right now
- Open Account → API Keys in the SolRouter dashboard
- Find the affected key
- Click Revoke and confirm
Revocation is instant. The moment you revoke the key, all requests using it return 401 Unauthorized. Do not wait.
Step 2 — Create a replacement key
Create a new key with a fresh name, update your environment variables and secrets manager, and deploy the change to all affected services.
Step 3 — Check your usage logs
Review the request history in your dashboard to see whether the leaked key was used without your knowledge. If you see unexpected requests, note the timestamps and models used.
Step 4 — Remove the key from its source
If the key was committed to a git repository, remove it from the current branch — but also purge it from history using git filter-repo or open a GitHub support ticket to rotate the secret. Deleting the file is not enough; the key remains visible in the commit history.
# Remove a secret from git history (requires git-filter-repo)
pip install git-filter-repo
git filter-repo --path-glob '*.env' --invert-paths
Step 5 — Audit access controls
Review who has access to the affected environment and whether any other secrets may have been exposed at the same time.
Rotate keys regularly
Even if a key has not been leaked, rotating it periodically limits the window of exposure in case of an undetected leak. A reasonable rotation schedule:
| Environment | Rotation frequency |
|---|---|
| Production | Every 90 days or after any personnel change |
| Staging | Every 6 months |
| Development | When a developer leaves the team |
| CI | Whenever CI provider credentials are rotated |
Rotation procedure:
- Create a new key with the same name (or a versioned name like
production-2025) - Update the secret in your environment / secrets manager
- Deploy the new value to all services
- Verify all services are using the new key (check for
401errors in logs) - Revoke the old key
Never expose keys to the browser
SolRouter API keys must only be used in server-side code. Embedding a key in client-side JavaScript exposes it to every visitor of your site.
Wrong — key visible to anyone who opens DevTools:
// ❌ Frontend code — key is exposed in the browser bundle
const response = await fetch("https://api.solrouter.io/ai/chat/completions", {
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_SOLROUTER_API_KEY}`,
},
// ...
});
Right — key stays on the server:
// ✅ Next.js Route Handler — runs only on the server
// app/api/chat/route.ts
import { NextRequest, NextResponse } from "next/server";
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.solrouter.io/ai",
apiKey: process.env.SOLROUTER_API_KEY, // server-only, no NEXT_PUBLIC_ prefix
});
export async function POST(req: NextRequest) {
const { messages } = await req.json();
const completion = await client.chat.completions.create({
model: "openai/gpt-4o-mini",
messages,
});
return NextResponse.json(completion);
}
The browser calls your Route Handler, which calls SolRouter server-to-server. The API key is never sent to the browser.
Next steps
- API Keys — creating, naming, and revoking keys
- Session Tokens — how the web UI authenticates with HttpOnly JWT cookies
- Environment Setup —
.envfiles, dotenv, and platform-specific secret injection