본문으로 건너뛰기

AI providers

FX runs a workflow's own code with no provider at all. A step that calls an AI model (an image, a clip, a 3D model, structured text) runs on a route, model@provider, and a live run needs that provider's key, or its program for Codex and Grok Build. This page lists the ways to set one up and what each one serves.

PathWhat you set upWhat it servesWho bills
No providernothingyour own code and the built-in local stepsnobody
Your own CodexCodex, signed in to ChatGPTimagesyour ChatGPT plan; $0 to FX
Your own Grok BuildGrok Build, signed in to a Grok subscriptionimagesyour Grok subscription; $0 to FX
Grida GatewayGG_API_KEYtext, agent turns, images, video, 3D models and rigsyour organization's Grida AI credits
Your own keyseach provider's keythat provider's routeseach provider's account
A provider FX does not shipa declaration in fx.yaml, and an adapter of your own unless it speaks OpenAI's chat API: a community providerits routesthe provider's account

You can mix them: a project picks a route per capability in fx.yaml, and a step can name its own route:. grida fx doctor says which keys FX found, where, and which routes a live run could serve (Running).

No provider​

FX is a workflow engine first. Your own nodes, in Python or TypeScript, the built-in local steps (image.resize, json.merge, package, …) and file facts run with no key and without --live, and a run of them is cached and recorded like any other. Without --live, a step that would call a provider is refused for nothing, so you can build and test the rest of a workflow first, or answer paid calls with stand-ins.

Your own Codex​

With Codex installed and signed in to your ChatGPT account, the route imagegen@codex draws and edits images with no API key. Codex counts each image against your plan; FX books it at $0, and a run whose calls are all on Codex needs no --max-usd.

It serves images only (image.generate and image.edit), and Codex picks the model and the quality. Setup and caveats, sizes above all: Images from your own Codex.

Your own Grok Build​

With Grok Build installed and signed in to a Grok account whose subscription includes image generation, the route imagine@grok draws and edits images with no API key. Grok Build counts each image against your subscription; FX books it at $0, and a run whose calls are all on Grok Build (or on it and Codex) needs no --max-usd.

It serves images only (image.generate and image.edit), opaque ones, and Grok Build picks the model and the size. Setup and caveats: Images from your own Grok Build.

Grida Gateway​

One key serves every route on Grida Gateway (@gg). Create it at console.grida.co, where you also add credits and see what each call was charged, then set it:

GG_API_KEY=…

in the environment, or in a .env file in your project (keep it out of version control). FX books a GG call at what GG charged for it (GG's adapter reference).

The key needs both scopes, text for text and agent steps and media for everything else. GG's own API reference, written for people and agents alike, is gateway.grida.ai/llms.txt: key scopes, credits, spending caps and limits.

GG serves text and agent turns, images, video, and 3D models and rigs. It does not serve sound, speech or music: those routes need their provider's own key (below).

Your own provider keys​

Every other route takes its provider's key under its usual name. Set only the ones your routes use; a missing key refuses only the calls that need it, before anything is sent.

ProviderVariableBuilt-in routes forGet a key
OpenAIOPENAI_API_KEYimagesplatform.openai.com
OpenRouterOPENROUTER_API_KEYtext, agent turns, images, musicopenrouter.ai
falFAL_KEYimages, videofal.ai
TripoTRIPO_API_KEY3D models and rigsplatform.tripo3d.ai
ElevenLabsELEVENLABS_API_KEYsound effects, speechelevenlabs.io

OPENAI_BASE_URL, OPENROUTER_BASE_URL, FAL_BASE_URL, ELEVENLABS_BASE_URL and GG_BASE_URL point a provider at another address, such as a proxy. How FX reads keys and the .env file: Keys and live runs. A provider FX does not ship is declared in fx.yaml with the variables of its own key and address (below).

The built-in routes​

CapabilityGrida GatewayCodex, Grok BuildYour own keys
structured.generate, agent.turn✓OpenRouter
image.generate, image.edit✓✓OpenAI, OpenRouter, fal
video.generate✓fal
mesh.generate, mesh.rig✓Tripo
music.generateOpenRouter
sound.generate, speech.generateElevenLabs

The routes themselves, with their models and planning prices, are in FX's built-in route table: grida fx nodes <type> lists a capability's routes, and grida fx doctor says which ones a live run could serve. A project can add or restate routes in its own route_tables: (where prices come from). An entry that extends: a built-in route states only what differs, a price say, and keeps the rest, contract included; restating the same route this way keeps its identity, so its cached results stay (route tables).

A route's contract names the adapter that serves it, as adapter and adapter_behavior (providers.md §2), and the name picks the adapter: a provider may have more than one for a capability. On a provider and capability FX serves, FX fills in what a project route's contract leaves out: the provider's default for the capability as adapter, and the adapter_behavior of the adapter the contract names. So such a route always names its adapter. A contract that names an adapter FX does not have is served by none: grida fx doctor says no adapter, and a live call on it is refused. Every adapter checks its behaviour: up to 0.6.1, fal's and Tripo's checked only adapter, and served a route that named another adapter_behavior.

A model FX does not list​

New models arrive faster than FX's built-in table. On a provider FX speaks, a route of your own is enough: name the model, and give a cap when you do not know its price.

# routes/models.yaml, listed under route_tables: in fx.yaml
fx: routes/v1
routes:
- capability: image.generate
route: acme/img-a@openrouter # made up: any image model OpenRouter serves
price: { cap_usd: 0.20 } # each call holds $0.20 and is booked at what OpenRouter reports
features: [image_input]
max_input_pictures: 8
- capability: image.edit
route: acme/img-a/edit@fal # made up: fal's edit endpoint of a model
price: { cap_usd: 0.20 }
contract:
provider_options: { fal: { seed: 7 } }
  • OpenRouter and fal images. A route whose contract names no adapter gets FX's generic image adapter for the provider. It sends what the provider's image models share, the prompt and the input pictures, then your provider_options, and nothing FX chose for one model: no size, quality or background. The picture it gets back becomes a PNG, the same pixels. On fal the model is the endpoint id as fal publishes it, the generate endpoint for image.generate and the edit endpoint for image.edit. An exact size, a mask or a transparent background is refused before anything is sent: leave it out and give the model's own parameter in provider_options (Parameters FX does not expose).
  • GPT Image. The built-in OpenRouter and fal image routes name GPT Image's own adapters, which send what FX chose for it: maximum quality, its exact sizes and, on OpenRouter, its moderation. A route that extends one of them keeps that adapter, so extend one for a GPT Image model FX does not list, and declare a route whose contract names no adapter for any other model.
  • OpenRouter text. A new model id is enough for structured output and agent turns: the route gets FX's OpenRouter chat adapters, which send the model, the messages and a strict JSON schema (or strict function tools). A route's contract.request_policy may pin OpenRouter's routing and reasoning effort, as the built-in openai/gpt-6-astra@openrouter route does (OpenRouter's reference).
  • GG. A new model id is enough for images, video, structured output and agent turns: GG checks each model's own request. A 3D route also names GG's endpoint in its contract (and a rig route may name a check_endpoint), as the built-in GG 3D routes do. GG image edits take at most the pictures the route declares (max_input_pictures, 16 on the built-in route).
  • fal video. A video route of your own on fal draws the resolution tiers its price names and up to its max_units seconds; a capped route, whose price states neither, draws 360p, 720p, 1080p and 4k, from 3 to 10 seconds. It needs a first frame unless it declares the feature text_to_video (features: [text_to_video]), for a model that draws from the prompt alone; the built-in route does not. It sends the built-in route's request (image_url, end_image_url, a whole-number duration, aspect_ratio, resolution), so it fits only a fal endpoint that takes that shape.

Each adapter's reference lists what it sends and refuses (providers.md §2).

A provider FX does not ship​

A provider FX does not ship is a community provider: your project declares it in fx.yaml, under providers:, by the name its routes give it (<model>@acme), with the variable its key comes from, its address and the hosts its results may be downloaded from. Then it needs one of:

  • family: openai-compatible, for a server that speaks the OpenAI Chat Completions API, a model server on your machine included: FX's own adapters serve its structured.generate and agent.turn routes, with no code of yours;
  • adapter: <path>#<name>, for any other provider: an adapter you write in Python or TypeScript, a module of the project that turns FX's request into the provider's and its answer into FX's.
# fx.yaml
fx: project/v1
providers:
acme:
credential: { variable: ACME_API_KEY } # sent as authorization: Bearer <key>
base_url: { variable: ACME_BASE_URL, default: "https://api.acme.test/v1" }
family: openai-compatible # or adapter: adapters/acme.py#acme

FX reads its key from the environment or .env as it reads its own, redacts it, and books its calls in dollars against your ceiling, at a price or a cap its routes give; grida fx doctor lists it (provider acme api.acme.test ACME_API_KEY missing). FX keeps the checks, the retries, the cache and the record, as for its own providers. Community providers is the whole job: the declaration and its refusals, routes for it, the OpenAI-compatible family, writing an adapter in Python and in TypeScript, checking it offline, identity, sharing it, and its limits.

Parameters FX does not expose​

A provider often takes parameters FX has no setting for: a seed, a style, a safety switch. Give them in provider_options, by provider, on a step or in a route's contract:

steps:
facts:
uses: fx/structured.generate@1
with:
prompt: Name a colour.
schema: ./schemas/colour.json
provider_options:
openrouter: { seed: 7 }
  • Only the provider the call runs on reads its member. The others are kept, so a step that may run on several routes can give each provider its own. The parameters go in the request after FX's own, as the provider's API names them.
  • A route can carry them too, in its contract (contract: { provider_options: { openrouter: { seed: 7 } } }). A step's parameter replaces the route's of the same name, and adds the rest. An agent's turns take only their route's, since FX builds each turn's request itself.
  • FX's own settings stay FX's. A parameter FX sets itself (prompt, model, n, stream, …) is refused before anything is sent, at $0, and so is a file: send files through the type's inputs. Each provider's list is in its adapter's reference (providers.md §2). Codex and Grok Build take none.
  • Options are part of the call. Another value is another paid call, and the route's are part of its identity. They are recorded with the run, so never put a key in them: keys come only from the environment or .env.
  • What the provider does with them is the provider's. Beyond those refusals, FX does not check a parameter's name or value: a provider that refuses it fails the call, and one that changes its answer so FX's checks refuse it fails the attempt, billed.