> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bfl.ml/llms.txt
> Use this file to discover all available pages before exploring further.

# Quick Start

> Get started with FLUX in minutes — use the API to build and integrate directly, or connect via MCP for instant image generation inside your AI tools.

export const mcpSetupTabs = prompt => [{
  id: 'claude',
  label: 'Claude',
  logo: 'claude',
  steps: [{
    number: 1,
    title: 'Open your connectors',
    description: 'This opens <strong>claude.ai</strong> on <strong>Customize → Connectors</strong>. In Claude Desktop, the same list is under Settings → Connectors.',
    button: {
      label: 'Open connectors',
      href: 'https://claude.ai/customize/connectors',
      logo: 'claude'
    }
  }, {
    number: 2,
    title: 'Add a custom connector',
    description: 'Choose to add a custom connector, then paste in these two values.',
    codes: [{
      label: 'Name',
      value: 'FLUX'
    }, {
      label: 'URL',
      value: 'https://mcp.bfl.ai'
    }]
  }, {
    number: 3,
    title: 'Sign in',
    description: 'Click <strong>Connect</strong>, sign in with your BFL account, and choose the organization you want billed.'
  }]
}, {
  id: 'claude-code',
  label: 'Claude Code',
  logo: 'claude',
  steps: [{
    number: 1,
    title: 'Let Claude Code set itself up',
    description: 'Opens a Claude Code session with the setup prompt filled in. Press Enter and it registers the server and verifies the connection. Works through <strong>Claude Desktop</strong>, or through the CLI once <code>claude-cli://</code> is registered.',
    button: {
      label: 'Open in Claude Code',
      href: `claude://code/new?q=${encodeURIComponent(prompt)}`,
      logo: 'claude'
    }
  }, {
    number: 2,
    title: 'Or add it yourself',
    description: 'One command in your terminal registers FLUX as a remote MCP server.',
    code: 'claude mcp add --transport http FLUX https://mcp.bfl.ai',
    wide: true
  }, {
    number: 3,
    title: 'Sign in',
    description: 'A browser opens on first use. Sign in, choose the organization you want billed, then return to Claude Code.'
  }]
}, {
  id: 'cursor',
  label: 'Cursor',
  logo: 'cursor',
  steps: [{
    number: 1,
    title: 'Install with one click',
    description: 'Click the button to open Cursor with the FLUX server pre-filled. Cursor will ask you to approve the install.',
    button: {
      label: 'Add to Cursor',
      href: 'cursor://anysphere.cursor-deeplink/mcp/install?name=FLUX&config=eyJ1cmwiOiJodHRwczovL21jcC5iZmwuYWkifQ==',
      logo: 'cursor'
    }
  }, {
    number: 2,
    title: 'Approve the install',
    description: 'In Cursor, confirm <strong>Add server</strong> in the approval dialog. The entry is saved to your global <code>~/.cursor/mcp.json</code>.'
  }, {
    number: 3,
    title: 'Sign in',
    description: 'The first FLUX request opens a browser for OAuth sign-in. Tokens refresh automatically after that.'
  }],
  configCaption: 'Prefer manual setup? Paste this into .cursor/mcp.json instead:',
  configCode: `{
  "mcpServers": {
    "FLUX": {
      "url": "https://mcp.bfl.ai"
    }
  }
}`
}, {
  id: 'codex',
  label: 'Codex',
  logo: 'codex',
  steps: [{
    number: 1,
    title: 'Let Codex set itself up',
    description: 'Opens the Codex app with the setup prompt filled in. Send it and the agent registers the server and verifies the connection.',
    button: {
      label: 'Open in Codex',
      href: `codex://threads/new?prompt=${encodeURIComponent(prompt)}`,
      logo: 'codex'
    }
  }, {
    number: 2,
    title: 'Or add it yourself',
    description: 'One command in your terminal registers FLUX as a remote MCP server.',
    code: 'codex mcp add FLUX --url https://mcp.bfl.ai',
    wide: true
  }, {
    number: 3,
    title: 'Sign in',
    description: 'Codex detects OAuth support and opens a browser automatically. Sign in, choose the organization you want billed, then start a new Codex session so the FLUX tools are loaded.'
  }]
}, {
  id: 'vscode',
  label: 'VS Code',
  logo: 'vscode',
  steps: [{
    number: 1,
    title: 'Install with one click',
    description: 'Click the button to open VS Code with the FLUX server pre-filled. VS Code will ask you to confirm the install.',
    button: {
      label: 'Add to VS Code',
      href: `vscode:mcp/install?${encodeURIComponent(JSON.stringify({
        name: 'FLUX',
        type: 'http',
        url: 'https://mcp.bfl.ai'
      }))}`,
      logo: 'vscode'
    }
  }, {
    number: 2,
    title: 'Start the server',
    description: 'Open the Chat view, pick <strong>Agent</strong> mode, and make sure FLUX is enabled in the tools picker. VS Code starts the server on demand.'
  }, {
    number: 3,
    title: 'Sign in',
    description: 'VS Code prompts you to authenticate the FLUX server. Sign in with your BFL account and choose the organization you want billed.'
  }],
  configCaption: 'Prefer manual setup? Paste this into .vscode/mcp.json (or your user mcp.json) instead:',
  configCode: `{
  "servers": {
    "FLUX": {
      "type": "http",
      "url": "https://mcp.bfl.ai"
    }
  }
}`
}, {
  id: 'devin',
  label: 'Devin',
  logo: 'devin',
  steps: [{
    number: 1,
    title: 'Open the MCP settings',
    description: 'In Devin, go to <strong>Settings → Connections → MCP servers</strong> and click <strong>Add a custom MCP</strong>. That button needs the <strong>Manage MCP Servers</strong> permission. Without it, use <strong>Suggest MCP Integration</strong> to ask an org admin.',
    button: {
      label: 'Devin MCP docs',
      href: 'https://docs.devin.ai/work-with-devin/mcp',
      logo: 'devin'
    }
  }, {
    number: 2,
    title: 'Add the FLUX server',
    description: 'Name it <code>FLUX</code>, add a short description, select the <strong>HTTP</strong> transport type, and enter <code>https://mcp.bfl.ai</code> as the server URL. Set the authentication method to <strong>OAuth</strong>.'
  }, {
    number: 3,
    title: 'Choose access, then test',
    description: 'Under <strong>Access</strong>, pick <strong>Organization</strong> to share one connection (connect a service account, not a personal one) or <strong>Personal</strong> so each member authenticates individually. Click <strong>Save</strong>, then <strong>Test listing tools</strong>. Devin prompts you to complete the OAuth flow during your first session, where you sign in and choose the organization you want billed.'
  }],
  configCaption: 'Prefer the Devin CLI? Run devin mcp add FLUX https://mcp.bfl.ai to write the entry below to .devin/mcp_config.local.json, then devin mcp login FLUX to authenticate:',
  configCode: `{
  "mcpServers": {
    "FLUX": {
      "url": "https://mcp.bfl.ai",
      "transport": "http"
    }
  }
}`
}, {
  id: 'mcp-remote',
  label: 'Other clients',
  logo: 'terminal',
  steps: [{
    number: 1,
    title: 'Run the bridge',
    description: 'Use this when your client supports stdio MCP servers but does not handle the OAuth flow on its own — for example <strong>Hermes</strong> (Nous Research) and similar tools that only accept static bearer tokens. A browser opens for sign-in; tokens cache to <code>~/.mcp-auth/</code> and refresh automatically.',
    code: 'npx -y mcp-remote https://mcp.bfl.ai',
    wide: true
  }, {
    number: 2,
    title: 'Add it to your config',
    description: 'Register FLUX as a local stdio server in your client’s MCP config. Use the JSON example below and adapt the server name if needed.',
    button: {
      label: 'mcp-remote docs',
      href: 'https://www.npmjs.com/package/mcp-remote'
    }
  }, {
    number: 3,
    title: 'Restart the client',
    description: 'Your client talks to local stdio. mcp-remote forwards requests to <code>https://mcp.bfl.ai</code> over HTTP and keeps OAuth tokens fresh.'
  }],
  configCaption: 'For stdio-based clients, the MCP config usually looks like this:',
  configCode: `{
  "mcpServers": {
    "FLUX": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://mcp.bfl.ai"]
    }
  }
}`
}];

export const agentPrompt = `Connect the FLUX MCP server to this agent, then verify it works.

Use https://docs.bfl.ai/llms.txt as the source of truth for FLUX setup, models, and API reference — prefer it over inventing steps.

Steps:
1. If FLUX MCP tools are already available in this agent, skip installation and go to step 4.
2. Register the remote HTTP MCP server https://mcp.bfl.ai under the name "FLUX":
   - Claude Code: run \`claude mcp add --transport http FLUX https://mcp.bfl.ai\`
   - Codex: run \`codex mcp add FLUX --url https://mcp.bfl.ai\`, then \`codex mcp login FLUX\`
   - Cursor: add {"mcpServers":{"FLUX":{"url":"https://mcp.bfl.ai"}}} to ~/.cursor/mcp.json
   - Devin CLI: run \`devin mcp add FLUX https://mcp.bfl.ai\`, then \`devin mcp login FLUX\`
   - VS Code: add {"servers":{"FLUX":{"type":"http","url":"https://mcp.bfl.ai"}}} to .vscode/mcp.json
   - Clients without remote MCP or OAuth support: register a stdio server that runs \`npx -y mcp-remote https://mcp.bfl.ai\`
3. Authentication is OAuth with a BFL account — a browser window opens on first use. Let me complete the sign-in and pick the billed organization myself. Do not complete OAuth through an embedded or automated browser, and never ask me for an API key.
4. Verify the connection by calling the FLUX get_credits tool and report my remaining credit balance.
5. Briefly tell me what I can do now: generate and edit images, generate video, create variations, and browse my generation history, all by prompting.`;

export const CLIENT_LOGOS = {
  claude: {
    viewBox: '0 0 24 24',
    fill: '#D97757',
    d: 'm4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z'
  },
  codex: {
    viewBox: '0 0 320 320',
    fill: 'currentColor',
    d: 'm297.06 130.97c7.26-21.79 4.76-45.66-6.85-65.48-17.46-30.4-52.56-46.04-86.84-38.68-15.25-17.18-37.16-26.95-60.13-26.81-35.04-.08-66.13 22.48-76.91 55.82-22.51 4.61-41.94 18.7-53.31 38.67-17.59 30.32-13.58 68.54 9.92 94.54-7.26 21.79-4.76 45.66 6.85 65.48 17.46 30.4 52.56 46.04 86.84 38.68 15.24 17.18 37.16 26.95 60.13 26.8 35.06.09 66.16-22.49 76.94-55.86 22.51-4.61 41.94-18.7 53.31-38.67 17.57-30.32 13.55-68.51-9.94-94.51zm-120.28 168.11c-14.03.02-27.62-4.89-38.39-13.88.49-.26 1.34-.73 1.89-1.07l63.72-36.8c3.26-1.85 5.26-5.32 5.24-9.07v-89.83l26.93 15.55c.29.14.48.42.52.74v74.39c-.04 33.08-26.83 59.9-59.91 59.97zm-128.84-55.03c-7.03-12.14-9.56-26.37-7.15-40.18.47.28 1.3.79 1.89 1.13l63.72 36.8c3.23 1.89 7.23 1.89 10.47 0l77.79-44.92v31.1c.02.32-.13.63-.38.83l-64.41 37.19c-28.69 16.52-65.33 6.7-81.92-21.95zm-16.77-139.09c7-12.16 18.05-21.46 31.21-26.29 0 .55-.03 1.52-.03 2.2v73.61c-.02 3.74 1.98 7.21 5.23 9.06l77.79 44.91-26.93 15.55c-.27.18-.61.21-.91.08l-64.42-37.22c-28.63-16.58-38.45-53.21-21.95-81.89zm221.26 51.49-77.79-44.92 26.93-15.54c.27-.18.61-.21.91-.08l64.42 37.19c28.68 16.57 38.51 53.26 21.94 81.94-7.01 12.14-18.05 21.44-31.2 26.28v-75.81c.03-3.74-1.96-7.2-5.2-9.06zm26.8-40.34c-.47-.29-1.3-.79-1.89-1.13l-63.72-36.8c-3.23-1.89-7.23-1.89-10.47 0l-77.79 44.92v-31.1c-.02-.32.13-.63.38-.83l64.41-37.16c28.69-16.55 65.37-6.7 81.91 22 6.99 12.12 9.52 26.31 7.15 40.1zm-168.51 55.43-26.94-15.55c-.29-.14-.48-.42-.52-.74v-74.39c.02-33.12 26.89-59.96 60.01-59.94 14.01 0 27.57 4.92 38.34 13.88-.49.26-1.33.73-1.89 1.07l-63.72 36.8c-3.26 1.85-5.26 5.31-5.24 9.06l-.04 89.79zm14.63-31.54 34.65-20.01 34.65 20v40.01l-34.65 20-34.65-20z'
  },
  cursor: {
    viewBox: '0 0 466.73 532.09',
    fill: 'currentColor',
    d: 'M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z'
  },
  devin: {
    viewBox: '0 0 192 192',
    fill: 'currentColor',
    d: 'M 46.5 30.4 L 32.5 38.6 32.2 56.2 L 32 73.8 34.7 75.7 C 40.3 79.6, 60.7 91, 62 91 C 62.7 91, 66.9 88.9, 71.2 86.4 L 79 81.8 82.9 81.3 L 86.8 80.8 90.4 82 L 94 83.2 96.8 86.8 L 99.5 90.3 99.5 96.6 L 99.5 102.9 96 106.5 L 92.6 110 88.7 111.1 L 84.7 112.1 82.1 111.5 C 80.7 111.2, 76 108.9, 71.8 106.5 C 67.5 104, 63.1 102, 62 102 C 60.9 102, 53.7 105.6, 46 110.1 L 32 118.2 32 136 L 32 153.7 46.1 161.9 C 53.9 166.3, 61 170, 62 170 C 63 170, 70.1 166.4, 77.8 162 L 91.8 154 92.2 142.7 L 92.5 131.5 94.4 128.5 C 95.4 126.8, 97.6 124.4, 99.4 123.2 L 102.6 121 107.6 121 L 112.6 121 121.3 126 C 126.1 128.8, 130.4 131, 130.8 131 C 131.2 131, 138 127.2, 146 122.6 L 160.5 114.2 160.5 96.4 L 160.5 78.5 146.5 70.3 C 138.8 65.8, 131.7 62.1, 130.7 62 C 129.8 62, 127.3 63.1, 125.2 64.4 C 123.2 65.7, 119 68, 116.1 69.5 L 110.6 72.2 106.2 71.7 L 101.8 71.2 99.1 69.3 C 97.7 68.2, 95.6 65.8, 94.5 63.9 L 92.5 60.5 92 49.5 L 91.5 38.5 77.5 30.3 C 69.8 25.8, 62.8 22.2, 62 22.2 C 61.2 22.2, 54.2 25.9, 46.5 30.4'
  },
  vscode: {
    viewBox: '0 0 24 24',
    fill: '#007ACC',
    d: 'M23.15 2.587L18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12 .326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128 9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z'
  }
};

export const SetupDivider = ({label = 'or'}) => <div className="not-prose flex items-center gap-4 my-10">
    <span className="flex-1 h-px bg-[var(--aspen-surface-strong)] dark:bg-white/10" />
    <span className="text-sm font-medium uppercase tracking-wide text-gray-400 dark:text-gray-500">{label}</span>
    <span className="flex-1 h-px bg-[var(--aspen-surface-strong)] dark:bg-white/10" />
  </div>;

export const AgentSetup = ({prompt, serverName = 'FLUX', serverUrl = 'https://mcp.bfl.ai', caption, step, title, subtitle}) => {
  const [copied, setCopied] = useState(false);
  const [missing, setMissing] = useState(null);
  const renderLogo = (id, size, className) => {
    if (!id) return null;
    if (id === 'terminal') {
      return <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
          <polyline points="4 17 10 11 4 5" />
          <line x1="12" y1="19" x2="20" y2="19" />
        </svg>;
    }
    const logo = CLIENT_LOGOS[id];
    if (!logo) return null;
    return <svg width={size} height={size} viewBox={logo.viewBox} xmlns="http://www.w3.org/2000/svg" className={className} aria-hidden="true">
        <path fill={logo.fill} d={logo.d} />
      </svg>;
  };
  const enc = encodeURIComponent(prompt);
  const cursorConfig = typeof btoa !== 'undefined' ? btoa(JSON.stringify({
    url: serverUrl
  })) : '';
  const vscodeConfig = encodeURIComponent(JSON.stringify({
    name: serverName,
    type: 'http',
    url: serverUrl
  }));
  const actions = [{
    id: 'claude-code',
    logo: 'claude',
    label: 'Open in Claude Code',
    hint: 'Opens with the setup prompt ready to send',
    hrefs: [`claude://code/new?q=${enc}`, `claude-cli://open?q=${enc}`],
    app: 'Claude Code',
    install: 'https://claude.com/product/claude-code'
  }, {
    id: 'codex',
    logo: 'codex',
    label: 'Open in Codex',
    hint: 'Opens with the setup prompt ready to send',
    href: `codex://threads/new?prompt=${enc}`,
    app: 'Codex',
    install: 'https://developers.openai.com/codex'
  }, {
    id: 'cursor',
    logo: 'cursor',
    label: 'Add to Cursor',
    hint: 'Installs the server directly',
    href: `cursor://anysphere.cursor-deeplink/mcp/install?name=${serverName}&config=${cursorConfig}`,
    app: 'Cursor',
    install: 'https://cursor.com'
  }, {
    id: 'vscode',
    logo: 'vscode',
    label: 'Add to VS Code',
    hint: 'Installs the server directly',
    href: `vscode:mcp/install?${vscodeConfig}`,
    app: 'VS Code',
    install: 'https://code.visualstudio.com'
  }, {
    id: 'claude',
    logo: 'claude',
    label: 'Add to Claude',
    hint: 'Opens your Claude connectors',
    href: 'https://claude.ai/customize/connectors',
    external: true
  }];
  const copyPrompt = () => {
    if (typeof navigator !== 'undefined' && navigator.clipboard) {
      navigator.clipboard.writeText(prompt);
      setCopied(true);
      setTimeout(() => setCopied(false), 1500);
    }
  };
  const launch = (event, action) => {
    if (!action.app || typeof window === 'undefined') return;
    event.preventDefault();
    setMissing(null);
    let left = false;
    const markLeft = () => {
      left = true;
    };
    const onVisibility = () => {
      if (document.hidden) left = true;
    };
    window.addEventListener('blur', markLeft);
    window.addEventListener('pagehide', markLeft);
    document.addEventListener('visibilitychange', onVisibility);
    const cleanup = () => {
      window.removeEventListener('blur', markLeft);
      window.removeEventListener('pagehide', markLeft);
      document.removeEventListener('visibilitychange', onVisibility);
    };
    const targets = action.hrefs || [action.href];
    targets.forEach((href, i) => {
      setTimeout(() => {
        if (left) return;
        window.location.href = href;
      }, i * 600);
    });
    setTimeout(() => {
      cleanup();
      if (!left) setMissing(action.id);
    }, 1400 + (targets.length - 1) * 600);
  };
  return <div className="not-prose my-8">
      {title && <div className="mb-4">
          <div className="flex items-center gap-3">
            {step && <span className="flex items-center justify-center flex-shrink-0 w-8 h-8 rounded-full bg-primary dark:bg-primary-light text-white dark:text-gray-900 font-semibold text-sm">
                {step}
              </span>}
            <h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 m-0">{title}</h3>
          </div>
          {subtitle && <p className="text-sm text-gray-600 dark:text-gray-400 mt-2 mb-0">{subtitle}</p>}
        </div>}
      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
        {actions.map(a => <a key={a.id} href={a.href || (a.hrefs || [])[0]} target={a.external ? '_blank' : undefined} rel={a.external ? 'noopener noreferrer' : undefined} title={a.hint} onClick={e => launch(e, a)} className="group flex items-center justify-center gap-3 w-full rounded-xl border border-[var(--aspen-surface-strong)] dark:border-white/15 bg-[var(--aspen-paper)] dark:bg-white/10 hover:bg-[var(--aspen-surface)] dark:hover:bg-white/20 hover:border-[var(--aspen-stone)] dark:hover:border-white/30 active:translate-y-px shadow-sm hover:shadow px-6 py-5 text-base font-semibold text-gray-900 dark:text-white no-underline transition-all">
            {renderLogo(a.logo, 22, 'flex-shrink-0')}
            {a.label}
          </a>)}
        <button onClick={copyPrompt} className="group flex items-center justify-center gap-3 w-full rounded-xl border border-[var(--aspen-surface-strong)] dark:border-white/15 bg-[var(--aspen-paper)] dark:bg-white/10 hover:bg-[var(--aspen-surface)] dark:hover:bg-white/20 hover:border-[var(--aspen-stone)] dark:hover:border-white/30 active:translate-y-px shadow-sm hover:shadow px-6 py-5 text-base font-semibold text-gray-900 dark:text-white no-underline transition-all">
          {copied ? <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="flex-shrink-0">
              <path d="M20 6 9 17l-5-5" />
            </svg> : <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="flex-shrink-0">
              <rect x="9" y="9" width="13" height="13" rx="2" />
              <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
            </svg>}
          {copied ? 'Copied' : 'Copy setup prompt'}
        </button>
      </div>
      {missing && <div className="flex items-start gap-2.5 mt-4 rounded-lg border border-amber-200 dark:border-amber-500/25 bg-amber-50 dark:bg-amber-500/10 px-4 py-3">
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="flex-shrink-0 mt-0.5 text-amber-600 dark:text-amber-400">
            <circle cx="12" cy="12" r="10" />
            <path d="M12 8v4" />
            <path d="M12 16h.01" />
          </svg>
          <p className="text-sm text-amber-900 dark:text-amber-200 m-0">
            {`You don't seem to have ${(actions.find(a => a.id === missing) || ({})).app} installed. `}
            <a href={(actions.find(a => a.id === missing) || ({})).install} target="_blank" rel="noopener noreferrer" className="underline font-medium text-amber-900 dark:text-amber-200">
              Install it
            </a>
            {' or try another client above.'}
          </p>
        </div>}
      {caption && <p className="text-xs text-gray-500 dark:text-gray-400 mt-4 mb-0">{caption}</p>}
    </div>;
};

export const SetupSteps = ({tabs, queryParam = 'client', step, title, subtitle}) => {
  const [activeId, setActiveId] = useState(tabs[0].id);
  const renderLogo = (id, size, className) => {
    if (!id) return null;
    if (id === 'terminal') {
      return <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
          <polyline points="4 17 10 11 4 5" />
          <line x1="12" y1="19" x2="20" y2="19" />
        </svg>;
    }
    const logo = CLIENT_LOGOS[id];
    if (!logo) return null;
    return <svg width={size} height={size} viewBox={logo.viewBox} xmlns="http://www.w3.org/2000/svg" className={className} aria-hidden="true">
        <path fill={logo.fill} d={logo.d} />
      </svg>;
  };
  const [copiedKey, setCopiedKey] = useState(null);
  const active = tabs.find(t => t.id === activeId) ?? tabs[0];
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const params = new URLSearchParams(window.location.search);
    const fromUrl = params.get(queryParam);
    if (fromUrl && tabs.some(t => t.id === fromUrl)) {
      setActiveId(fromUrl);
    }
  }, [queryParam, tabs]);
  const selectTab = id => {
    setActiveId(id);
    if (typeof window !== 'undefined') {
      const params = new URLSearchParams(window.location.search);
      params.set(queryParam, id);
      const newUrl = `${window.location.pathname}?${params.toString()}${window.location.hash}`;
      window.history.replaceState(null, '', newUrl);
    }
  };
  const copy = (key, value) => {
    if (typeof navigator !== 'undefined' && navigator.clipboard) {
      navigator.clipboard.writeText(value);
      setCopiedKey(key);
      setTimeout(() => setCopiedKey(curr => curr === key ? null : curr), 1500);
    }
  };
  return <div className="not-prose">
      {title && <div className="mb-6">
          <div className="flex items-center gap-3">
            {step && <span className="flex items-center justify-center flex-shrink-0 w-8 h-8 rounded-full bg-primary dark:bg-primary-light text-white dark:text-gray-900 font-semibold text-sm">
                {step}
              </span>}
            <h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 m-0">{title}</h3>
          </div>
          {subtitle && <p className="text-sm text-gray-600 dark:text-gray-400 mt-2 mb-0">{subtitle}</p>}
        </div>}
      <div className="flex justify-center mb-8">
        <div className="inline-flex flex-wrap justify-center gap-1 p-1 rounded-full bg-[var(--aspen-surface-strong)] dark:bg-white/5 border border-[var(--aspen-surface-strong)] dark:border-white/10">
          {tabs.map(tab => {
    const selected = activeId === tab.id;
    return <button key={tab.id} onClick={() => selectTab(tab.id)} className={`inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-full transition-colors ${selected ? 'bg-[var(--aspen-paper)] text-gray-900 shadow-sm dark:bg-white dark:text-gray-900' : 'text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white'}`}>
                {tab.logo && renderLogo(tab.logo, 15, 'flex-shrink-0')}
                {tab.label}
              </button>;
  })}
        </div>
      </div>

      <div className={`grid grid-cols-1 ${active.steps.some(s => s.wide) ? 'md:grid-cols-4' : 'md:grid-cols-3'} gap-4`}>
        {active.steps.map((step, i) => {
    const key = `${active.id}-${i}`;
    return <div key={key} className={`rounded-2xl border border-[var(--aspen-surface-strong)] dark:border-white/10 bg-[var(--aspen-paper)] dark:bg-white/5 p-6 flex flex-col ${step.wide ? 'md:col-span-2' : ''}`}>
              <div className="text-base font-semibold text-gray-400 dark:text-gray-500 mb-3">
                {step.number}
              </div>
              <h3 className="text-base font-semibold text-gray-900 dark:text-gray-100 mt-0 mb-2">
                {step.title}
              </h3>
              <p className="text-sm text-gray-600 dark:text-gray-400 mb-4 flex-1" dangerouslySetInnerHTML={{
      __html: step.description
    }} />
              {step.button && step.button.copyText && <button onClick={() => copy(`${key}-btn`, step.button.copyText)} className="self-start mt-1 inline-flex items-center gap-2 rounded-lg bg-[var(--aspen-surface)] dark:bg-white/10 hover:bg-[var(--aspen-surface-strong)] dark:hover:bg-white/15 text-gray-900 dark:text-gray-100 text-sm font-medium px-3 py-2">
                  {step.button.logo && renderLogo(step.button.logo, 14)}
                  {copiedKey === `${key}-btn` ? 'Copied' : step.button.label}
                  {copiedKey !== `${key}-btn` && <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                      <rect x="9" y="9" width="13" height="13" rx="2" />
                      <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
                    </svg>}
                </button>}
              {step.button && !step.button.copyText && <a href={step.button.href} target={step.button.href.startsWith('http') ? '_blank' : undefined} rel={step.button.href.startsWith('http') ? 'noopener noreferrer' : undefined} className="self-start mt-1 inline-flex items-center gap-2 rounded-lg bg-[var(--aspen-surface)] dark:bg-white/10 hover:bg-[var(--aspen-surface-strong)] dark:hover:bg-white/15 text-gray-900 dark:text-gray-100 text-sm font-medium px-3 py-2 no-underline">
                  {step.button.logo && renderLogo(step.button.logo, 14)}
                  {step.button.label}
                  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
                    <path d="M7 17 17 7" />
                    <path d="M7 7h10v10" />
                  </svg>
                </a>}
              {step.code && <div className="mt-1 flex items-start justify-between gap-2 rounded-lg bg-[var(--aspen-surface-strong)] dark:bg-black/40 border border-[var(--aspen-stone)] dark:border-white/10 px-3 py-2">
                  <code className="text-xs sm:text-sm font-mono text-gray-700 dark:text-gray-200 break-all whitespace-pre-wrap min-w-0 flex-1">{step.code}</code>
                  <button onClick={() => copy(key, step.code)} className="flex-shrink-0 text-gray-400 hover:text-gray-700 dark:hover:text-gray-100 mt-0.5" aria-label="Copy">
                    {copiedKey === key ? <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
                        <path d="M20 6 9 17l-5-5" />
                      </svg> : <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                        <rect x="9" y="9" width="13" height="13" rx="2" />
                        <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
                      </svg>}
                  </button>
                </div>}
              {step.codes && step.codes.map((c, ci) => {
      const codeKey = `${key}-c${ci}`;
      return <div key={ci} className={`${ci === 0 ? 'mt-1' : 'mt-2'} flex items-start gap-2 rounded-lg bg-[var(--aspen-surface-strong)] dark:bg-black/40 border border-[var(--aspen-stone)] dark:border-white/10 px-3 py-2`}>
                    {c.label && <span className="text-xs font-medium text-gray-500 dark:text-gray-400 flex-shrink-0 mt-0.5">
                        {c.label}
                      </span>}
                    <code className="text-xs sm:text-sm font-mono text-gray-700 dark:text-gray-200 break-all whitespace-pre-wrap min-w-0 flex-1">{c.value}</code>
                    <button onClick={() => copy(codeKey, c.value)} className="flex-shrink-0 text-gray-400 hover:text-gray-700 dark:hover:text-gray-100" aria-label="Copy">
                      {copiedKey === codeKey ? <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
                          <path d="M20 6 9 17l-5-5" />
                        </svg> : <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                          <rect x="9" y="9" width="13" height="13" rx="2" />
                          <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
                        </svg>}
                    </button>
                  </div>;
    })}
            </div>;
  })}
      </div>

      {active.configCode && <div className="mt-8">
          {active.configCaption && <p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
              {active.configCaption}
            </p>}
          <div className="relative">
            <pre className="rounded-xl bg-[var(--aspen-surface-strong)] dark:bg-black/40 border border-[var(--aspen-stone)] dark:border-white/10 p-4 text-xs sm:text-sm font-mono text-gray-800 dark:text-gray-200 overflow-x-auto m-0">
              <code>{active.configCode}</code>
            </pre>
            <button onClick={() => copy(`${active.id}-config`, active.configCode)} className="absolute top-3 right-3 text-gray-400 hover:text-gray-700 dark:hover:text-gray-100" aria-label="Copy config">
              {copiedKey === `${active.id}-config` ? <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M20 6 9 17l-5-5" />
                </svg> : <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <rect x="9" y="9" width="13" height="13" rx="2" />
                  <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
                </svg>}
            </button>
          </div>
        </div>}
    </div>;
};

<Tabs>
  <Tab title="API">
    Start generating images with FLUX: create an account, add credits, create an API key, and make your first call — for FLUX 3 or FLUX.2.

    <Steps>
      <Step title="Create your account">
        Go to [dashboard.bfl.ai](https://dashboard.bfl.ai) and register your account. You'll need to confirm your email before logging in.

        When you create your account, we automatically set up:

        * A **Default Organization** - your workspace for managing team members and billing
        * A **Default Project** - where your API keys and usage are tracked

        <Tip>
          **Organizations** group your team and billing. **Projects** isolate your work with separate API keys and usage tracking. Think of organizations as your company and projects as different apps or environments.
        </Tip>
      </Step>

      <Step title="Add credits to your account">
        Credits power all API and Playground usage.

        <Info>
          **Simple pricing:** 1 credit = \$0.01 USD
        </Info>

        **To add credits:**

        1. Go to [dashboard.bfl.ai](https://dashboard.bfl.ai)
        2. In your organization sidebar, navigate to **API → Credits**
        3. Click **Add Credits**
        4. Choose your amount and complete payment via Stripe

        <Tip>
          Credits are available immediately after payment. Start with \$10-20 to experiment with different models.
        </Tip>

        [Full credits & billing guide](/account_management/credits_billing)
      </Step>

      <Step title="Create your API key">
        <Info>
          **Prefer an interactive experience?** Use our [Dashboard Quickstart](https://dashboard.bfl.ai/get-started) to create your API key and make your first call.
        </Info>

        Navigate to **API → Keys** in your project sidebar and click **Add Key**. Give it a descriptive name like "Production App" or "Testing Environment" — each key is scoped to its project. The key is shown **once**, so copy it right away, then add it to your environment:

        ```bash theme={null}
        export BFL_API_KEY="your_api_key_here"
        ```

        <Warning>
          Your API key is only shown once when created. Store it securely before closing the dialog and never expose it in client-side code — treat it like a password.
        </Warning>
      </Step>

      <Step title="Make your first call">
        Generation is **asynchronous**: you POST a request, get back a `polling_url`, then poll it for the finished result. Here is that first request — a FLUX 3 video from a text prompt.

        <CodeGroup>
          ```python Python theme={null}
          import os, requests

          r = requests.post(
              "https://api.bfl.ai/v1/flux-3-video",
              headers={"x-key": os.environ["BFL_API_KEY"], "Content-Type": "application/json"},
              json={
                  "mode": "t2v",
                  "prompt": "a fox running through dawn mist",
              },
          ).json()
          print(r["polling_url"])
          ```

          ```bash cURL theme={null}
          curl -X POST https://api.bfl.ai/v1/flux-3-video \
            -H "x-key: $BFL_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
              "mode": "t2v",
              "prompt": "a fox running through dawn mist"
            }'
          ```
        </CodeGroup>

        See the [image generation guide](/quick_start/generating_images) for the polling loop and full examples in Python, TypeScript, and cURL.

        <Tip>
          Want to skip the code? Try our [Playground](https://playground.bfl.ai) to generate images directly in your browser.
        </Tip>
      </Step>
    </Steps>

    <Note>
      See our [full pricing page](/quick_start/pricing) for complete details or use the [pricing calculator](https://bfl.ai/pricing).
    </Note>
  </Tab>

  <Tab title="MCP">
    Connect FLUX to the tools you already use and generate images and video by prompting. OAuth sign-in, no API keys to manage.

    <AgentSetup step="1" title="Set up in one click" subtitle="Click your client. The editor buttons install mcp.bfl.ai directly. The agent buttons open a session with a setup prompt ready to send, which registers the server and verifies it worked." prompt={agentPrompt} />

    <SetupDivider />

    <SetupSteps step="2" title="Set up manually" subtitle="Pick your client for the full steps. For stdio-only or OAuth-incompatible clients (for example Hermes), use Other clients: it runs mcp-remote locally, handles the browser OAuth flow, refreshes tokens for you, and exposes FLUX as a normal stdio server." tabs={mcpSetupTabs(agentPrompt)} />

    <br />

    <br />

    <Note>
      See our [full pricing page](/quick_start/pricing) for complete details or use the [pricing calculator](https://bfl.ai/pricing).
    </Note>
  </Tab>
</Tabs>

## Next Steps

<Columns cols={2}>
  <Card title="Generate Your First Image" icon="image" href="/quick_start/generating_images">
    Learn how to create images using our API
  </Card>

  <Card title="Try the Playground" icon="play" href="https://playground.bfl.ai">
    Test models instantly without writing code
  </Card>

  <Card title="Explore FLUX.2" icon="sparkles" href="/flux_2/flux2_overview">
    Our latest and most powerful model family
  </Card>

  <Card title="Prompting Guide" icon="graduation-cap" href="/guides/prompting_summary">
    Get better results with effective prompts
  </Card>
</Columns>
