srbhr/Resume-Matcher · error · Error

${data.detail || Failed to update API keys (status ${res.sta

Error message

${data.detail || Failed to update API keys (status ${res.status}).}

What it means

updateApiKeys (saving per-provider keys via the /config/api-keys endpoint) throws this when the response is not ok. It prefers the backend's `detail` string (FastAPI validation/auth message) and falls back to the generic status-code text when the body isn't JSON. Called from the settings key-management `handleSave`.

Source

Thrown at apps/frontend/lib/api/config.ts:500

  if (!res.ok) {
    throw new Error(`Failed to load API key status (status ${res.status}).`);
  }

  return res.json();
}

// Update API keys for one or more providers
export async function updateApiKeys(keys: ApiKeysUpdateRequest): Promise<ApiKeysUpdateResponse> {
  const res = await apiFetch('/config/api-keys', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'include',
    body: JSON.stringify(keys),
  });

  if (!res.ok) {
    const data = await res.json().catch(() => ({}));
    throw new Error(data.detail || `Failed to update API keys (status ${res.status}).`);
  }

  return res.json();
}

// Delete API key for a specific provider
export async function deleteApiKey(provider: ApiKeyProvider): Promise<void> {
  const res = await apiFetch(`/config/api-keys/${provider}`, {
    method: 'DELETE',
    credentials: 'include',
  });

  if (!res.ok) {
    const data = await res.json().catch(() => ({}));
    throw new Error(data.detail || `Failed to delete API key (status ${res.status}).`);
  }
}

View on GitHub (pinned to 116f9cc3b0)

Solutions

  1. Read the surfaced detail: it usually names the offending provider/field — correct the key entry and resubmit.
  2. 401/403 → re-authenticate then retry the save; 404 → fix proxy rewrites/BACKEND_ORIGIN; 5xx → check backend key-store logs.
  3. Validate the key format client-side against PROVIDER_INFO (prefix/length expectations) before calling updateApiKeys.
  4. Catch in handleSave and surface the message next to the key form so the user can fix input without losing edits.

Example fix

// before
await updateApiKeys({ openai: keyInput });

// after
if (!/^sk-/.test(keyInput.trim())) {
  setKeyError('OpenAI keys start with sk-');
  return;
}
try {
  await updateApiKeys({ openai: keyInput.trim() });
} catch (e) {
  setKeyError(e instanceof Error ? e.message : 'Save failed');
}
Defensive patterns

Strategy: validation

Validate before calling

const SUPPORTED = ['openai', 'openai_compatible', 'azure_foundry', 'anthropic', 'openrouter', 'gemini', 'deepseek', 'groq', 'ollama'];
if (Object.keys(keys).some((p) => !SUPPORTED.includes(p))) {
  throw new Error('Unknown provider in key update');
}

Type guard

function isProviderKeys(x: unknown): x is Record<string, string> {
  return typeof x === 'object' && x !== null &&
    Object.values(x).every((v) => typeof v === 'string');
}

Try / catch

try {
  await updateApiKeys(keys);
} catch (e) {
  const msg = e instanceof Error ? e.message : 'Save failed';
  setKeyError(msg); // backend detail surfaces here
}

Prevention

When it happens

Trigger: Non-2xx on the API-keys update: 401/403 (session expired, CSRF), 422 (payload violates schema — e.g. unknown provider id or malformed key object), 400 (backend rejects a key, e.g. provider-specific format check or encryption failure), 404 (proxy/route misconfiguration), 500 (key store write failed).

Common situations: Pasting a provider key into the wrong provider slot; saving right after the session timed out; backend upgraded with stricter key validation than the running frontend expects; disk/DB issue preventing persistence of the encrypted key.

Related errors


AI-assisted analysis of srbhr/Resume-Matcher@116f9cc3b0 (2026-08-28). Data as JSON: /api/errors/704ea93e7f2c4b59. Report an issue: GitHub.