decolua/9router · warning
[${providerId}] ${errorMsg}
Error message
[${providerId}] ${errorMsg} What it means
This is a 503-style 'provider temporarily unavailable' response raised in handleSingleProviderFetch (src/sse/handlers/fetch.js:168). It fires when getProviderCredentials reports credentials.allRateLimited for the requested provider, meaning every connection for that provider is inside a rate-limit/unavailable lock window. The message embeds the last upstream error (or 'Unavailable') plus a Retry-After hint (credentials.retryAfterHuman) so the caller knows when to come back. The gateway deliberately refuses to keep hammering a provider whose accounts have all been throttled by the upstream API.
Source
Thrown at src/sse/handlers/fetch.js:168
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }
});
}
return errorResponse(result.status || HTTP_STATUS.BAD_GATEWAY, result.error || "Fetch failed");
}
// Credential + fallback loop
const excludeConnectionIds = new Set();
let lastError = null;
let lastStatus = null;
while (true) {
const credentials = await getProviderCredentials(providerId, excludeConnectionIds);
if (!credentials || credentials.allRateLimited) {
if (credentials?.allRateLimited) {
const errorMsg = lastError || credentials.lastError || "Unavailable";
const status = lastStatus || Number(credentials.lastErrorCode) || HTTP_STATUS.SERVICE_UNAVAILABLE;
log.warn("FETCH", `[${providerId}] ${errorMsg} (${credentials.retryAfterHuman})`);
return unavailableResponse(status, `[${providerId}] ${errorMsg}`, credentials.retryAfter, credentials.retryAfterHuman);
}
if (excludeConnectionIds.size === 0) {
log.error("AUTH", `No credentials for provider: ${providerId}`);
return errorResponse(HTTP_STATUS.BAD_REQUEST, `No credentials for provider: ${providerId}`);
}
log.warn("FETCH", "No more accounts available", { provider: providerId });
return errorResponse(lastStatus || HTTP_STATUS.SERVICE_UNAVAILABLE, lastError || "All accounts unavailable");
}
log.info("AUTH", `\x1b[32mUsing ${providerId} account: ${credentials.connectionName}\x1b[0m`);
const refreshedCredentials = await checkAndRefreshToken(providerId, credentials);
const result = await handleFetchCore({
url: targetUrl,
format,
maxCharacters,View on GitHub (pinned to 90b52e06ff)
Solutions
- Wait until the Retry-After timestamp in the response (credentials.retryAfterHuman) elapses, then retry the request.
- Check the dashboard's provider/connections page for the rate-limited account and clear the error state manually.
- Add more connections (additional API keys/accounts) for that provider so multi-account fallback has another credential to use.
- Route the fetch through a combo (multiple providers) so handleComboChat can fall back to a different provider instead of hitting the locked one.
- Reduce request concurrency against the provider or add client-side backoff to avoid re-tripping the 429 lock.
Example fix
// before: tight loop that exhausts the provider and trips the lock
for (const url of urls) {
await fetch(`${base}/v1/fetch`, { method: 'POST', body: JSON.stringify({ provider: 'exa', url }) });
}
// after: respect Retry-After and back off
let res;
for (const url of urls) {
res = await fetch(`${base}/v1/fetch`, { method: 'POST', body: JSON.stringify({ provider: 'exa', url }) });
if (res.status === 503) {
const retryAfter = Number(res.headers.get('Retry-After')) || 60;
await new Promise(r => setTimeout(r, retryAfter * 1000));
}
} Defensive patterns
Strategy: retry
Validate before calling
// Cannot be fully pre-checked (server-side lock state), but check the response up front:
const res = await fetch(base + '/v1/fetch', { ...opts });
if (res.status === 503) {
const ra = Number(res.headers.get('Retry-After'));
if (ra) await new Promise(r => setTimeout(r, ra * 1000));
} Try / catch
for (let attempt = 0; attempt < 3; attempt++) {
const res = await doFetch();
if (res.status !== 503) return res;
const retryAfter = Number(res.headers.get('Retry-After')) || 30 * (attempt + 1);
await new Promise(r => setTimeout(r, retryAfter * 1000));
}
throw new Error('Provider rate-limited after retries'); Prevention
- Add client-side throttling/backoff so you never exceed the provider's quota in the first place.
- Configure multiple connections per provider so the gateway can rotate accounts.
- Use combos that span distinct providers for automatic cross-provider fallback.
- Monitor the dashboard for accounts entering rate-limited state and rotate keys proactively.
When it happens
Trigger: POST /v1/fetch (web fetch) where the resolved provider has at least one stored connection, but all of its connections are currently marked rate-limited/unavailable: a previous fetch/chat call got a 429 (or another lock-triggering status) from the upstream, markAccountUnavailable set the account-wide lock, and now getProviderCredentials returns { allRateLimited: true, lastError, lastErrorCode, retryAfter }.
Common situations: Bursting a web-fetch pipeline past the upstream provider's quota (e.g. many URL extractions in a loop through one provider); sharing a single free-tier API key across multiple tools; a provider outage that returns 429/5xx for every account, tripping the lock for the whole retry window; stale lastErrorCode from an earlier failure keeping the 503 status sticky.
Related errors
- All accounts unavailable
- [${providerId}] ${errorMsg}
- ${msg} (lastError or "All combo models unavailable")
- MiMo bootstrap failed: ${response.status}
- NanoBanana status ${r.status}
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/de57b7aab73acfb5.
Report an issue: GitHub.