Skip to main content
MarketplaceProvider Keys — Fal

Provider Keys — Fal

Fal is the one provider on the key market that does not take its target in the URL. That single difference accounts for most first-call failures, so it is the first thing on this page.

Billing unit: per megapixel. Fal keeps no record of a call against the seller's account, so nothing you generate here persists on someone else's history.

The target is a header, and the path is empty

Why Fal is shaped differently
What worksPOST /v1/resources/fal + x-fal-target-url: https://fal.run/fal-ai/flux/schnell
What is refusedPOST /v1/resources/fal/fal-ai/flux/schnell → 400 unexpected_path
Fal's own SDK builds the full model URL and expects to send it through a proxy as a header. A path on our route would be a second, conflicting target — so it is refused outright rather than guessed at.

The target must be an absolute https URL on one of Fal's two model hosts. Anything else — Fal's storage, pricing or account hosts, or any other domain — never leaves our fence.

fal.runWhat it is · Answers with the result itself. Synchronous.
queue.fal.runWhat it is · Starts a job and answers with an id.

Using the SDK

@fal-ai/client is the normal way in. Point proxyUrl at our route and send the target always, not just when the SDK feels like it:

Fal SDK through Surplus
import { fal } from '@fal-ai/client';

fal.config({
  // No Fal key here — the seller's key is resolved on our side, at call time.
  proxyUrl: {
    url: '$SURPLUS_BASE_URL/resources/fal',
    when: 'always',
  },
  requestMiddleware: async (request) => ({
    ...request,
    headers: { ...request.headers, Authorization: `Bearer ${process.env.SURPLUS_API_KEY}` },
  }),
});

const result = await fal.subscribe('fal-ai/flux/schnell', {
  input: { prompt: 'a tiny red origami crane', image_size: 'square', num_images: 1 },
});
console.log(result.data.images[0].url);

when: 'always' matters. With the default the SDK proxies only some calls and sends the rest straight to Fal, where your request has no credential and fails — with nothing on our side to explain why.

Fal is the one provider whose follow-up URLs we leave untouched: its SDK builds them itself and sends each target in the header, so it stays on your key without our help. Path-style providers get their follow-up URLs rewritten instead — see jobs and settlement.

What is reachable

Only Fal's own public models, on three methods:

POST /fal-ai/+Covers · Submit or run a model.
GET /fal-ai/+Covers · Status and result.
PUT /fal-ai/+Covers · Cancel.

fal-ai is the only publisher. That is not a preference — a seller's private apps live under <their-username>/<app> and answer on these same two hosts to the seller's key, so no denylist could cover them, while nobody else can deploy under fal-ai. An allowlist of one publisher is the only shape that is safe by construction. Other public publishers are added as data once verified.

?logs=1 is the one query parameter passed through, because the SDK asks for run logs on status polls. fal_webhook is deliberately not: it would send your result to a URL we do not control.

Two response headers come back untouched, because the SDK reads both: x-fal-request-id and x-fal-request-timeout-type.

Two things Fal refuses here

LLMs

fal-ai/any-llm is Fal's unified LLM endpoint — Claude, GPT, Gemini, Llama — and it is denied provider-wide, not per listing.

An LLM bills on output tokens, which no quote taken before the call can bound. That makes it a platform pricing gap rather than a seller's choice, which is exactly why it is not left to the access editor. The rule is unqualified across every method, not POST-only: a family refused on one verb and open on the others reads as an oversight the next time someone edits it.

LLM resale is the order book's job, not this market's. Use /v1/chat/completions.

Models that can run for minutes

Fal prices most image models per megapixel or per image, and those answer inside the request. Everything else — video, 3D, long audio — can run for minutes.

Why a long model must be queued
  1. Priced per megapixel or imageRuns on fal.run, answers inline
  2. Priced any other wayRefused on fal.run — 400 job_route_required
  3. Submit to queue.fal.runSurvives a replaced task
A synchronous call holds a task until the provider answers, and a task being replaced by a deploy drains for 60 seconds. A minutes-long model cut there is billed to the seller and never reaches you — so it is refused up front instead.

The refusal names itself: job_route_required. Submit the same model to queue.fal.run instead and follow it as a job. A queue submit answers immediately either way, so this costs you nothing but a different target host.

On the x402 rail there is no queue option — the message says so, and you need a Surplus API key for those models.

Timeouts

POST /fal-ai/+ is capped at 50 seconds, inside the fleet's 60-second drain, so a deploy never cuts a call a seller is already billed for. A model that needs longer belongs on the queue.

A worked queue job

Submit, poll, collect
curl -X POST $SURPLUS_BASE_URL/resources/fal \
  -H "Authorization: Bearer $SURPLUS_API_KEY" \
  -H "x-fal-target-url: https://queue.fal.run/fal-ai/flux/dev" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"a paper boat","image_size":"square"}'
# → { "request_id": "764cabcf-…", "status_url": "…", "response_url": "…" }

COMPLETED does not mean it worked. A failed Fal job also reads COMPLETED, with no error on the status, so the outcome is only confirmed on the result: 2xx succeeded, 4xx failed. We settle on the same evidence you do.