can1357/oh-my-pi · error

Provider ${providerName}, model ${modelDef.id}: no "api" spe

Error message

Provider ${providerName}, model ${modelDef.id}: no "api" specified.

What it means

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.

Source

Thrown at packages/coding-agent/src/config/model-registry.ts:2607

		if (config.models && config.models.length > 0) {
			// Build model overlays that persist across refresh() cycles
			const newOverlays: CustomModelOverlay[] = [];
			for (const modelDef of config.models) {
				const overlay = buildCustomModelOverlay(
					providerName,
					config.baseUrl!,
					config.api,
					config.headers,
					config.apiKey,
					config.authHeader,
					config.compat,
					undefined,
					config.remoteCompaction,
					modelDef as CustomModelDefinitionLike,
				);
				if (!overlay) {
					throw new Error(`Provider ${providerName}, model ${modelDef.id}: no "api" specified.`);
				}
				newOverlays.push(overlay);
			}
			// Store as runtime overlays so they survive #reloadStaticModels()
			this.#runtimeModelOverlays = this.#runtimeModelOverlays.filter(m => m.provider !== providerName);
			this.#runtimeModelOverlays.push(...newOverlays);

			// A modifier is explicitly a whole-catalog transform and may throw
			// based on another provider's models. Preserve registration-time
			// execution/error reporting by materializing only for this case.
			if (config.oauth?.modifyModels && !this.#hasFullSnapshot) {
				logger.time(`modelRegistry:materializeModifier:${providerName}`, () => this.#ensureFullSnapshot());
			}
			if (config.oauth?.modifyModels) {
				this.#runtimeModelModifiers.set(providerName, config.oauth.modifyModels);
			} else {
				this.#runtimeModelModifiers.delete(providerName);
			}

View on GitHub (pinned to 9690622007)

Solutions

  1. Add `api` to the offending model definition in the `models` array.
  2. Alternatively set a provider-level `api` so all models inherit it.
  3. Verify the field name/casing is exactly `api` on the model definition object.

Example fix

// before
registerProvider("gw", { models: [{ id: "mini", baseUrl: "https://gw/v1" }] });
// after
registerProvider("gw", { models: [{ id: "mini", api: "openai-completions", baseUrl: "https://gw/v1" }] });
Defensive patterns

Strategy: validation

Validate before calling

for (const m of cfg.models ?? []) {
  if (!cfg.api && !m.api) throw new Error(`model ${m.id}: set api at provider or model level`);
}

Type guard

function everyModelHasApi(cfg: ProviderConfigInput): boolean {
  return (cfg.models ?? []).every(m => Boolean(cfg.api || (m as { api?: unknown }).api));
}

Try / catch

try {
  registry.registerProvider(name, cfg);
} catch (err) {
  if (err instanceof Error && err.message.includes('no "api" specified')) {
    const id = err.message.match(/model ([^:]+):/)?.[1];
    throw new Error(`Fix config: add "api" to model ${id} or set provider-level api`);
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/1ac8d9196d740b42. Report an issue: GitHub.