santifer/career-ops · error · Error
OPENROUTER_API_KEY not found. Copy .env.example to .env and…
Error message
OPENROUTER_API_KEY not found. Copy .env.example to .env and add your API key. Free key: https://openrouter.ai
What it means
Thrown at the top of callOpenRouter() when `process.env.OPENROUTER_API_KEY` is undefined or empty. The library refuses to make any API call without credentials. The multi-line message includes exact setup steps and a link to get a free key.
Solutions
- Copy .env.example to .env and paste a key from https://openrouter.ai (free tier available).
- Confirm the dotenv loader runs BEFORE openrouter-runner is imported (import order matters in ESM).
- Check the variable name is exactly OPENROUTER_API_KEY with no surrounding quotes or whitespace in .env.
- In CI, verify the secret is exposed to the job's environment, not just the repo settings.
- Test with `echo ${OPENROUTER_API_KEY:+set}` in the same shell/runner that executes the script.
Example fix
// before (runner imported before env is loaded)
import { callOpenRouter } from './openrouter-runner.mjs';
import dotenv from 'dotenv';
dotenv.config();
// after
import dotenv from 'dotenv';
dotenv.config();
import { callOpenRouter } from './openrouter-runner.mjs'; Defensive patterns
Strategy: validation
Validate before calling
import 'dotenv/config'; // must run before runner modules
if (!process.env.OPENROUTER_API_KEY?.trim()) {
throw new Error('OPENROUTER_API_KEY missing — copy .env.example to .env and add a key from https://openrouter.ai');
} Try / catch
try {
await callOpenRouter(systemPrompt, userMessage);
} catch (err) {
if (err.message.includes('OPENROUTER_API_KEY not found')) {
console.error('Credentials missing. Setup: cp .env.example .env && add key from https://openrouter.ai');
process.exit(1);
}
throw err;
} Prevention
- Put `import 'dotenv/config'` as the first import of every entrypoint.
- Validate required env vars at startup with an assert-env helper.
- In CI, mark OPENROUTER_API_KEY as a required secret and fail fast when absent.
- Avoid stray quotes/whitespace around the value in .env.
When it happens
Trigger: Any evaluation run where the environment lacks OPENROUTER_API_KEY: .env file missing, .env present but not loaded before this module reads process.env, variable misspelled (OPEN_ROUTER_API_KEY), or key defined only in shell profile of a different user/session.
Common situations: Fresh clone without `cp .env.example .env`; CI/CD job where the secret was not injected into env; dotenv imported after openrouter-runner; running via systemd/cron where interactive shell env vars are absent.
Understand the failure class
Background: "API key is required" / "API key not found" / "No API key was set": the missing-api-key error family across 16 libraries — this error's family across 16 libraries.
Related errors
- H1B_INDEX_PATH is set but empty. Unset it to use the…
- HTTP
- [models] Failed to fetch free model list
- [models] Failed to fetch free model list
- NOTION_ACCESS_TOKEN is not set (.env) — the Notion plugin…
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/d7c185f43e49fa73.
Report an issue: GitHub.
Appendix: source
Thrown at openrouter-runner.mjs:224
// Providers that support caching (Anthropic, Gemini, …) reuse the prefix across
// back-to-back calls within the cache TTL; providers that don't simply ignore
// the field, so this is a safe passthrough that never changes the prompt text.
export function buildCachedSystemMessage(systemPrompt) {
return {
role: 'system',
content: [
{ type: 'text', text: systemPrompt, cache_control: { type: 'ephemeral' } },
],
};
}
// ---------------------------------------------------------------------------
// OpenRouter API call — automatic model rotation with fallback
// ---------------------------------------------------------------------------
async function callOpenRouter(systemPrompt, userMessage) {
const key = process.env.OPENROUTER_API_KEY;
if (!key) {
throw new Error(
'OPENROUTER_API_KEY not found.\n' +
'Copy .env.example to .env and add your API key.\n' +
'Free key: https://openrouter.ai'
);
}
const pinnedModel = process.env.CAREER_OPS_MODEL;
if (pinnedModel) {
activeModel = pinnedModel;
process.stdout.write(`[model] ${pinnedModel} (pinned) ... `);
const body = JSON.stringify({
model: pinnedModel,
messages: [
buildCachedSystemMessage(systemPrompt),
{ role: 'user', content: userMessage },
],
max_tokens: MAX_TOKENS,
});View on GitHub (pinned to aac998c7ed)