How resolution works
A provider is an organization-scoped credential row, not a global setting. FirstFlow recognizes exactly three provider namesopenai, anthropic, and cloudflare-gateway and a feature never reaches for a raw key directly. It calls AiProviderResolverService, which resolves the right client for the organization that owns the agent in play.
The order is deterministic. The resolver prefers a directly configured provider; failing that it routes through a Cloudflare AI Gateway you configured (the gateway holds the upstream key and FirstFlow authenticates to it); failing that and only for the Anthropic family it falls back to the server’s ANTHROPIC_API_KEY environment variable. Branch/classify nodes add one more rung at the very top: a per-node API key, kept for explicit overrides and legacy nodes.
Resolution is keyed to the organization, derived from the agent. A feature passes an agentId; the resolver looks up the agent’s organizationId and reads that organization’s enabled provider rows. One organization’s keys never serve another’s traffic.
When the chain finds nothing, the resolver returns null rather than throwing. The calling feature treats that as “no AI available” and degrades the rest of the platform keeps running. That is why AI features quietly do nothing on a fresh self-host with no key set, instead of erroring.
How credentials are stored
The secret half of a provider the API key is encrypted with AES-256-GCM before it touches Postgres, in theencryptedAuth column. The non-secret half (a Cloudflare account id, gateway id, and manual model list) is stored as plaintext JSON in config. The encryption key comes from INTEGRATION_ENCRYPTION_KEY (a 64-hex-character string). If that variable is missing or malformed, FirstFlow auto-generates an ephemeral key and logs a warning fine for local development, but stored secrets become unreadable across restarts, so set a stable key in production.
Decrypted credentials never leave the server. The public HTTP shape (AiProviderPublic) exposes hasAuth: boolean instead of the key, and the resolver’s decrypted view is marked internal-only. When you read a provider back from the API you get its metadata and whether a key is present never the key itself.
Configuring a provider
Open Settings → AI Providers in the dashboard, choose a provider, and paste its key. FirstFlow validates the credential live before saving: it calls the provider’s real models endpoint (https://api.anthropic.com/v1/models for Anthropic, https://api.openai.com/v1/models for OpenAI) and rejects the save with a 400 if the key is unusable. A successful save returns the safe public view.
Under the hood this is a thin REST surface, all scoped to your organization via your dashboard session:
{ data } envelope. Adding a second provider of the same name returns 409 Conflict one row per provider name per organization.
1
Add the credential
Pick Anthropic, OpenAI, or Cloudflare AI Gateway and paste the key. The create call shape is the same for the direct providers only The save fails fast with
auth.apiKey matters:400 if the key cannot list models, so a stored provider is always one that worked at least once.2
Let FirstFlow fetch the model list
For OpenAI and Anthropic the model list is pulled live from the provider’s API and cached for 24 hours per organization, so dropdowns reflect what your key can actually reach. OpenAI ids containing
dall-e are typed as image; everything else is text.3
Assign models per feature
On the relevant editors (for example a classify node, covered below) choose the provider family and one of its live models. Composition and generation use Anthropic automatically see What each feature uses.
Cloudflare AI Gateway
The gateway is a routing front-end, not a model vendor. It tunnels to sub-providers, so its configuration carries an account id, a gateway id, and a manually maintained model list Cloudflare cannot enumerate models over its API, soGET /ai-providers/cloudflare-gateway/models returns exactly the list you saved.
Every model id in that list must be prefixed with its sub-provider, and the save is rejected otherwise. The resolver builds the upstream base URL from your account and gateway ids and authenticates with a cf-aig-authorization: Bearer <token> header the actual OpenAI/Anthropic key lives inside Cloudflare, not in FirstFlow.
gpt-4o is rejected with a message telling you to write openai/gpt-4o. Because the gateway can route either family, configuring it alone makes both the OpenAI and Anthropic families available to features that pick a family the resolver strips the prefix when it needs a bare model id.
What each feature uses
The features split into two groups: ones that always use Anthropic, and ones where you pick the family per node. The provider/model column below is the runtime default, not a hard limit.
Composition and generation go through the Anthropic-compatible client the resolver returns, so a Cloudflare Gateway with an
anthropic/… model satisfies them too. The feature asks the resolver for an Anthropic client; whether that client points at api.anthropic.com or at your gateway is transparent to the feature.
Branch and classify nodes
A classify node resolves a branch by calling an LLM with the user’s projected context and the branch labels, then matching the model’s answer back to a branch id. Its config is stored per node in the agent’ssdkSettings and carries a provider family (openai or anthropic), a model, free-text instructions, and tuning knobs:
confidenceFloor(default0.5) answers below this route to the fallback branch.resolutionModeblockingwaits for the decision;optimisticproceeds and corrects.ttlMs(default300000) how long a resolved decision is reused.fallbackBranchIdwhere unmatched / low-confidence / errored decisions land.signalMaskwhich context signals (message, metrics, behaviors, session, memory) are projected to the model.
fallbackBranchId with confidence: 0 rather than erroring the flow.
The credential precedence for these nodes is the full chain with the per-node key on top: an explicit per-node key wins, otherwise the resolver uses the organization’s provider or gateway. There is no env fallback for a node that names openai; the env fallback only ever covers the Anthropic family.
Self-hosting and environment fallback
Dashboard-managed providers are the primary path and work identically on a self-host. The environment variables are the floor beneath them useful for a single-tenant deployment where you would rather set one key than click through the UI. SetANTHROPIC_API_KEY to give every organization an Anthropic fallback for composition, generation, and the analytics evaluators when they have no provider row. OPENAI_API_KEY exists for parity, but note the resolver’s env fallback is Anthropic-only: a classify node configured for openai with no organization provider will not pick up OPENAI_API_KEY configure an OpenAI provider in the dashboard instead. Set a stable INTEGRATION_ENCRYPTION_KEY so encrypted keys survive restarts.