Empty model picker on a custom endpoint: read /v1/models first

A working key and a working URL do not guarantee a populated dropdown. The picker is fed by a separate route, and some clients do not fetch it at all — so the empty box tells you nothing about whether your configuration is correct. The endpoint's own model list is the only authority, and it is one curl away.

In short: GET /v1/models is the only authoritative source of the id to send. Some clients fetch it and some expect manual entry, and a client that guesses the context window from an unrecognised id trims the prompt silently.
You need a key before the code below runs. Create an account, generate a key, and copy the base URL (https://aicomp.ai/v1). Create one free →
Check current rates → Free to sign up · $1 minimum top-up · No prepayment
Confirm the key works first. One command, no SDK, costs nothing:
curl https://aicomp.ai/v1/models \
  -H "Authorization: Bearer sk-your-gateway-key"

A JSON list of model IDs means the key is good. Invalid token means it was copied wrong.

The route that feeds the dropdown

The list endpoint is a normal GET on your base URL, and the response shape is fixed: an object whose object is list, containing a data array of entries each carrying an id.

GET /v1/models
Authorization: Bearer sk-...

200 OK
{
  "object": "list",
  "data": [
    {"id": "gpt-5.6-luna", "object": "model", "created": 1750000000, "owned_by": "openai"},
    {"id": "claude-sonnet-5", "object": "model", "created": 1750000001, "owned_by": "anthropic"}
  ]
}

That id is the only string the completion route will accept as model. Not the display name, not the vendor's marketing name, not the slug in a documentation URL.

# The endpoint's own list is the only authoritative source of model ids.
curl -s https://aicomp.ai/v1/models -H "Authorization: Bearer $GATEWAY_API_KEY" > /tmp/models.json

head -c 200 /tmp/models.json
#   -> starts with {"object":"list" ...   good
#   -> starts with <!DOCTYPE or <html     something else answered; fix the URL first

python3 - <<'PY'
import json
d = json.load(open("/tmp/models.json"))
ids = [m.get("id") for m in d.get("data", [])]
print(len(ids), "models returned")
print("\n".join(str(i) for i in ids[:10]))
PY

Run it once and keep the output. Everything else on this page is downstream of what it prints.

Four ways an empty picker happens

They look identical in the UI, and only one of them is actually about your configuration:

Why copying the id from the wrong place 404s

Ids get rewritten for the address bar. In our catalogue, 15 of the tracked model ids differ from the slug used in their URL — the dot in gpt-5.6-luna becomes a hyphen in gpt-5-6-luna, for instance. Send the slug and the server sees a model it does not serve.

Ids whose URL slug is not the string the API expects
Send this (the id)Not this (the URL slug)
claude-haiku-4-5-20251001claude-haiku-4-5
gpt-5.6-solgpt-5-6-sol
gpt-5.6-terragpt-5-6-terra
gpt-5.6-lunagpt-5-6-luna
gemini-3.7-flashgemini-3-7-flash
deepseek-v4-pro-0813deepseek-v4-pro
deepseek-v4-flash-0731deepseek-v4-flash
glm-5.3glm-5-3

Showing 8 of 15. The full catalogue exposes 839 ids — too many for any picker, which is the next point.

When the list is too big to be useful

A gateway catalogue runs to 839 entries. A picker that renders all of them is technically working and practically unusable, and it makes the "empty dropdown" failure and the "unusable dropdown" failure look like the same bug. If your client supports pinning a shortlist or typing an id, use it.

Three ids worth pinning to the top of a shortlist — gateway rate vs official list
ModelGateway rate
in / out per 1M tokens
Official list
in / out per 1M tokens
Diff
gpt-5.6-luna$0.1 / $0.6$0.2 / $1.250%
deepseek-v4-flash$0.22 / $0.66— / ——
gemini-3.7-flash$0.375 / $1.875— / ——

Rates checked 2026-09-26. Gateway rates move with upstream promotions — verify the current number in your dashboard before committing to a budget.

Rates checked 2026-09-26. Gateway rates move with upstream promotions — verify the current number in your dashboard before committing to a budget.

The failure that does not announce itself

Here is the expensive one. Clients infer a model's context window from its name. Serve a model under an id the client does not recognise and it may assume a small window and trim your prompt to fit — silently, with no error and a perfectly valid response. You get shorter answers and occasional nonsense, and nothing points at the model id.

Where the client exposes a model configuration section — context window, maximum output tokens, image support, tool support — fill it in explicitly for any custom id. That turns an inference into a stated fact.

Cost note. A truncated prompt is worse than a rejected one. A rejected request tells you the window was exceeded; a silently trimmed one just costs you the same money for degraded output, and the only symptom is quality.

Related guides

Empty-picker failures and what each one is actually telling you

SymptomWhat is actually happeningHow to confirmFix
Dropdown empty, hand-typed id worksThe client did not fetch or could not use /v1/models; the endpoint and key are both fine.Type an id copied from the route's response.Use manual entry, and keep the id in configuration rather than inline.
Dropdown empty and everything 404sThe URL or the credential is wrong, so no route is reachable at all.Run the curl above and check the body parses as JSON.Fix the base URL suffix, then re-read the list.
The list returns HTMLSomething between you and the endpoint answered — a proxy, a captive portal, a wrong path.Inspect the first 200 bytes of the response.Correct the URL or the network path; no client setting will fix this.
The list is empty but the key authenticatesThe credential is valid but scoped to zero models.Compare the same request with a key you know has access.Fix the key's scope rather than the client configuration.
Long files get summarised instead of readThe client inferred a small context window from an unrecognised model id and trimmed the prompt.Check whether the client exposes a context-window override.Enter the window and max output explicitly for every custom id.
A model id copied from a URL 404sSlugs are rewritten for the address bar; the API compares against the raw id.Compare against the /v1/models response.Copy ids from the route or from a rate table that quotes them.

When this is the wrong move

Four assumptions behind most empty-picker tickets

SituationWhy it breaksDo this instead
You are about to assume your client fetches the listWhether a client populates its picker from /v1/models varies by client and by version.Test once with a hand-typed id; prefer manual entry in production.
You are about to trust a 200 without reading the bodyProxies and error pages return 200 with HTML.Confirm the body parses as JSON before drawing any conclusion.
You are about to leave the context window to inferenceAn unrecognised id gets a conservative default and your prompt is trimmed silently.State the window and max output explicitly for custom ids.
You are about to hardcode an id from a documentation URLSlugs differ from ids, and the difference is invisible until a 404.Copy from /v1/models only.

Rolling it out without finding out the hard way

The failure mode you want to avoid is discovering a problem through a production bill or a customer-visible error. Four steps, in order:

  1. Prove it on one request with the id copied from /v1/models, with the window set explicitly. One call, from a script, with an explicit timeout and the model ID echoed back. You are testing reachability, authentication and model availability — three things that can fail independently.
  2. Measure before you switch. Record tokens per task and cost per task on the current path first. Without that baseline, "it got cheaper" is an impression, not a result.
  3. Move one workload, not everything. Pick the workload with the most predictable shape — batch jobs over interactive traffic. Leave the interactive path on the old configuration until the batch numbers are in.
  4. Decide the rollback condition in advance. Write down what makes you revert (error rate above X, cost per task above Y, latency above Z) before you start, so the decision is not made under pressure.

Keep the base URL in configuration, never inline. That single choice is what makes step four take a minute instead of an afternoon.

FAQ

Why is the dropdown empty when my key and URL both work?

Because the picker and the credential are independent. Several clients populate their model dropdown from GET /v1/models; if that route is not implemented, returns an empty data array, or is answered by something that is not the API, the picker stays empty while a hand-typed id would work perfectly. Which clients fetch the list and which expect manual entry varies by client and by version, so verify yours rather than assuming either.

What exactly should <code>/v1/models</code> return?

A JSON object with object set to list and a data array whose entries each carry an id plus object, created and owned_by. The id is the string you send as model in a completion request. If the body you get back does not start with that shape, you are not looking at the endpoint's model list — check the URL and what answered.

Is the model id the same as the one in the page URL?

Not necessarily, and this is a real 404 source. Ids get rewritten for the address bar: in our own catalogue, 15 of the tracked ids differ from their URL slug — for example claude-haiku-4-5-20251001 is written claude-haiku-4-5 in a URL. Copy ids out of the /v1/models response or out of a rate table that quotes the raw id, never out of a browser address bar.

My model works but the client truncates long files. Why?

Because clients guess the context window from the model name, and a name they do not recognise gets a conservative default. Serve a model under a custom id and the client may assume a small window and trim your prompt silently — no error, just missing context. Where the client exposes a model configuration section, enter the window and the maximum output tokens explicitly instead of leaving them to be inferred.

The list came back enormous and the picker is unusable.

That is a different problem with the same symptom. A gateway catalogue can run to hundreds of entries — ours exposes 839 — and a picker rendering all of them is practically unusable. Prefer clients that let you type an id, or that let you pin a shortlist. A huge list is evidence the endpoint is working, not that it is broken.

Should I ever hardcode the model id?

Yes, for a client that does not fetch the list, and it is the more predictable choice in production either way. Put it in configuration rather than inline so that changing models is a config change. The failure to avoid is hardcoding a string you copied from somewhere other than the endpoint's own list.

Get API access