API Overview
Run your published Agents, Flows and Apps, and provision end-user accounts, plans and access, from your own systems with the FormWise External API.
API Overview
The FormWise External API does two things from your own systems:
- Run what you built. Published Agents, Flows and Apps are callable over HTTP: send a message to an Agent, fill in a Flow's questions, or call an App's typed Transaction Service. The same surface is available to AI clients as an MCP server.
- Manage your end users. Create accounts, assign plans, put people in groups, and admit groups at your Portals. The headline use case is bring-your-own-billing: you take payment anywhere (your own checkout, an invoice, a partner platform), then call the API to create the account and grant access - no Stripe Connect required.
The API is versioned under /api/v1. Once published, the v1 surface only changes additively - fields are added, never renamed or removed.
Base URL: https://builder.formwise.ai/api/v1Availability
The External API and the MCP server are available on the Pro and Agency plans. API keys are managed in Settings → API; if you don't see that page, the developer API isn't enabled for your organization yet.
Authentication
Every request carries an organization API key as a bearer token:
curl https://builder.formwise.ai/api/v1/end-users \
-H "Authorization: Bearer fw_live_..."There are two kinds of key. Both work on every endpoint; they differ in who they act as.
| Organization key | Personal key | |
|---|---|---|
| Who creates it | An admin, in Settings → API | Any member, for themselves, in Settings → API |
| Acts as | The organization (no person attached) | That member. Runs and changes are recorded against them |
| Scopes | Any | Capped by the member's role, re-checked on every call |
| When it stops working | When revoked | When revoked, or when the member leaves the organization |
| MCP server | Not accepted | Accepted |
Use an organization key for headless integrations (a backend job, Zapier, a CRM sync). Use a personal key when a person is behind the calls, for example an AI client on their desktop. Keys are organization-wide - one key works across every Portal and Suite in your organization.
Creating a key
- Open Settings → API in the dashboard.
- Name the key (for example "Production - Zapier") and choose its scopes.
- Copy the secret immediately. It is shown exactly once - FormWise stores only a hash, so a lost secret can't be recovered. Create a new key instead.
Each key shows its display prefix (fw_live_xxxx), creation date, and when it was last used. Revoking a key takes effect immediately - requests using it start failing with api_key_revoked. An organization can hold up to 20 active organization keys; each member can hold up to 5 personal keys.
Scopes
| Scope | Grants | Personal keys: minimum role |
|---|---|---|
agents:read | List published Agents | Viewer |
agents:execute | Talk to Agents through the Conversation Service | Viewer |
agents:write | Create and edit Agents, publish versions | Member |
flows:read | List published Flows and their inputs | Viewer |
flows:execute | Run Flows | Viewer |
flows:write | Create and edit Flows, publish versions | Member |
apps:read | List published Apps and their service contracts | Viewer |
apps:execute | Run Apps through the Conversation and Transaction services | Viewer |
apps:write | Create and edit Apps, publish versions | Member |
knowledge:read | List, search and read Knowledgebases: documents, pages and (personal keys) your own memories | Viewer |
knowledge:write | Create Knowledgebases (within your plan's limit), create, edit, append to and move pages, and upload documents | Member |
end_users:read | List and read end users, user groups, portals, and the plan catalog | Admin |
end_users:write | Create and update end users and user groups; admit groups at portals | Admin |
The API enforces scopes per endpoint. A personal key reaches exactly what the member's current role allows, no more and no less: change the role and the next call reflects it, and a personal key created before a scope existed picks it up on its own. An organization key keeps the scope list it was created with, so an organization key created before a scope existed doesn't carry it - create a new key to use it. Deleting an Agent, Flow or App is not a scope: it follows the same rule as the dashboard and needs the Admin role.
Requests and responses
- Request bodies are JSON (
Content-Type: application/json). - Every response - success or error - carries an
X-Request-Idheader. Include it when contacting support about a specific call. - Errors use one stable envelope:
{ "error": { "code": "...", "message": "..." } }. Branch oncode;messageis human-readable text and may change. See Errors for the full code table. - Creates take an
Idempotency-Keyheader (any unique string, up to 64 characters):POST /v1/knowledgebases,POST /v1/knowledgebases/{id}/pages,POST /v1/knowledgebases/{id}/sources,POST /v1/agents,/v1/flows,/v1/appsandPOST /v1/end-users. A retry with the same key within 24 hours returns the first response again (withIdempotent-Replayed: true) instead of creating twice; the same key with a different body is409 conflict. Keys are scoped to your organization and to the address they were sent to.
Rate limits
Each key may make 120 requests per minute (sliding 60-second window). Exceeding the limit returns 429 with a Retry-After header giving the number of seconds to wait.
Quickstart
Create an end user with a plan in one call:
curl -X POST https://builder.formwise.ai/api/v1/end-users \
-H "Authorization: Bearer fw_live_..." \
-H "Content-Type: application/json" \
-d '{
"email": "customer@example.com",
"fullName": "Sam Customer",
"tierPlanIds": ["<tier-plan-uuid>"]
}'The person then signs in to your Portal with that email address using a one-time code - there is no password to distribute. Find plan IDs with GET /tier-plans or from the plan editor in the dashboard.
Running Agents
Published Agents are the simplest thing to call: a message in, a reply out, with the conversation kept for you on the server.
# Discover what's callable
curl https://builder.formwise.ai/api/v1/agents \
-H "Authorization: Bearer fw_live_..."
# Talk to an Agent
curl -X POST https://builder.formwise.ai/api/v1/agents/<agent-uuid>/conversation \
-H "Authorization: Bearer fw_live_..." \
-H "Content-Type: application/json" \
-d '{ "message": "Summarize the open tickets from this week." }'The reply carries a conversation_id. Pass it back on the next call and the prior turns are restored server-side, so you never resend history. Set stream: true for Server-Sent Events. The Conversation Service only ever runs the Agent's published version; to try a draft, use POST /agents/{id}/draft/run, which needs agents:write. Each call runs and bills as one execution against your organization's credits.
List published Agents
Discovery: id, name, description.
Conversation Service
Send a message; multi-turn via conversation_id.
Running Flows
A published Flow that asks questions is, to the API, a function with named parameters: its questions are the inputs. GET /flows lists each Flow's inputs (with types, options and which are required) and the same list as a JSON Schema, and POST /flows/{id}/run takes them by name and runs the Flow once, exactly as if the person had filled in the form.
# See what a Flow needs
curl https://builder.formwise.ai/api/v1/flows/<flow-uuid> \
-H "Authorization: Bearer fw_live_..."
# Run it with its inputs
curl -X POST https://builder.formwise.ai/api/v1/flows/<flow-uuid>/run \
-H "Authorization: Bearer fw_live_..." \
-H "Content-Type: application/json" \
-d '{ "inputs": { "company_name": "Acme", "plan_tier": "Pro" } }'Inputs are validated before anything runs or is charged: a missing required input, a value outside a question's options, or an unknown key comes back as invalid_request naming every problem. Parameter names come from the question labels (Company name becomes company_name). A Flow with no questions is talked to like an Agent, with message and conversation_id.
List published Flows
Each Flow's inputs and their JSON Schema.
Run a Flow
Inputs in, output out; sync or streamed.
Using Knowledgebases as a data layer
A Knowledgebase is the curated body of documents and pages your Agents draw on. The API opens it to your own software as well: an agent framework, a nightly job or an n8n flow can search the same documents and pages directly, with no Agent in between, and file what it learned back as pages for your Agents to use on their next run.
# What is there
curl https://builder.formwise.ai/api/v1/knowledgebases \
-H "Authorization: Bearer fw_live_..."
# Search every knowledgebase the key can read (documents, pages, memories)
curl -X POST https://builder.formwise.ai/api/v1/knowledgebases/all/search \
-H "Authorization: Bearer fw_live_..." \
-H "Content-Type: application/json" \
-d '{ "query": "refund window for annual plans", "kinds": ["documents", "pages"] }'
# What changed since the last run
curl "https://builder.formwise.ai/api/v1/knowledgebases/all/changes?since=2026-09-24T00:00:00Z" \
-H "Authorization: Bearer fw_live_..."
# File notes back
curl -X POST https://builder.formwise.ai/api/v1/knowledgebases/<knowledgebase-uuid>/pages \
-H "Authorization: Bearer fw_live_..." \
-H "Content-Type: application/json" \
-d '{ "title": "Weekly support digest", "templateKey": "meeting_notes", "bodyMarkdown": "- Refund questions doubled after the pricing change." }'Every search result carries a citation: a document hit names its source, a page hit carries the page id to fetch in full, a memory hit its id. Searching documents spends one of your organization's credits per call; pages, reads, the page index and the changes feed are free, so pass kinds: ["pages"] when documents are not needed. Use all as the id to span every Knowledgebase the key may read, or a specific id to stay inside one.
Writes never wipe a page: create one, append to one, or edit one in place with PATCH .../pages/{pageId}, whose anchored operations change only the exact text they name and refuse the whole edit if any anchor misses. There is no page delete over the API. Inside a page, mention another page as [Title](fwpage://<page-id>) and an uploaded file as [Name](fwsource://<source-id>); they render as the same pills the editor makes, and the response reports which links resolved. If your organization reviews agent writes, a write answers 202 with status: "pending_review" and waits for a person in the dashboard; otherwise it is live at once. A personal key is recorded as the page's author; an organization key writes unattributed. Pages written this way are read by your Agents on later runs, so treat content copied from the web or from messages as untrusted before saving it.
The changes feed is keyed by when a row changed, not by any date inside a document, and pages with an opaque cursor, so a recurring job can call it first and process only what is new. The guide Use FormWise as a data layer shows the same loop from Claude, ChatGPT, Claude Code, Cursor and a custom agent.
Search a knowledgebase
Documents, pages and memories in one call, with citations.
List changes
What was added or updated since you last looked.
Get a page
Markdown body, attachments and links.
Create a page
File notes for your Agents, under review mode.
OpenAPI specification
The full machine-readable spec is available at
https://builder.formwise.ai/api/v1/openapi.json --
use it to generate typed clients or import the API into your tooling. The
reference pages below are generated from the same spec.
The surface covers the full lifecycle: create or update by email (adding plans and groups is a re-POST -- it's idempotent), inspect and list, adjust bonus credits, and take access away again -- remove a plan, remove the person from a group, or revoke the whole identity. Access comes from groups: give a group access once, then pass its id in groupIds when you provision someone. Nothing is emailed unless you send sendEmail: true.
Per-user grants are deprecated
The grants field on POST /end-users and the remove-grant endpoint belong to the older per-user access model. Organizations that manage access with groups refuse grants with 400 grants_unsupported. Use groupIds instead.
Create or update an end user
Provision accounts with plans and groups.
Get an end user
Identity and subscriptions with credit usage.
Revoke access
Idempotent identity revoke; re-POST restores.
Remove a plan
Delete a subscription and its credit pool.
Errors
Error envelope, codes, and rate limits.
Running Apps
Rolling out
Apps arrive one workspace at a time. Until they are enabled for your organization, the /apps endpoints answer 403 api_disabled.
Published Apps are callable over the API - the same composition your Portal serves, pinned at publish. Every App exposes up to two services:
- Conversation Service - unstructured chat. Send a message, get the Supervisor's synthesized reply. Pass the returned
conversation_idon later calls and prior turns are restored server-side, so you never resend history. Setstream: truefor Server-Sent Events. - Transaction Service - machine-to-machine, typed JSON in and out. Input is validated against the App's published input schema before anything runs; the reply is validated against the output schema before you get it. It is off by default and has no setting in the App editor: turn it on, and set the input and output schemas, with
PATCH /v1/apps/{id}/draft(api.transactionEnabled,api.inputSchema,api.outputSchema), then publish.
# Discover what's callable
curl https://builder.formwise.ai/api/v1/apps \
-H "Authorization: Bearer fw_live_..."
# Talk to an App
curl -X POST https://builder.formwise.ai/api/v1/apps/<app-uuid>/conversation \
-H "Authorization: Bearer fw_live_..." \
-H "Content-Type: application/json" \
-d '{ "message": "What should I focus on this quarter?" }'
# Typed execution
curl -X POST https://builder.formwise.ai/api/v1/apps/<app-uuid>/transaction \
-H "Authorization: Bearer fw_live_..." \
-H "Content-Type: application/json" \
-d '{ "input": { "revenue": 120000, "headcount": 8 } }'Services are enabled per App on its editor's API tab; the config is pinned into the published version, so republish the App to change the live contract. Each call runs and bills as one execution against your organization's credits.
List published Apps
Discovery: services and schemas per App.
Conversation Service
Chat with the Supervisor; multi-turn via conversation_id.
Transaction Service
Schema-validated JSON in, schema-validated JSON out.
User groups
Groups let you manage access in cohorts: create a group, grant it access, and every member inherits those grants. The full lifecycle is API-manageable, so you can mirror cohorts from your own systems -- a CRM segment, a course roster, a team on your side. (The dashboard's Groups panel manages the same groups.)
Rolling out
User groups arrive one workspace at a time. Until they are enabled for your organization, the /user-groups and /portals/{id}/admissions endpoints answer 403 groups_disabled.
# Create the group
curl -X POST https://builder.formwise.ai/api/v1/user-groups \
-H "Authorization: Bearer fw_live_..." \
-H "Content-Type: application/json" \
-d '{ "name": "Beta testers" }'
# Grant it access to a Suite (or an app)
curl -X POST https://builder.formwise.ai/api/v1/user-groups/<group-uuid>/grants \
-H "Authorization: Bearer fw_live_..." \
-H "Content-Type: application/json" \
-d '{ "targetKind": "suite", "targetId": "<suite-uuid>" }'
# Add a member (ids come from the end-users endpoints)
curl -X POST https://builder.formwise.ai/api/v1/user-groups/<group-uuid>/members \
-H "Authorization: Bearer fw_live_..." \
-H "Content-Type: application/json" \
-d '{ "endUserId": "<end-user-uuid>" }'Group grants are additive-only: they always allow, and there is no deny. To take access away from one person, remove them from the group. Adding a member emails them an invite to sign in; send "sendEmail": false to add them quietly. A plan's group can't be joined directly (422 plan_group): people join it by holding the plan, so assign the plan with tierPlanIds on POST /end-users. Membership and grant calls are idempotent: adding an existing member (or an identical grant) succeeds with added: false / created: false, and removing a non-member succeeds too. Removing a member, a grant, or the whole group only takes away access that flowed through the group -- the person's account and plans are untouched.
Create a user group
Start a cohort, then grant it access and add members.
List user groups
Every group with its member count.
Get a user group
The group plus its member roster and grants.
Grant a group access
Idempotent, allow-only; members inherit immediately.
Add a member
The end user inherits the group's grants immediately.
Remove a member
Group-granted access stops applying; the account is untouched.
Portals and admissions
A Portal is the door your end users sign in through. A Portal with a storefront attached admits any signed-in identity; one without admits only the groups you admit to it, and a Portal with neither is a door nobody can open. GET /portals reports each Portal's door as open, restricted or closed, and the admissions endpoints finish the provisioning chain: create the user, put them in a group, admit the group at the Portal.
# Admit a group at a Portal (idempotent)
curl -X POST https://builder.formwise.ai/api/v1/portals/<portal-uuid>/admissions \
-H "Authorization: Bearer fw_live_..." \
-H "Content-Type: application/json" \
-d '{ "groupId": "<group-uuid>" }'