odysseus-dev/odysseus · error · HTTPException
hidden must be a list of model IDs
Error message
hidden must be a list of model IDs
What it means
400 raised at routes/model_routes.py:2383 in the bulk visibility update handler when the body contains a 'hidden' key but its value is not a JSON list. The handler checks `isinstance(hidden, list)` because it must iterate model ids and later json.dumps them; a string, object, number, or null fails the guard. Element types are not checked here — only the container.
Source
Thrown at routes/model_routes.py:2383
Expects JSON body with optional keys:
{"hidden": ["model-id-1", ...], "pinned_models": ["deploy-id", ...]}
Each key is updated only when present, so callers can patch one list
without clobbering the other.
"""
require_admin(request)
db = SessionLocal()
try:
ep = db.query(ModelEndpoint).filter(ModelEndpoint.id == ep_id).first()
if not ep:
raise HTTPException(404, "Endpoint not found")
body = await request.json()
if not isinstance(body, dict):
raise HTTPException(400, "Body must be a JSON object")
if "hidden" in body:
hidden = body.get("hidden")
if not isinstance(hidden, list):
raise HTTPException(400, "hidden must be a list of model IDs")
base = _normalize_base(ep.base_url)
kind = _effective_endpoint_kind(ep, base)
if _picker_requires_pinning(base, kind):
# Compatibility for older/admin UI paths that still submit
# the previous hide-list shape. API pickers are allow-lists:
# convert "unchecked models" into an explicit pinned list so
# Settings summary, /api/models, and chat agree.
selected = _visible_models(_cached_model_ids(ep), hidden, None)
ep.pinned_models = json.dumps(selected)
ep.hidden_models = None
else:
ep.hidden_models = json.dumps(hidden) if hidden else None
# Accept either "pinned" or "pinned_models" for the manual IDs list.
if "pinned_models" in body or "pinned" in body:
pinned = _normalize_model_ids(body.get("pinned_models", body.get("pinned")))
base = _normalize_base(ep.base_url)
kind = _effective_endpoint_kind(ep, base)
if _picker_requires_pinning(base, kind):View on GitHub (pinned to f9235ebbf1)
Solutions
- Send a JSON array: {"hidden": ["model-id-1", "model-id-2"]}.
- For 'no hidden models', omit the 'hidden' key entirely or send an empty array — do not send null.
- If the source is a comma-separated string, split it: hidden.split(',').map(s => s.trim()).filter(Boolean).
- Remember 'pinned_models' expects a list of deploy ids, same rule.
Example fix
// before
body = { hidden: selectedHidden.join(',') }; // "a,b" -> 400
// after
body = { hidden: selectedHidden }; // ["a","b"] Defensive patterns
Strategy: validation
Validate before calling
const hidden = Array.isArray(rawHidden) ? rawHidden : null;
if (rawHidden !== undefined && hidden === null) throw new TypeError('hidden must be an array of model ids'); Type guard
function isModelIdList(v: unknown): v is string[] {
return Array.isArray(v) && v.every(x => typeof x === 'string');
} Try / catch
try { await api.patchVisibility(epId, {hidden}); }
catch (e) { if (e.status === 400 && /hidden/.test(e.message)) throw new TypeError('wrap in array'); throw e; } Prevention
- Omit the 'hidden' key entirely rather than sending null.
- Split comma-separated strings into arrays before sending.
- Type the payload in the client (TS interface) so shape errors surface at compile time.
When it happens
Trigger: Sending {"hidden": "gpt-4o"} (single id as a bare string), {"hidden": {"gpt-4o": true}} (an object/map), or {"hidden": null}. Sending a comma-separated string "a,b,c" instead of ["a","b","c"].
Common situations: Form serialization that maps unchecked checkboxes to a map or single value. Client building the payload from a text input without splitting into an array. Passing undefined/null because the caller 'had no hidden models' — the correct move is to omit the key entirely (each key is only updated when present).
Related errors
- Body must be a JSON object
- Current password is incorrect
- {key} must be an integer
- Password is required
- Invalid recurring occurrence uid
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/3f41e17728d96b9c.
Report an issue: GitHub.