{"record":{"id":"4a91ac1b182593ba","repo":"different-ai/openwork","slug":"invalid-api-keys","errorCode":"invalid_api_keys","errorMessage":"${error.message}","messagePattern":"\\$\\{error\\.message\\}","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"ee/apps/den-api/src/routes/org/llm-providers.ts","lineNumber":365,"sourceCode":"  existingProvider: Pick<LlmProviderRow, \"apiKey\" | \"providerConfig\"> | null\n  apiKey?: string\n  apiKeys?: Record<string, string>\n}) {\n  try {\n    return resolveProviderCredential({\n      envNames: readProviderEnvNames(input.providerConfig),\n      existing: input.existingProvider\n        ? {\n            value: input.existingProvider.apiKey,\n            envNames: readProviderEnvNames(input.existingProvider.providerConfig ?? {}),\n          }\n        : null,\n      apiKey: input.apiKey,\n      apiKeys: input.apiKeys,\n    })\n  } catch (error) {\n    if (error instanceof ProviderCredentialError) {\n      throw createFailure(400, \"invalid_api_keys\", error.message)\n    }\n\n    throw error\n  }\n}\n\nfunction resolveMemberCredentialSecret(\n  provider: Pick<LlmProviderRow, \"providerConfig\">,\n  input: z.infer<typeof memberCredentialWriteSchema>,\n) {\n  const secret = resolveCredentialColumn({\n    providerConfig: provider.providerConfig,\n    existingProvider: null,\n    apiKey: input.apiKey,\n    apiKeys: input.apiKeys,\n  })\n  if (!secret) {\n    throw createFailure(400, \"invalid_api_keys\", \"Provide a non-empty credential.\")","sourceCodeStart":347,"sourceCodeEnd":383,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-api/src/routes/org/llm-providers.ts#L347-L383","documentation":"resolveCredentialColumn delegates credential resolution to resolveProviderCredential, which throws ProviderCredentialError when the supplied apiKey/apiKeys/providerConfig cannot be resolved into a usable credential. This is translated into a 400 with code invalid_api_keys carrying the underlying error.message as the detail.","triggerScenarios":"Creating or updating an org LLM provider with apiKey/apiKeys that are empty, malformed, or inconsistent with providerConfig (e.g. missing key when providerConfig expects one, or invalid key entries in the multi-key map).","commonSituations":"Secrets not injected in CI so apiKey is empty string; rotating keys and leaving the map with blank entries; pasting a key with surrounding whitespace/quotes; switching source/models_dev but leaving incompatible credential fields.","solutions":["Read error.message in the 400 response — it states the exact credential problem.","Provide a non-empty apiKey or a well-formed apiKeys map matching the provider's expected credential shape.","Trim whitespace/quotes from pasted keys and confirm the provider requires a key at all (some local providers do not).","Verify secrets are present in the environment/CI before submitting."],"exampleFix":"// before\nawait createProvider({ source: \"models_dev\", providerId: \"openai\", apiKey: process.env.OPENAI_API_KEY ?? \"\" })\n// after\nif (!process.env.OPENAI_API_KEY) throw new Error(\"OPENAI_API_KEY missing\");\nawait createProvider({ source: \"models_dev\", providerId: \"openai\", apiKey: process.env.OPENAI_API_KEY })","handlingStrategy":"validation","validationCode":"if (!apiKey || typeof apiKey !== 'string' || apiKey.trim() === '') throw new Error('apiKey must be non-empty before calling the API');","typeGuard":"function hasCredential(input: { apiKey?: string | null; apiKeys?: Record<string, string> | null }): boolean { return Boolean(input.apiKey?.trim()) || Object.values(input.apiKeys ?? {}).some(v => v.trim() !== '') }","tryCatchPattern":"try { await createProvider(payload) } catch (e) { if (e.code === 'invalid_api_keys') console.error('credential rejected:', e.message); }","preventionTips":["Read error.message from the 400 response — it names the exact problem","Verify secret env vars are set in CI before submission","Trim whitespace/quotes from pasted API keys","Match credential shape to the provider (single key vs key map)"],"tags":["http-400","api-keys","validation"],"backgroundTag":"invalid-api-key","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}