# Search API fallback for AI agents with Mux

> How to set up a search API fallback for AI agents with Mux's models list, so a provider outage, rate limit or timeout never stops a run mid-task.

Canonical: https://getmux.io/guides/search-fallbacks-for-ai-agents


A search API fallback is an ordered list of search providers that a request walks through until one answers. In Mux you pass that list as `models`, up to five model ids, and the gateway tries each in order, refunds failed attempts and returns the first success along with an `attempts` array that shows what happened. An agent wired to a single provider stops when that provider has a bad minute; an agent with a fallback list keeps going and pays only for the model that answered.

## Why one search provider is a single point of failure

An agent that calls a web search tool in a loop depends on that tool in a way a human user never does. A person who sees an error retries in a few seconds. An agent mid-plan either crashes the run, hallucinates around the gap, or burns retries against a provider that is still down. The failure modes are ordinary:

- **Provider 5xx.** Every search API has outages and partial outages. During one, every request from your agent fails until the provider recovers.
- **Rate limits.** A burst of parallel tool calls, or a few agents sharing one provider key, can hit the provider's per-second or per-minute cap. Retrying harder makes it worse.
- **Timeouts.** A slow upstream turns a 1 second tool call into a 15 second one, and an agent framework with its own timeout gives up first.
- **Regional problems.** A provider can be fine from one region and unreachable from another.

The usual fix is to integrate two or three search APIs, write a retry wrapper, normalize three response shapes, and hold three billing relationships. Mux does that once, behind one endpoint, with one key and one bill. The response shape is identical no matter which provider answered, so your agent's parsing code does not change when the fallback kicks in.

## How the `models` list works

`POST /v1/search` accepts either `model` (one id) or `models` (an ordered list of up to five). When `models` is set it takes precedence. The rules:

| Behavior | What Mux does |
|---|---|
| Order | Tries each model in the order given, stops at the first success. |
| Failed attempts | The price was reserved before the call and is refunded when the call fails. You pay for the model that answered. |
| Unaffordable models | A model whose price would exceed your balance or the key's credit limit is skipped, not called and not charged. A cheaper model later in the list can still run. |
| Timeouts | A provider that has not answered after 15 seconds is abandoned and the next model is tried. Because the provider has almost always finished the search by then, that attempt keeps the model's base price, shown as `cost_usd` on its entry in `attempts`. |
| All fail | You get `502 all_providers_failed` with the full `attempts` array. Nothing is charged, except any timed-out attempt. |
| Observability | Every response includes `attempts`, one entry per model tried, with `status` of `ok`, `error` or `skipped`. |

If you omit both fields, Mux uses `auto`, which is a built-in fallback list: `parallel/fast`, then `exa/auto`, then `keenable/pro`. That order favors the cheapest model first and still covers all three providers.

The Mux SDKs retry `429` and `5xx` responses twice with backoff, but not `502 all_providers_failed`, because the gateway has already tried everything you allowed. That error is safe to retry yourself since you were not charged.

## Recommended orders for three goals

Prices below are list prices per 1,000 requests with 10 results each. See the live table on [/pricing](https://getmux.io/pricing) and the per-model pages such as [/models/parallel/fast](https://getmux.io/models/parallel/fast).

(Live price table: https://getmux.io/models or GET https://api.getmux.io/v1/models)

### Cheapest first

Start with `parallel/fast` at $1 per 1,000, fall back to `keenable/pro` at $4, and keep `exa/auto` at $7 as the last resort. Most requests cost $0.001, and the expensive model only runs when both cheaper ones are down.

```bash
curl https://api.getmux.io/v1/search \
  -H "Authorization: Bearer $MUX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "postgres connection pooling", "models": ["parallel/fast", "keenable/pro", "exa/auto"]}'
```
```ts
import { Mux } from "getmux";

const mux = new Mux();
const res = await mux.search({
  query: "postgres connection pooling",
  models: ["parallel/fast", "keenable/pro", "exa/auto"],
});
console.log(res.model, res.usage.costUsd);
```
```python
from getmux import Mux

mux = Mux()
res = mux.search(
    "postgres connection pooling",
    models=["parallel/fast", "keenable/pro", "exa/auto"],
)
print(res.model, res.cost_usd)
```

### Quality first

Lead with `exa/auto`, which is Exa's general-purpose mode, then `parallel/advanced` for its high-quality excerpts, then `keenable/pro`. Use this for research agents where a weaker result set costs more in model tokens than the search does.

```ts
const res = await mux.search({
  query: "peer-reviewed studies on intermittent fasting and insulin sensitivity",
  models: ["exa/auto", "parallel/advanced", "keenable/pro"],
  maxResults: 20,
  content: "full",
});
```
```python
res = mux.search(
    "peer-reviewed studies on intermittent fasting and insulin sensitivity",
    models=["exa/auto", "parallel/advanced", "keenable/pro"],
    max_results=20,
    content="full",
)
```

Note that `content: "full"` returns page text on Exa and joined excerpts on Parallel, but `content` is `null` on Keenable. If your agent needs page text, put Keenable last or leave it out.

### Latency first

For interactive agents, start with `parallel/turbo` at about 200 ms typical, then `exa/instant` at about 300 ms, then `keenable/pro` at about 700 ms.

```ts
const res = await mux.search({
  query: "weather api with hourly forecast",
  models: ["parallel/turbo", "exa/instant", "keenable/pro"],
  maxResults: 5,
});
```
```python
res = mux.search(
    "weather api with hourly forecast",
    models=["parallel/turbo", "exa/instant", "keenable/pro"],
    max_results=5,
)
```

A good rule for any order: put at least two different providers in the list. Two Exa variants protect you against a bad model, not against an Exa outage.

## Reading `attempts` in your logs

Log `res.model` and `res.attempts` on every search. A normal response has a single `ok` entry. A response that fell back looks like this:

```json
{
  "model": "keenable/pro",
  "usage": { "cost_usd": 0.004, "results": 10, "latency_ms": 712, "byok": false },
  "attempts": [
    { "model": "parallel/fast", "source": "platform", "status": "error", "error": "parallel 503: service unavailable" },
    { "model": "keenable/pro", "source": "platform", "status": "ok" }
  ]
}
```

Things to watch for:

- **`status: "error"`** means the provider was called and failed. It was refunded unless `cost_usd` is present, which marks a timeout that kept the base price.
- **`status: "skipped"`** means the model was never called. The `error` field says why, usually insufficient credits or a key limit. If you see this often, raise the key's limit or move a cheaper model earlier in the list.
- **The last entry** is always the model that served the request, and matches the top-level `model` field and the `x-mux-model` response header.
- **A rising fallback rate** for one provider is an early warning that it is degraded. Counting `attempts` entries with `status: "error"` by model over a window gives you a free provider health dashboard.

The same information appears per request on the activity page of the [dashboard](https://getmux.io/dashboard) and from `GET /v1/activity`, where each failed upstream attempt is logged as its own row with its error.

```ts
for (const a of res.attempts) {
  if (a.status !== "ok") console.warn("search fallback", a.model, a.status, a.error);
}
```
```python
for a in res.attempts:
    if a["status"] != "ok":
        print("search fallback", a["model"], a["status"], a.get("error"))
```

## FAQ

### Does a fallback cost more than a single-provider request?

No. Each attempt's price is reserved before the call and refunded if it fails, so you pay for the model that succeeded. The one exception is a timed-out attempt, which keeps that model's base price because the provider has normally completed the search by then.

### What happens if every model in the list fails?

You get `502 all_providers_failed` with the `attempts` array in the error body, and your balance is unchanged apart from any timed-out attempt. The SDKs do not retry this status automatically, but it is safe for you to retry.

### Can I use `models` with my own provider keys?

Yes. With [BYOK](https://getmux.io/docs/byok), a request to a provider you have connected runs on your key, and `attempts` shows `source: "byok"` for it. If your key fails, Mux falls back to its own account for that provider at list price unless you set `always_use: true`.

### How many models can I list?

Up to five. Past three you get diminishing returns, since Mux currently routes to three providers. A list that covers all three providers is the strongest guard against an outage.

Set `MUX_API_KEY` and send your first fallback request in a minute with the [quickstart](https://getmux.io/docs/quickstart).
