{"record":{"id":"1ac8d9196d740b42","repo":"can1357/oh-my-pi","slug":"provider-providername-model-modeldef-id-no","errorCode":null,"errorMessage":"Provider ${providerName}, model ${modelDef.id}: no \"api\" specified.","messagePattern":"Provider (.+?), model (.+?): no \"api\" specified\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/config/model-registry.ts","lineNumber":2607,"sourceCode":"\n\t\tif (config.models && config.models.length > 0) {\n\t\t\t// Build model overlays that persist across refresh() cycles\n\t\t\tconst newOverlays: CustomModelOverlay[] = [];\n\t\t\tfor (const modelDef of config.models) {\n\t\t\t\tconst overlay = buildCustomModelOverlay(\n\t\t\t\t\tproviderName,\n\t\t\t\t\tconfig.baseUrl!,\n\t\t\t\t\tconfig.api,\n\t\t\t\t\tconfig.headers,\n\t\t\t\t\tconfig.apiKey,\n\t\t\t\t\tconfig.authHeader,\n\t\t\t\t\tconfig.compat,\n\t\t\t\t\tundefined,\n\t\t\t\t\tconfig.remoteCompaction,\n\t\t\t\t\tmodelDef as CustomModelDefinitionLike,\n\t\t\t\t);\n\t\t\t\tif (!overlay) {\n\t\t\t\t\tthrow new Error(`Provider ${providerName}, model ${modelDef.id}: no \"api\" specified.`);\n\t\t\t\t}\n\t\t\t\tnewOverlays.push(overlay);\n\t\t\t}\n\t\t\t// Store as runtime overlays so they survive #reloadStaticModels()\n\t\t\tthis.#runtimeModelOverlays = this.#runtimeModelOverlays.filter(m => m.provider !== providerName);\n\t\t\tthis.#runtimeModelOverlays.push(...newOverlays);\n\n\t\t\t// A modifier is explicitly a whole-catalog transform and may throw\n\t\t\t// based on another provider's models. Preserve registration-time\n\t\t\t// execution/error reporting by materializing only for this case.\n\t\t\tif (config.oauth?.modifyModels && !this.#hasFullSnapshot) {\n\t\t\t\tlogger.time(`modelRegistry:materializeModifier:${providerName}`, () => this.#ensureFullSnapshot());\n\t\t\t}\n\t\t\tif (config.oauth?.modifyModels) {\n\t\t\t\tthis.#runtimeModelModifiers.set(providerName, config.oauth.modifyModels);\n\t\t\t} else {\n\t\t\t\tthis.#runtimeModelModifiers.delete(providerName);\n\t\t\t}","sourceCodeStart":2589,"sourceCodeEnd":2625,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/config/model-registry.ts#L2589-L2625","documentation":"When registering per-model overlays in registerProvider, each custom model definition must resolve to an overlay carrying an `api` (either provider-level `config.api` or `modelDef.api`). If building the overlay returns null — meaning no API could be determined for that model — registerProvider throws this error naming the provider and model id.","triggerScenarios":"Calling registerProvider(name, { models: [{ id: \"x\", ... }] }) where neither the provider config nor the individual model definition specifies `api`. Triggered per offending modelDef inside the models array.","commonSituations":"Declaring custom models for a gateway whose dialect the registry cannot infer; copying a model entry that worked under a provider that had a provider-level api, into a provider without one; typos like `API` vs `api` or putting api inside the wrong nesting level of the model definition.","solutions":["Add `api` to the offending model definition in the `models` array.","Alternatively set a provider-level `api` so all models inherit it.","Verify the field name/casing is exactly `api` on the model definition object."],"exampleFix":"// before\nregisterProvider(\"gw\", { models: [{ id: \"mini\", baseUrl: \"https://gw/v1\" }] });\n// after\nregisterProvider(\"gw\", { models: [{ id: \"mini\", api: \"openai-completions\", baseUrl: \"https://gw/v1\" }] });","handlingStrategy":"validation","validationCode":"for (const m of cfg.models ?? []) {\n  if (!cfg.api && !m.api) throw new Error(`model ${m.id}: set api at provider or model level`);\n}","typeGuard":"function everyModelHasApi(cfg: ProviderConfigInput): boolean {\n  return (cfg.models ?? []).every(m => Boolean(cfg.api || (m as { api?: unknown }).api));\n}","tryCatchPattern":"try {\n  registry.registerProvider(name, cfg);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('no \"api\" specified')) {\n    const id = err.message.match(/model ([^:]+):/)?.[1];\n    throw new Error(`Fix config: add \"api\" to model ${id} or set provider-level api`);\n  }\n  throw err;\n}","preventionTips":["Prefer a provider-level `api` so every model inherits it; only override per model.","Run the everyModelHasApi precheck on the models array before registration.","Watch field casing: it is `api`, all lowercase, on the model definition.","When moving model entries between providers, re-check that the destination provider declares api."],"tags":["configuration","validation","model-registry","api"],"backgroundTag":"missing-required-config-field","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}