{"record":{"id":"c6d0e8c9969b9a69","repo":"invoke-ai/InvokeAI","slug":"no-external-provider-config-fields-provided","errorCode":null,"errorMessage":"No external provider config fields provided","messagePattern":"No external provider config fields provided","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"invokeai/app/api/routers/app_info.py","lineNumber":331,"sourceCode":"    response_model=ExternalProviderConfigModel,\n)\ndef set_external_provider_config(\n    _: AdminUserOrDefault,\n    provider_id: str = Path(description=\"The external provider identifier\"),\n    update: ExternalProviderConfigUpdate = Body(description=\"External provider configuration settings\"),\n) -> ExternalProviderConfigModel:\n    api_key_field, base_url_field = _get_external_provider_fields(provider_id)\n    updates: dict[str, str | None] = {}\n\n    if update.api_key is not None:\n        api_key = update.api_key.strip()\n        updates[api_key_field] = api_key or None\n    if update.base_url is not None:\n        base_url = update.base_url.strip()\n        updates[base_url_field] = base_url or None\n\n    if not updates:\n        raise HTTPException(status_code=400, detail=\"No external provider config fields provided\")\n\n    api_key_removed = update.api_key is not None and updates.get(api_key_field) is None\n    _apply_external_provider_update(updates)\n    if api_key_removed:\n        _remove_external_models_for_provider(provider_id)\n    return _build_external_provider_config(provider_id, get_config())\n\n\n@app_router.delete(\n    \"/external_providers/config/{provider_id}\",\n    operation_id=\"reset_external_provider_config\",\n    status_code=200,\n    response_model=ExternalProviderConfigModel,\n)\ndef reset_external_provider_config(\n    _: AdminUserOrDefault,\n    provider_id: str = Path(description=\"The external provider identifier\"),\n) -> ExternalProviderConfigModel:","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/app/api/routers/app_info.py#L313-L349","documentation":"set_external_provider_config returns 400 when the request body contains no fields that actually map to external-provider config keys (e.g. api_key / base_url for the provider). After building the updates dict from update.api_key/update.base_url, an empty dict means nothing was provided, so the server refuses the no-op call.","triggerScenarios":"POSTing to the external-provider config endpoint with a body where every optional field is None/absent (e.g. {} or {\"api_key\": null, \"base_url\": null}), so `updates` stays empty.","commonSituations":"Frontend sending an untouched form; client serializing all-None optional fields; a provider whose only fields are api_key/base_url both left blank; generic code paths reusing a payload template across providers.","solutions":["Include at least one real field in the body: api_key and/or base_url","To CLEAR a field send an empty string (\"\") rather than null (empty string maps to None and counts as an update)","Fix the client to omit the call entirely when nothing changed","Check which fields the target provider supports via its config schema and send one of those"],"exampleFix":"// before\nawait fetch(url, { method: 'POST', body: JSON.stringify({ api_key: null, base_url: null }) });\n// after\nawait fetch(url, { method: 'POST', body: JSON.stringify({ api_key: 'sk-...' }) });","handlingStrategy":"validation","validationCode":"def validate_provider_update(update: dict) -> bool:\n    fields = {'api_key', 'base_url'}\n    return any(update.get(f) is not None for f in fields)  # at least one real value\n\nif not validate_provider_update(payload):\n    raise ValueError('Supply api_key and/or base_url (use \"\" to clear)')","typeGuard":"def has_provider_field(update: dict) -> bool:\n    return isinstance(update, dict) and any(\n        v is not None for k, v in update.items() if k in ('api_key', 'base_url')\n    )","tryCatchPattern":"try:\n    resp = requests.post(url, json=payload)\n    resp.raise_for_status()\nexcept requests.HTTPError as e:\n    if e.response.status_code == 400 and 'No external provider' in e.response.json().get('detail', ''):\n        skip_call = True  # nothing to update","preventionTips":["Only call the endpoint when at least one field actually changed","Send \"\" (empty string) to clear a field, not null","Diff the payload against current config before posting","Omit all-null optional fields when building the request body"],"tags":["http-400","api-request","validation"],"backgroundTag":"empty-request-payload","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}