Media routes
Default model and fallbacks for each media endpoint, so requests can leave out the model.
A media route is the ordered list of models an endpoint uses when a request has no model. Leave model out, or send "model": "auto". DurinDoor tries the route's models in order and moves to the next one on a retryable failure (429, 5xx, missing or exhausted credentials). Any other error comes back unchanged. A 200 with an empty result (a silent audio file, a page with no text) is returned as is, not retried. If the request names a model or a combo, the route is not used.
Routes exist for these endpoints:
| Route | Endpoint | Notes |
|---|---|---|
| Text to speech | POST /v1/audio/speech | |
| Speech to text | POST /v1/audio/transcriptions, POST /v1/audio/translations | Translations only use providers with a translations endpoint (OpenAI, Groq, Local Whisper). |
| Web search | POST /v1/search | Send no model or provider. |
| Web fetch | POST /v1/web/fetch | Send no model or provider. |
| Embeddings | POST /v1/embeddings | Only the first available model runs. Vectors from different models are not comparable, so embeddings never switch models. They still move to the next account of that provider. |
| Image generation | POST /v1/images/generations | |
| Video generation | POST /v1/video/generations, POST /v1/videos/{action} | /v1/video/generations tries the models it can run, in order. /v1/videos creates a billable job, so it uses only the first route model whose provider runs async jobs and never retries. A /v1/videos poll without x-connection-id works only when one async video provider is connected; otherwise it returns 400 asking for the header. |
| Music generation | POST /v1/music/generations, POST /v1/audio/music |
Connect a provider for the endpoint
DurinDoor does not ship a default model. A route can only hold models that GET /v1/models/{kind} lists right now. Each one has to meet all of these:
- It comes from a provider with an active connection, or from an enabled keyless provider that is installed and working:
- A keyless provider that calls a server counts only if the URL this request would use answers within 1.5 seconds. That covers Local Whisper, Coqui, Tortoise, SearXNG, self-hosted Firecrawl and veoaifree-web.
- For an unscoped request, Local Whisper uses its first active connection's host, or its default host when none is saved. Self-hosted Firecrawl uses a valid self-hosted Firecrawl URL setting, then its first active connection's host, then
FIRECRAWL_BASE_URL, then its default. For an API key scoped to provider accounts, the provider counts if the first connection that key may use (by priority) answers. - The check uses the same outbound URL guard as provider requests, so a blocked host counts as not working and is never contacted.
local-devicecounts only when the OS voice list loads (macOSsayor Windows SAPI).- Keyless libraries with no server (edge-tts, google-tts) count as working.
- Checks are cached per URL for 30 seconds.
- The provider serves that kind.
- For image and embedding routes, the provider has an image or embedding adapter in DurinDoor. A catalog that lists such models for a provider without one (Venice, for example) does not put them on the route.
- The model itself is that kind.
- The provider's visible-model allowlist and disabled-model list allow it.
If nothing qualifies, a request without a model gets HTTP 400 with error.code: "no_provider_for_kind". The message depends on why:
- No connected provider serves the kind at all:
{
"error": {
"message": "No connected provider supports text to speech on this endpoint. Connect one in Dashboard > Media Providers, or pass a model.",
"type": "invalid_request_error",
"code": "no_provider_for_kind"
}
}- A saved route exists, but none of its models are available right now (connection removed or disabled, model hidden):
"None of the models in the text to speech route are available. Update it in Dashboard > Media Routes, or pass a model." - A saved route exists and some models are available, but this endpoint can't run any of them (a sync-only video model on
/v1/videos, a Deepgram-only speech-to-text route on/v1/audio/translations):"None of the models in the text to speech route can run on this endpoint. Add one it supports in Dashboard > Media Routes, or pass a model."
Save a default and fallback order
Open Dashboard → Media Routes (/dashboard/media-routes). Each endpoint has a card:
- Automatic is used until you save an order. It tries every available model in catalog order.
- Customize starts an editable list from the current first model. Add fallbacks from the available models, drag to reorder, remove models, then Save. The first model is the default and the rest are fallbacks.
- Use automatic order clears the saved list.
- A saved model that later becomes unavailable (connection removed or disabled, model hidden) is skipped and shown as unavailable, skipped. If every saved model is unavailable, the request fails with
no_provider_for_kind. DurinDoor does not quietly switch to the automatic order. The same applies when the saved models are available but the endpoint can't run any of them, for example aveoaifree-web-only video route on/v1/videos, or a Deepgram-only speech-to-text route on/v1/audio/translations. The video and speech-to-text cards list what each endpoint will run and flag an endpoint that can't run any model in the route.
Routes are stored in settings as mediaRoutes, for example { "tts": ["elevenlabs/eleven_v3", "openai/tts-1"] }. You can also write them through:
PUT /api/media-providers/routeswith{ "kind": "tts", "models": [...] }. An empty list resets the route to automatic.PATCH /api/settingswith{ "mediaRoutes": {...} }.
Both reject unknown kinds, and ids without a provider/ prefix.
Verify a request without a model
curl http://localhost:20128/v1/audio/speech \
-H "Authorization: Bearer $DURINDOOR_KEY" \
-H "Content-Type: application/json" \
-d '{"input":"Speak, friend, and enter."}' --output door.mp3Expect a nonempty door.mp3 when a connected text-to-speech provider succeeds. Inspect the HTTP status before using the file because a provider error can be written to the same output path.
API key model policies still apply to each route model, the same way they do when the model is named explicitly. For an API key scoped to provider accounts, the route leaves out providers the key has no connection for, so embeddings and /v1/videos pick the first model that key can call.
Recover an unavailable route
Check active connections and hidden or disabled models. Update the saved route, or choose Use automatic order. A saved but unavailable list is not replaced automatically. For embeddings, changing the model changes the vector space; rebuild downstream indexes deliberately. For async video, poll the original job instead of creating another billable job.