Genace

fal API error: Downstream service unavailable (HTTP 500) — a partner API behind the model is down

HTTP 500 downstream_service_unavailable Retry depends on cause

The exact error

Downstream service unavailable

What it means

fal’s error reference defines downstream_service_unavailable as an error that occurs when a required third-party service is unavailable, and it names partner APIs explicitly. The status code is 500, and the reference says retryable “can be true or false”. Its sibling, downstream_service_error, has the message Downstream service error. It also returns 500 with the same retry note, and it means a problem talking to an external service rather than a service that is fully down. In fal’s examples, both use loc: ["body"], which means neither points at a particular input field.

In other words, fal received your request, but a service that fal depends on for that model failed to respond. Nothing in your payload caused it.

A community report. In okou issue #34176, 20 image jobs sent through fal (gpt-image-1 on fal-ai/gpt-image-1/text-to-image) failed within about 90 seconds. Most failed in under a second. Starting 32 seconds later, 23 jobs on the same endpoint all succeeded. The investigator believed the likely cause was downstream_service_error or an upstream 429 or 5xx. The provider’s actual reason was never logged, so that remains an inference. The team also found that fal can put provider-specific text in msg and loc for these errors, so matching on the exact message missed them. They changed their code to classify on type and the reported HTTP status instead.

How to confirm it’s this

  • The detail[].type is downstream_service_unavailable or downstream_service_error. Match on type. fal’s reference says not to rely on msg, and okou’s experience shows why.
  • The HTTP status is 500.
  • According to fal’s reference, the response includes an X-Fal-Needs-Retry header, and fal says to use it for retry decisions.
  • In the JS client, a 500 with a JSON body is thrown as ApiError (not ValidationError), and the parsed body is on error.body. In the client source, ApiError doesn’t expose response headers. To read X-Fal-Needs-Retry you have to call the HTTP endpoint directly.
import { ApiError } from '@fal-ai/client';

const DOWNSTREAM = new Set([
  'downstream_service_unavailable',
  'downstream_service_error',
]);

function isDownstreamFailure(error: unknown): boolean {
  if (!(error instanceof ApiError) || error.status !== 500) return false;
  const detail = (error.body as { detail?: unknown })?.detail;
  return (
    Array.isArray(detail) && detail.some((d) => DOWNSTREAM.has(d?.type))
  );
}

Fix

Your request doesn’t need fixing. What you control is how your code waits and retries.

  • Know which retries fal already does. fal’s reliability page says the queue retries 503s, 504s, connection errors and 429s automatically, up to 10 times. A 500 isn’t on that list. The JS client’s default retry codes are 429, 502, 503 and 504, which also leaves out 500. A downstream 500 therefore usually reaches your code.
  • Model fallbacks. For supported models, fal can reroute a request to an equivalent endpoint when the primary endpoint is temporarily unavailable, after up to five retries. Fallbacks are on by default. If you send x-app-fal-disable-fallback, you have turned this protection off for that request.
  • Cost. fal’s pricing docs say a request that fails with HTTP 500 or higher is never billed. A bounded retry costs time, not money.
  • Persistent failures. If one model keeps returning this error, the partner is probably having an outage. Check fal’s status page, which is linked from the docs header, before you change code.

Retry or not

It depends. fal documents this error as possibly retryable and possibly not, and points to X-Fal-Needs-Retry to decide. If the header is available, follow it. If not, a few spaced-out retries are reasonable, because the cause is on the provider’s side and failed 500s aren’t billed.

If you build agents, set a small retry limit with backoff and a deadline, then report the failure. Don’t resubmit in a tight loop. The okou incident was a burst of rejections that each took under a second, and that team deliberately left automatic resubmission out of their fix because fal documents some provider errors as non-retryable.

When it’s not this

  • internal_server_error (message Internal server error, HTTP 500) is an unexpected fault on fal’s side, not a partner’s.
  • generation_timeout (message Generation timeout, HTTP 504) means the generation took too long.
  • Infrastructure request errors use a different format: detail is a plain string, and the body also has error_type, which is repeated in the X-Fal-Error-Type header. Examples are runner_scheduling_failure (503) and runner_server_error (500).
  • content_policy_violation (HTTP 422) can also come from a partner’s checker, but it is a refusal, not an outage.

Error text last verified against the sources below on .

Where these facts come from