Black Forest Labs FLUX Free code examples in Python and JavaScript
FLUX image models — currently the strongest open-weight image family.
WHAT THIS PAGE COVERS
The async submit-then-poll job shape most image APIs use.
flux-2-pro
requests
Model ids change. If a sample returns a 404 on the model, that is the first thing to check against the official docs.
A plain REST API — requests is all it needs. The key header is x-key, not Authorization, which is the first thing to check on a 401.
pip install requests
export BFL_API_KEY="your-key" # macOS / Linux
setx BFL_API_KEY "your-key" # Windows, then open a new shell
THE ENDPOINT IS THE MODEL. There is no model field in the body — /flux-2-pro and /flux-2-flex are different URLs. The response is an id and a polling_url, not an image.
import os
import requests
submitted = requests.post(
"https://api.bfl.ai/v1/flux-2-pro",
headers={
"accept": "application/json",
"x-key": os.environ["BFL_API_KEY"],
"Content-Type": "application/json",
},
json={
"prompt": "A studio photograph of a brass microphone on a dark desk",
"width": 1024,
"height": 1024,
},
timeout=30,
).json()
print(submitted["id"], submitted["polling_url"])
Poll the URL THEY RETURNED. Results are served from region-specific hosts, so a rebuilt URL asks the wrong region and never sees the job finish. And handle all four failure states, or a moderated prompt loops forever.
import time
TERMINAL_FAILURES = {"Error", "Failed", "Request Moderated", "Content Moderated"}
def wait_for(polling_url: str, timeout_seconds: int = 300) -> str:
deadline = time.monotonic() + timeout_seconds
while time.monotonic() < deadline:
time.sleep(0.5)
result = requests.get(
polling_url,
headers={
"accept": "application/json",
"x-key": os.environ["BFL_API_KEY"],
},
timeout=30,
).json()
status = result["status"]
if status == "Ready":
return result["result"]["sample"]
if status in TERMINAL_FAILURES:
raise RuntimeError(f"FLUX returned {status}")
raise TimeoutError("FLUX job did not finish in time")
TEN MINUTES. The signed URL expires, so fetch the bytes in the same breath as the poll. Persisting the URL instead of the image is the mistake that only shows up the next day.
sample_url = wait_for(submitted["polling_url"])
# Do this now, not later. The URL is signed and short-lived.
image = requests.get(sample_url, timeout=60)
image.raise_for_status()
with open("microphone.png", "wb") as handle:
handle.write(image.content)
For anything running in production, ask BFL to call you. Polling a queue of jobs at two requests a second per job is how a worker pool ends up doing nothing else.
submitted = requests.post(
"https://api.bfl.ai/v1/flux-2-pro",
headers={
"accept": "application/json",
"x-key": os.environ["BFL_API_KEY"],
"Content-Type": "application/json",
},
json={
"prompt": "A studio photograph of a brass microphone on a dark desk",
"width": 1024,
"height": 1024,
"webhook_url": "https://example.com/hooks/flux",
},
timeout=30,
).json()
# The callback carries the same status values as the poll, so the
# TERMINAL_FAILURES check above applies unchanged. Download the
# sample inside the handler — the ten-minute clock starts when the
# job finishes, not when you get round to it.
Nothing to install on modern Node. The header is x-key; an Authorization header is ignored and you get a 401 with no hint.
# Node 18+ has fetch built in.
export BFL_API_KEY="your-key"
The endpoint is the model — /flux-2-pro, /flux-2-flex, /flux-kontext-pro are different URLs rather than a field. The response is a job, not an image.
const submit = async (prompt) => {
const response = await fetch("https://api.bfl.ai/v1/flux-2-pro", {
method: "POST",
headers: {
accept: "application/json",
"x-key": process.env.BFL_API_KEY,
"content-type": "application/json",
},
body: JSON.stringify({ prompt, width: 1024, height: 1024 }),
});
if (!response.ok) throw new Error(await response.text());
// { id, polling_url }
return response.json();
};
const job = await submit(
"A studio photograph of a brass microphone on a dark desk",
);
USE job.polling_url. Results live on region-specific hosts, so a reconstructed URL polls somewhere the job will never appear. Four statuses are terminal failures, not one.
const TERMINAL_FAILURES = new Set([
"Error", "Failed", "Request Moderated", "Content Moderated",
]);
async function waitFor(pollingUrl, timeoutMs = 300_000) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, 500));
const result = await fetch(pollingUrl, {
headers: {
accept: "application/json",
"x-key": process.env.BFL_API_KEY,
},
}).then((r) => r.json());
if (result.status === "Ready") return result.result.sample;
if (TERMINAL_FAILURES.has(result.status)) {
throw new Error(`FLUX returned ${result.status}`);
}
}
throw new Error("FLUX job did not finish in time");
}
Ten minutes from ready. Fetch the bytes as soon as you have the URL — storing the link and resolving it later gives you a gallery of expired signatures.
import { writeFile } from "node:fs/promises";
const sampleUrl = await waitFor(job.polling_url);
// Immediately. The signature expires in ten minutes.
const image = await fetch(sampleUrl);
if (!image.ok) throw new Error(`Download failed: ${image.status}`);
await writeFile("microphone.png", Buffer.from(await image.arrayBuffer()));
FLUX.2 takes input_image, input_image_2 and so on. PASS A URL WHERE YOU CAN — the API fetches it itself, and base64 makes the request body enormous for no benefit.
const edit = await fetch("https://api.bfl.ai/v1/flux-kontext-pro", {
method: "POST",
headers: {
accept: "application/json",
"x-key": process.env.BFL_API_KEY,
"content-type": "application/json",
},
body: JSON.stringify({
prompt: "Replace the desk with dark walnut, keep the microphone",
// A URL is fetched server-side; base64 also works and is bigger.
input_image: "https://example.com/assets/microphone.png",
}),
}).then((r) => r.json());
const editedUrl = await waitFor(edit.polling_url);
const edited = await fetch(editedUrl);
await writeFile("edited.png", Buffer.from(await edited.arrayBuffer()));
QUESTIONS
Which languages are the Black Forest Labs FLUX examples in?
Python and JavaScript. Every sample runs as pasted and reads its credentials from an environment variable — no key is ever written into the code.
Which package do I install for Black Forest Labs FLUX?
The samples use requests. The install step on each language tab gives the exact command.
Which Black Forest Labs FLUX model do these examples use?
flux-2-pro, checked against the official documentation on 26 September 2026. Model ids change often; if a call returns a model-not-found error, check the current list in the official docs.
Where is the official Black Forest Labs FLUX documentation?
https://docs.bfl.ai/ — it is linked at the bottom of this page, and it is the authority whenever it disagrees with a sample here.
ALSO IN IMAGE & VIDEO
The authority
Official Black Forest Labs FLUX documentation
Model names, pricing and rate limits change faster than any page here can. When this page and the official docs disagree, the official docs are right.
https://docs.bfl.ai/