Integration Guide¶
API Overview¶
All API endpoints are available at /api/. Authentication endpoints are at /api/auth.
Authentication is via IDP-issued access tokens. The Floh web UI uses httpOnly session cookies (BFF pattern). External API clients use Authorization: Bearer <token> with a valid IDP access token. See External API Access for details.
Interactive API Documentation¶
The server provides auto-generated OpenAPI 3.0 documentation via Swagger UI:
| Resource | URL |
|---|---|
| Swagger UI (interactive explorer) | https://<host>/api/docs |
| OpenAPI JSON spec (machine-readable) | https://<host>/api/docs/json |
For local development this is https://localhost:7070/api/docs. The Swagger UI lets you browse all endpoints grouped by tag, inspect request/response schemas, and try out API calls directly. The JSON spec can be imported into tools like Postman, Insomnia, or used with OpenAPI code generators.
Identity provider setup¶
| Provider | Document |
|---|---|
| Okta (OIDC login + inbound SCIM into Floh) | Okta setup for Floh |
| Microsoft Entra ID (OIDC + inbound SCIM) | Entra setup for Floh |
| Okta (create / update / deactivate in Okta) | Okta outbound user lifecycle |
| Inbound SCIM (any IdP) | Inbound SCIM |
| Outbound SCIM (Floh → any IdP) | SCIM outbound |
Endpoints¶
Auth (at /api/auth, not versioned)¶
GET /api/auth/config— check if OIDC is enabledGET /api/auth/me— current user info
Browser login lives on the console or portal BFF (/bff/login, /bff/callback).
The API does not register /api/auth/login or /api/auth/callback.
Workflows¶
GET /api/workflows— list definitions (paginated)POST /api/workflows— create definitionGET /api/workflows/:id— get definitionPUT /api/workflows/:id— update definition (draft only)POST /api/workflows/:id/publish— publish (draft → active)POST /api/workflows/:id/start— start runDELETE /api/workflows/:id— delete definition
See also:
- AI Assistant Integration — scoped API tokens, workflow validation, run diagnosis, documentation generation, and MCP tool access.
- AI Agents in Floh Workflows — product strategy, safe use cases, guardrails, MVP patterns, and implementation path for using LLMs inside workflow design and execution.
Runs¶
GET /api/runs— list runs (paginated, filterable by status, definitionId)GET /api/runs/:id— get runPUT /api/runs/:id— edit run variables (pending/running only)POST /api/runs/:id/cancel— cancel run
Tasks & Approvals¶
GET /api/tasks— list assigned tasksPOST /api/tasks/:id/complete— complete taskGET /api/approvals— list pending approvalsPOST /api/approvals/:id/decide— approve/reject
Other¶
GET /api/usersGET /api/connectors,POST /api/connectors,POST /api/connectors/:id/testGET /api/schedules,POST /api/schedules,PATCH /api/schedules/:idGET /api/audit-logs— with filtersGET /api/reports/*— workflow-stats, sla-compliance, approver-performanceGET /api/health— health check
Authorization¶
Floh does not store roles or permissions. Every authenticated request carries
an OIDC access token, and the token's scope claim is the caller's
permission set. When scope is absent (Entra v2 and Okta access tokens often
emit scp instead — a string or string[]), Floh reads scp. Grant or
revoke access by changing the scopes Authifi (or the IdP) issues for a user,
not by editing anything in Floh.
Two things narrow the effective permission set on top of scope:
- Channel membership. Each permission is reachable from a fixed subset of
the console / portal / MCP clients. A portal token carrying a console-only
scope such as
system:administerdoes not get that permission. The mapping lives inpackages/shared/src/azp-allow-set.ts. system:administeris console-only. It replaces every place the product previously asked "is this caller an admin?".
The full permission catalog, with per-channel availability, lives in Roles & Entitlements.
Note:
role_definition/role_assignment/role_entitlementand the/api/rolesUI are a provisioning feature — they describe roles Floh grants in downstream systems (Google Workspace, LDAP, ...). They have never governed access to Floh itself and are unaffected by the above.
External API Access¶
A client of your own is not currently accepted
Beyond verifying aud, authenticate resolves the token's authorized
party (azp / client_id) through resolveKnownOidcClient, which
matches only the configured console client (OIDC_CLIENT_ID), the
configured portal client (PORTAL_OIDC_CLIENT_ID), and the reserved
portal and MCP client identifiers. Any other client is rejected with
403 CHANNEL_DENIED, however correctly its token is signed, scoped,
and audienced.
In practice API access today is channel-scoped: you call through the console or portal client rather than one you register yourself. (MCP is a registered resource server but not a registered client, so it is not a third option here.) Adding a registration path for external clients is tracked in #1190; the steps below describe the token requirements and remain accurate for the existing channel clients.
External applications call Floh APIs with an access token from the IDP (Authifi or any compliant OIDC/OAuth 2.0 provider), issued to one of the channel clients above.
Prerequisites¶
- Register the Floh resource in your IDP. The resource identifier URI becomes the
audclaim in issued access tokens. Floh verifies that claim against its channel-audience allow-list —FLOH_CONSOLE_AUDIENCE,FLOH_PORTAL_AUDIENCE, andFLOH_MCP_AUDIENCE— so request a token for whichever channel your integration uses. (FLOH_RESOURCE_ID, aliasOIDC_AUDIENCE, names the API resource for operator tooling but is not accepted asaud.) - Use one of the registered channel clients — console or portal — with the authorization code grant. A newly registered client of your own will fail the authorized-party check described above. MCP is not a third option:
setup-authifi-oidc-clientscreates only the console and portal clients, andfloh-mcp-clientis reserved in the allow-set but not registered, so selecting it fails at the identity provider before a token is issued. MCP integrations use a console-channel token today (see MCP setup). - Request a token for the channel audience your integration targets.
No service-to-service path into the Floh API today
This limit is about inbound calls to Floh's API. It does not describe Floh's own outbound integrations, which do use client credentials — see the note below.
A caller cannot reach the Floh API with client_credentials. The channel
clients are registered with
grantTypes: ["authorization_code", "refresh_token"]
(scripts/setup-authifi-oidc-clients-lib.mjs:207), and no dedicated MCP
client is registered at all, so the request is refused by the identity
provider before it ever reaches Floh. Every supported inbound integration
is user-delegated: obtain the token through the authorization code flow.
Provisioning a machine client whose identifier the API accepts is part of #1190.
Floh's own outbound client-credentials flows
Floh acts as a client-credentials client in two places, both optional and neither required to run the platform:
| Flow | Reached when | Code |
|---|---|---|
| Authifi vault (secrets manager) | SECRETS_BACKEND=authifi. The default is env, and AuthifiSecretProvider is dynamically imported only on that branch |
config/authifi-secret-provider.ts:153 |
| Authifi connector | A workflow runs an Authifi connector command; the client is built per command, not at startup | modules/connectors/authifi-client.ts:102 |
The dedicated floh-vault-client is registered only when you pass
--vault to setup-authifi-oidc-clients, and it is deliberately
restricted to grantTypes: ["client_credentials"] — the script rejects
any other grant for that client. A baseline deployment
(SECRETS_BACKEND=env, no Authifi connector configured) performs no
client-credentials exchange at all.
Connectors for non-Authifi systems have their own independent
client-credentials support — outbound SCIM (authType=oauth2_client_credentials)
and the Entra Graph client — and are likewise per-connector, not global.
Calling the API¶
Include the IDP-issued access token in the Authorization header:
Floh validates the token against the IDP's JWKS endpoint, checking:
- Signature — via the IDP's published JSON Web Key Set
- Issuer (
iss) — must matchOIDC_ISSUER - Audience (
aud) — must match one of the configured channel audiences (FLOH_CONSOLE_AUDIENCE,FLOH_PORTAL_AUDIENCE,FLOH_MCP_AUDIENCE).FLOH_RESOURCE_ID/OIDC_AUDIENCEis not accepted asaud - Expiry (
exp) — token must not be expired
Auto-provisioned Users¶
When an API request arrives with a valid IDP token for a sub (subject) that
has no account in Floh, a user record is created automatically so audit and
ownership columns have a row to point at. That row has no Floh
permissions of its own — the next request still authorizes only from the
token's scope / scp. Grant access by issuing those scopes at the IdP, not
by editing the Floh user record.
Configuration¶
| Variable | Description | Example |
|---|---|---|
OIDC_ISSUER |
IDP issuer URL | https://a-ci.ncats.io/_api/auth/ls |
FLOH_CONSOLE_AUDIENCE |
Console channel resource identifier — an accepted access-token aud |
http://console.floh.api |
FLOH_PORTAL_AUDIENCE |
Portal channel resource identifier — an accepted access-token aud |
http://portal.floh.api |
FLOH_MCP_AUDIENCE |
MCP channel resource identifier — an accepted access-token aud |
http://mcp.floh.api |
FLOH_RESOURCE_ID |
API resource id used by operator tooling. Not accepted as aud, and not required at startup |
http://floh.api |
OIDC_AUDIENCE |
Deprecated alias of FLOH_RESOURCE_ID |
(omit; use FLOH_RESOURCE_ID) |
OIDC_CLIENT_ID |
Client ID for the Floh web app's own OIDC flow | floh-client |
Startup requires OIDC_ISSUER, OIDC_CLIENT_ID, and at least one of the
three channel audiences. It does not require FLOH_RESOURCE_ID.
Custom Connectors¶
Connector Interface¶
interface ConnectorHandler {
name: string;
version: string;
execute(context: ConnectorContext): Promise<ConnectorResult>;
}
interface ConnectorContext {
config: Record<string, unknown>;
variables: Record<string, unknown>;
stepId: string;
instanceId: string;
}
interface ConnectorResult {
success: boolean;
/** Step's domain payload — namespaced under the step's `outputKey` in the run record. */
payload?: Record<string, unknown>;
/** Top-level keys merged into the run variable bag on success. */
variables?: Record<string, unknown>;
/** Failure-only structured context (HTTP status, response body, validation details). */
diagnostics?: Record<string, unknown>;
error?: string;
}
Registering a Connector¶
import { registerConnector } from "./modules/connectors/registry.js";
registerConnector({
name: "slack",
version: "1.0.0",
async execute(ctx) {
const { webhookUrl, message } = ctx.config as any;
const resp = await fetch(webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: message }),
});
return { success: resp.ok };
},
});
Built-in Connectors¶
| Name | Description |
|---|---|
http |
Makes HTTP requests to external APIs |
delay |
Pauses execution for a specified duration |
authifi |
Manages Authifi group membership via the Admin API |
To create a new instance of a built-in connector type (e.g. connecting to a second Authifi tenant), use the Connectors → New Connector → Built-in wizard in the UI, or send a POST /api/connectors request with the type set to the registered handler name and executionModel set to built_in. See the Connector Creation Guide for details.
Test Connectors¶
Three simulated connectors are registered automatically and provide realistic seed data for development and testing without external dependencies.
| Name | Description | Default Command |
|---|---|---|
test-ldap |
Simulated LDAP directory with users and groups | search |
test-db |
Simulated relational database with tables | query |
test-activedirectory |
Simulated Active Directory with users, groups, and account management | findUser |
Commands
- test-ldap —
test,search,bind,add,modify,delete - test-db —
test,query,insert,update,delete,list-tables - test-activedirectory —
test,findUser,findGroup,authenticate,getGroupMembers,enableAccount,disableAccount,addToGroup,removeFromGroup,setPassword,resetPassword(deprecated alias forsetPassword, LSA-8655),listUsers,listGroups,checkGroupMembership,checkAccountExists,createAccount(now accepts an optionalpasswordfor create-and-set in one round-trip)
Simulating Failures¶
All test connectors accept a simulateFailure config option. When set, the connector returns { success: false } immediately without executing the command. This is useful for testing error-handling paths in workflows.
// Boolean — uses a default error message
{ "connectorName": "test-ldap", "command": "search", "simulateFailure": true }
// → { success: false, error: "Simulated failure (test-ldap connector)" }
// String — uses the value as a custom error message
{ "connectorName": "test-db", "command": "query", "simulateFailure": "Connection refused" }
// → { success: false, error: "Connection refused" }
When simulateFailure is absent, false, or an empty string, the connector executes normally.
Pagination¶
All list endpoints support pagination:
page— page number (default: 1)pageSize— items per page (default: 20, max: 100)sortBy— field to sort bysortOrder—ascordesc
Response format: