Docs
API Reference

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/v1

Availability

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 keyPersonal key
Who creates itAn admin, in Settings → APIAny member, for themselves, in Settings → API
Acts asThe organization (no person attached)That member. Runs and changes are recorded against them
ScopesAnyCapped by the member's role, re-checked on every call
When it stops workingWhen revokedWhen revoked, or when the member leaves the organization
MCP serverNot acceptedAccepted

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

  1. Open Settings → API in the dashboard.
  2. Name the key (for example "Production - Zapier") and choose its scopes.
  3. 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

ScopeGrantsPersonal keys: minimum role
agents:readList published AgentsViewer
agents:executeTalk to Agents through the Conversation ServiceViewer
agents:writeCreate and edit Agents, publish versionsMember
flows:readList published Flows and their inputsViewer
flows:executeRun FlowsViewer
flows:writeCreate and edit Flows, publish versionsMember
apps:readList published Apps and their service contractsViewer
apps:executeRun Apps through the Conversation and Transaction servicesViewer
apps:writeCreate and edit Apps, publish versionsMember
knowledge:readList, search and read Knowledgebases: documents, pages and (personal keys) your own memoriesViewer
knowledge:writeCreate Knowledgebases (within your plan's limit), create, edit, append to and move pages, and upload documentsMember
end_users:readList and read end users, user groups, portals, and the plan catalogAdmin
end_users:writeCreate and update end users and user groups; admit groups at portalsAdmin

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-Id header. Include it when contacting support about a specific call.
  • Errors use one stable envelope: { "error": { "code": "...", "message": "..." } }. Branch on code; message is human-readable text and may change. See Errors for the full code table.
  • Creates take an Idempotency-Key header (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/apps and POST /v1/end-users. A retry with the same key within 24 hours returns the first response again (with Idempotent-Replayed: true) instead of creating twice; the same key with a different body is 409 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.

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.

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.

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.

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_id on later calls and prior turns are restored server-side, so you never resend history. Set stream: true for 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.

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.

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>" }'

On this page