{"record":{"id":"a0a1eda4e445e103","repo":"farion1231/cc-switch","slug":"model-id-needs-an-api-either-directly-or-from","errorCode":null,"errorMessage":"Model {{id}} needs an API, either directly or from the provider","messagePattern":"Model (.+?)\\} needs an API, either directly or from the provider","errorType":"validation","errorClass":"PiFormValidationError","httpStatus":null,"severity":"error","filePath":"src/components/providers/forms/PiProviderForm.tsx","lineNumber":1181,"sourceCode":"            true,\n          );\n        }\n        // Pi's schema supports rare per-model api/baseUrl overrides. Keep\n        // imported values losslessly, but use the provider-level format and\n        // endpoint as the normal product model.\n        const modelApi =\n          typeof model.passthrough.api === \"string\"\n            ? model.passthrough.api.trim()\n            : \"\";\n        const modelBaseUrl =\n          typeof model.passthrough.baseUrl === \"string\"\n            ? model.passthrough.baseUrl.trim()\n            : \"\";\n        // Existing explicit nodes may be partial overrides of a Pi built-in\n        // provider. Pi inherits the built-in transport in that case, so only\n        // require a complete transport when CC Switch creates a new provider.\n        if (!isEdit && !modelApi && !api.trim()) {\n          throw new PiFormValidationError(\n            t(\"pi.form.effectiveApiRequired\", { id }),\n            \"#pi-provider-api-select\",\n            true,\n          );\n        }\n        const effectiveUrl = modelBaseUrl || baseUrl.trim();\n        if (!isEdit && !effectiveUrl) {\n          throw new PiFormValidationError(\n            t(\"pi.form.effectiveBaseUrlRequired\", { id }),\n            \"#pi-provider-base-url\",\n          );\n        }\n        return {\n          ...model.passthrough,\n          id,\n          ...(includeName ? { name: displayName } : {}),\n          ...(includeReasoning ? { reasoning: model.reasoning } : {}),\n          ...(includeInput","sourceCodeStart":1163,"sourceCodeEnd":1199,"githubUrl":"https://github.com/farion1231/cc-switch/blob/0b5da510168914b251481654a568c3ffacd62cf4/src/components/providers/forms/PiProviderForm.tsx#L1163-L1199","documentation":"PiProviderForm computes each model's transport: modelApi = trimmed passthrough api override (model.passthrough.api), falling back to the provider-level api field. In create mode it throws pi.form.effectiveApiRequired (with the model id, focusing #pi-provider-api-select, revealing advanced) when both are empty — the model would have no API type to route with. Edit mode is exempt because existing explicit nodes may be partial overrides of a Pi built-in provider that inherits the built-in transport.","triggerScenarios":"Creating a custom provider, choosing/entering models, but leaving the provider-level API select empty while no model carries a passthrough api; also when the api select was cleared after a preset switch to 'custom'.","commonSituations":"Custom (non-preset) configuration where the user assumes the API type is inferred from the base URL; importing models whose passthrough lacks 'api'; preset data lost during form state resets.","solutions":["Pick a value in the provider-level API selector (advanced section auto-reveals) such as 'anthropic' or 'openai', then resubmit.","Alternatively give the specific model an api override in its passthrough settings if it must diverge from the provider default.","If you were editing an existing provider and hit this, verify you did not accidentally open the create form with cleared transport state."],"exampleFix":"// before\nconst api = \"\"; // provider API select left empty\nconst model = { id: \"m1\", passthrough: {} }; // no api override\nsubmit(identity); // throws \"Model m1 needs an API, ...\"\n\n// after\nconst api = \"openai\"; // set provider-level API type\nsubmit(identity);","handlingStrategy":"validation","validationCode":"const modelApi = typeof model.passthrough.api === \"string\" ? model.passthrough.api.trim() : \"\";\nif (!isEdit && !modelApi && !api.trim()) highlight(\"#pi-provider-api-select\", \"Choose the API type\");","typeGuard":"const hasApiOverride = (m: ModelRow): boolean => typeof m.passthrough.api === \"string\" && m.passthrough.api.trim().length > 0;","tryCatchPattern":"catch (e) {\n  if (e instanceof PiFormValidationError && e.fieldSelector === \"#pi-provider-api-select\") { setAdvancedOpen(true); setFormError(e.message); return; }\n  throw e;\n}","preventionTips":["Default the provider-level API select when a custom configuration is chosen.","For imports, populate passthrough.api only with non-empty strings."],"tags":["pi","form-validation","api-type","transport-config"],"backgroundTag":"required-form-field-missing","analyzedSha":"0b5da510168914b251481654a568c3ffacd62cf4","analyzedAt":"2026-08-20T14:29:00.113Z","contentChangedAt":"2026-08-20T14:29:00.113Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}