{"record":{"id":"eb3b83648cead108","repo":"musistudio/claude-code-router","slug":"openrouter-model-id-must-be-author-slug-model-eb3b83","errorCode":null,"errorMessage":"OpenRouter model id must be author/slug: ${model}","messagePattern":"OpenRouter model id must be author/slug: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/providers/openrouter-provider-catalog.ts","lineNumber":222,"sourceCode":"  date.setUTCDate(date.getUTCDate() - 1);\n  return date.toISOString().slice(0, 10);\n}\n\nfunction bestPercent(left: number | undefined, right: number | undefined): number | undefined {\n  if (left === undefined) {\n    return right;\n  }\n  if (right === undefined) {\n    return left;\n  }\n  return Math.max(left, right);\n}\n\nfunction modelEndpointsPath(model: string): string {\n  const [author, ...slugParts] = model.split(\"/\");\n  const slug = slugParts.join(\"/\");\n  if (!author || !slug) {\n    throw new Error(`OpenRouter model id must be author/slug: ${model}`);\n  }\n  return `/api/v1/models/${encodeURIComponent(author)}/${encodeURIComponent(slug)}/endpoints`;\n}\n\nfunction baseProviderSlug(value: string): string {\n  return slugify(value.split(\"/\")[0] ?? \"\");\n}\n\nfunction providerNameFromSlug(slug: string): string {\n  return slug.split(\"-\").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(\" \");\n}\n\nfunction uniqueDisplayStrings(values: unknown[]): string[] {\n  const byKey = new Map<string, string>();\n  for (const value of values) {\n    const text = stringValue(value);\n    const key = text.toLowerCase();\n    if (!text || byKey.has(key)) {","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/providers/openrouter-provider-catalog.ts#L204-L240","documentation":"modelEndpointsPath expects OpenRouter model ids in 'author/slug' form and throws when the string has no slash or nothing after it. The path template /api/v1/models/{author}/{slug}/endpoints cannot be built safely from a malformed id.","triggerScenarios":"Calling getOpenRouterProviderCatalog with model set to something without a '/', such as 'gpt-4o', or with an empty slug like 'openai/'.","commonSituations":"Passing a plain model name instead of the OpenRouter qualified id (e.g. 'claude-3' vs 'anthropic/claude-3'); user-typed model ids from a config field; trailing-slash or whitespace-mangled ids.","solutions":["Use the fully qualified OpenRouter model id 'author/slug' (e.g. 'anthropic/claude-3.5-sonnet')","Validate/normalize user input against this pattern before calling the API: /^[^/]+\\/.+$/","Look up the correct slug in the OpenRouter models listing if unsure of the author prefix"],"exampleFix":"// before\ngetOpenRouterProviderCatalog({ model: \"claude-3.5-sonnet\" })\n// after\ngetOpenRouterProviderCatalog({ model: \"anthropic/claude-3.5-sonnet\" })","handlingStrategy":"validation","validationCode":"const MODEL_ID = /^[^/\\s]+\\/[^/\\s]+$/;\nif (!MODEL_ID.test(model)) throw new Error(`model must be author/slug: ${model}`);","typeGuard":"function isOpenRouterModelId(v: string): boolean { return /^[^/\\s]+\\/[^/\\s]+$/.test(v.trim()); }","tryCatchPattern":null,"preventionTips":["Store fully qualified model ids in config","Validate free-text model fields before calling the catalog"],"tags":["openrouter","model-id","validation"],"backgroundTag":"invalid-model-identifier","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}