Error Handling
send-message validates every candidate model
before it starts a generation run. If at least one candidate survives, you get 200
and generation proceeds with the survivors. If none do, you get 400 in the HTTP response
itself — no run is started, and nothing is written to the real-time stream.
POST /api/v1/llm/gateway/send-message (model: "invalid/model-xyz")
↓
Validate candidates → none survive
↓
400 with an RpcError body
↓
No generation run. No stream update.
This means a 400 here is final. There is no later event to wait for, so a client should
surface the error straight away rather than attaching a stream listener.
Handling the response
| Response | What it means | What to do |
|---|---|---|
200 with a runId | At least one candidate survived | Listen to the stream, or poll conversation-state, as usual |
400 MODEL_FILTERED_OUT | No candidate survived | Show the error. Do not listen to the stream — nothing will arrive. |
Candidates that fail while others survive are dropped silently: you get 200, and
generation runs with what is left. Only the all-or-nothing case surfaces an error.
Error payload
When pre-validation leaves zero candidates, the response body is:
{
"code": "ERROR_CODE_INVALID_ARGUMENT",
"message": "all candidate models were filtered out: [invalid/model-xyz deprecated/old-model]",
"is_terminal": true,
"details": {
"error_info": {
"reason": "MODEL_FILTERED_OUT",
"domain": "conversation"
}
}
}
The payload is identical whether the candidates were rejected as unknown model IDs or
eliminated by model_routing_filter — it does not distinguish the two. Either way it is a
terminal configuration error: correct the model IDs, or relax the filter.
send-message-sync returns the same shape under
the same conditions.
The error above comes from the conversation actor, synchronously, before generation begins.
An error raised later by the generation workflow — for example when the OpenRouter service
re-validates candidates during a retry — is streamed instead, and uses
code: ERROR_CODE_MODEL_INVALID, reason: "ALL_MODELS_FILTERED", domain: "openrouter",
with per-model reasons in message.