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:

EnvironmentKey namePurpose
Local developmentmy-app-devYour personal dev machine
CI / automated testsci-integration-testsTest runner in GitHub Actions / GitLab CI
Stagingmy-app-stagingPre-production environment
Productionmy-app-productionLive 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.

PlatformServiceHow to inject
AWSSecrets Manager / Parameter StoreVia Lambda env vars, ECS task definitions, or the SDK
GCPSecret ManagerMounted as env vars in Cloud Run or accessed via the SDK
AzureKey VaultReferenced in App Service configuration or via managed identity
HashiCorp VaultVault KVRetrieved at startup via the Vault SDK or agent sidecar
InfisicalInfisicalCLI 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

  1. Open Account → API Keys in the SolRouter dashboard
  2. Find the affected key
  3. 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:

EnvironmentRotation frequency
ProductionEvery 90 days or after any personnel change
StagingEvery 6 months
DevelopmentWhen a developer leaves the team
CIWhenever CI provider credentials are rotated

Rotation procedure:

  1. Create a new key with the same name (or a versioned name like production-2025)
  2. Update the secret in your environment / secrets manager
  3. Deploy the new value to all services
  4. Verify all services are using the new key (check for 401 errors in logs)
  5. 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.env files, dotenv, and platform-specific secret injection