Black Forest Labs' FLUX API

Black Forest Labs 57,408 , founded by researchers behind latent diffusion (Latent Diffusion), sells its FLUX 57,408 models through api.bfl.ai, in credits worth $0.01. The FLUX.2 family is priced by output megapixels: [klein] 4B from $0.014, [klein] 9B from $0.015, [pro] from $0.03 ($0.045 for edits), [flex] from $0.05 and [max] from $0.07. The previous generation sells per image: FLUX1.1 [pro] $0.04, [pro] Ultra $0.06, FLUX.1 Kontext [pro] $0.04 and [max] $0.08.

The API is asynchronous. You POST a prompt to a model endpoint such as /v1/flux-2-max with an x-key header, receive a polling_url, poll it until the status is Ready, and download the result, whose signed URL is "only valid for 10 minutes": store it at once. FLUX.2 [dev] and FLUX.1 [schnell] and [dev] are open weights (FLUX.1).

FLUX's asynchronous API replayed: POST, poll the polling_url until Ready, then download before the signed URL expiresHTMLLive
<!doctype html>
<style>
  body { margin: 0; padding: 8px; background: #fafaf7; font: 12px system-ui, sans-serif; color: #263238; }
  canvas { display: block; max-width: 100%; }
</style>
<p><button id="run">Submit to /v1/flux-2-max</button> <label><input type="checkbox" id="late"> download late (after 10 minutes)</label></p>
<canvas id="c" width="600" height="270"></canvas>
<script>
  const ctx = document.getElementById('c').getContext('2d');
  let log = [], state = 'idle', raf;
  const lanes = ['your server', 'api.bfl.ai', 'delivery URL'];
  function draw(now) {
    ctx.clearRect(0, 0, 600, 270);
    ctx.font = 'bold 12px system-ui';
    lanes.forEach((l, i) => { ctx.fillStyle = '#263238';  ctx.fillText(l, 10 + i * 200, 16);
      ctx.strokeStyle = '#cfd8dc';  ctx.beginPath();  ctx.moveTo(40 + i * 200, 24);  ctx.lineTo(40 + i * 200, 190);  ctx.stroke(); });
    ctx.font = '11px monospace';
    log.forEach((e, i) => {
      const y = 36 + i * 17, [a, b] = e.lanes;
      ctx.strokeStyle = e.color;  ctx.fillStyle = e.color;  ctx.lineWidth = 1.5;
      ctx.beginPath();  ctx.moveTo(40 + a * 200, y);  ctx.lineTo(40 + b * 200, y);  ctx.stroke();
      ctx.beginPath();  const d = b > a ? -6 : 6;  ctx.moveTo(40 + b * 200, y);  ctx.lineTo(40 + b * 200 + d, y - 4);  ctx.lineTo(40 + b * 200 + d, y + 4);  ctx.fill();
      ctx.fillStyle = '#263238';  ctx.fillText(e.text, 46 + Math.min(a, b) * 200, y - 3);
    });
    // Price list: FLUX.2 by output megapixel (from), FLUX.1 per image
    const prices = [['[klein] 4B', 0.014], ['[klein] 9B', 0.015], ['[pro]', 0.03], ['[flex]', 0.05], ['[max]', 0.07], ['1.1 [pro]', 0.04], ['Kontext [max]', 0.08]];
    ctx.font = '11px system-ui';  ctx.fillStyle = '#263238';  ctx.fillText('from $ per image (1 credit = $0.01):', 10, 212);
    prices.forEach(([n, p], i) => {
      ctx.fillStyle = i < 5 ? '#1f5f8b' : '#90a4ae';  ctx.fillRect(10 + i * 84, 250 - p * 400, 60, p * 400);
      ctx.fillStyle = '#263238';  ctx.fillText(`${n} $${p}`, 10 + i * 84, 264);
    });
  }
  const sleep = ms => new Promise(r => setTimeout(r, ms));
  async function run() {
    log = [];  draw();
    const add = async (lanes, text, color = '#1f5f8b', ms = 350) => { log.push({ lanes, text, color });  draw();  await sleep(ms); };
    await add([0, 1], 'POST {prompt} x-key: …');
    await add([1, 0], '{ id, polling_url }', '#5b3f99');
    for (const status of ['Pending', 'Pending', 'Ready']) {
      await add([0, 1], 'GET polling_url', '#78909c', 250);
      await add([1, 0], `status: ${status}${status === 'Ready' ? ', result.sample (signed URL)' : ''}`, status === 'Ready' ? '#3f7d3a' : '#e09a10', 250);
    }
    const late = document.getElementById('late').checked;
    await add([0, 2], late ? 'GET sample after 11 min' : 'GET sample within 10 min');
    await add([2, 0], late ? '403 expired: "only valid for 10 minutes"' : '200 image.jpeg → store it at once', late ? '#b5452f' : '#3f7d3a');
  }
  document.getElementById('run').onclick = run;
  run();
</script>