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

  1. Copy .env.example to .env and paste a key from https://openrouter.ai (free tier available).
  2. Confirm the dotenv loader runs BEFORE openrouter-runner is imported (import order matters in ESM).
  3. Check the variable name is exactly OPENROUTER_API_KEY with no surrounding quotes or whitespace in .env.
  4. In CI, verify the secret is exposed to the job's environment, not just the repo settings.
  5. 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

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


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)