Skip to main content

Management API

List and manage your forms, responses, and respondents from your own backend. The API lives under /api/v1/manage, authenticates with a scoped project secret key, and returns keyset-paginated JSON. The complete contract is available as openapi.json.

Authentication

Create a key under Settings → Connections → API keys. The plaintext is shown once; Fillo stores only its hash. Send it as a bearer token on every request:

Authorization header
curl https://fillo.so/api/v1/manage/forms \
  -H "Authorization: Bearer fsk_your_key_here"

Scopes

Every key stores an explicit set of scopes. A request for a scope the key lacks returns 403 with a stable message that names the missing scope. Mint keys in the dashboard or with fillo keys create --preset read|agent|full: read is the three read scopes, agent adds forms:write, forms:publish, and responses:export, and full is every scope except the irreversible delete tier. Delete scopes are never part of a preset — name them explicitly to grant them.

ScopeGrants
forms:readList forms and versions; read a form's published schema and settings.
responses:readList, read, and summarize responses, including one respondent's history. Insights quotes answers back, so it needs this as well as forms:read.
responses:exportExport a form's responses as CSV.
respondents:readLook up living respondent profiles.
forms:writeCreate and edit a form's schema; rename, duplicate, unpublish, discard staged changes, and change the settings that live in the schema (submit copy, redirect, progress bar).
forms:publishTake a form's staged changes live.
storage:manageConnect and disconnect the workspace's upload destinations, and choose where a form's uploads land (including its Google Drive folder).
webhooks:manageList, add, update, and remove a form's signed webhooks.
settings:manageRead and patch a form's operational settings. The six presentation keys live in the schema and additionally need forms:write.
integrations:manageTurn a form's Sheets, Notion, HubSpot, or Slack destination on or off, choose the account a project uses, and manage connected accounts.
responses:manageRead held submissions and drafts, release held submissions, and retry or re-send deliveries.
members:manageList members and invitations, change a role, and remove a member.
workspace:manageRename the workspace or project and manage sync tokens, connector tokens, code-sync policy, allowed origins, identity verification, and agent access.
forms:deleteDelete a form with its responses and files.
responses:deleteDelete a response and its uploaded files.
respondents:deleteForget a respondent, optionally with every response they submitted.
workspace:deleteSchedule the workspace itself for deletion.

Endpoints

A form path segment accepts a form id or slug. Any id outside the key’s project returns 404 (never 403), so the API never reveals whether an inaccessible id exists.

Everything a workspace member can do in the dashboard is here, including the outward and the irreversible actions. On this API the scope is the consent: whoever minted the key chose it. Irreversible operations ask for one thing more — a body confirm field naming the exact target: a member's email address, a token, grant, or server id, an account's exact name, a respondent's external id, or the literal identity. A value that does not match is a 409 and nothing changes, and a boolean true is rejected outright, so a bare confirmation can never stand in for a typed one.

MethodPathScopeSummary
GET/api/v1/manage/formsforms:readList the project's forms.
GET/api/v1/manage/forms/{form}forms:readA form's published schema, settings, and hasDraft. ?include=draft adds the staged revision and needs forms:write.
PATCH/api/v1/manage/forms/{form}forms:writeRename a form; old links keep working.
POST/api/v1/manage/forms/{form}/unpublishforms:writeTake a live form offline. Responses and files are kept.
POST/api/v1/manage/forms/{form}/discardforms:writeDrop a published form's staged changes; the live form stays.
POST/api/v1/manage/forms/{form}/duplicateforms:writeCopy a form into the same project as a fresh draft. A form whose deletion has started is 410.
GET/api/v1/manage/forms/{form}/versionsforms:readThe form's stored schema versions.
GET/api/v1/manage/forms/{form}/storagestorage:manageWhere this form's uploads land, and what they resolve to now.
PUT/api/v1/manage/forms/{form}/storagestorage:managePin the form's upload destination (or clear it).
GET/api/v1/manage/forms/{form}/storage/folderstorage:manageThe selected Google Drive folder plus the account's writable folders.
PUT/api/v1/manage/forms/{form}/storage/folderstorage:manageSend new uploads to a Drive folder; existing files stay put.
DELETE/api/v1/manage/forms/{form}/storage/folderstorage:manageReturn to the automatic per-form Drive folder.
GET/api/v1/manage/forms/{form}/settingssettings:manageThe form's operational settings.
PATCH/api/v1/manage/forms/{form}/settingssettings:managePatch one or more operational settings. The six presentation keys also need forms:write.
GET/api/v1/manage/forms/{form}/webhookswebhooks:manageList the form's webhooks (never the secret).
POST/api/v1/manage/forms/{form}/webhookswebhooks:manageAdd a webhook; the signing secret is returned once.
PATCH/api/v1/manage/forms/{form}/webhooks/{id}webhooks:manageUpdate a webhook's events or write-only receiver authentication.
DELETE/api/v1/manage/forms/{form}/webhooks/{id}webhooks:manageDelete a webhook.
GET/api/v1/manage/forms/{form}/responsesresponses:readA keyset page of the form's responses. ?held=1 returns the withheld queue instead and also needs responses:manage.
GET/api/v1/manage/forms/{form}/responses/exportresponses:exportStream the form's responses as a CSV download.
GET/api/v1/manage/forms/{form}/responses/summaryresponses:readTotals, per-field answer rates, and choice distributions.
POST/api/v1/manage/forms/{form}/responses/releaseresponses:manageAccept held submissions; released responses get delivered.
GET/api/v1/manage/forms/{form}/draftsresponses:manageWho is mid-fill, and what they have typed so far.
GET/api/v1/manage/forms/{form}/insightsforms:read + responses:readTotals, median completion time, the journey, and per-question analysis. The per-question breakdowns quote answers, so this costs both scopes.
GET/api/v1/manage/responses/{id}responses:readOne response with its file references.
DELETE/api/v1/manage/responses/{id}responses:deleteDelete a response and its files.
GET/api/v1/manage/forms/{form}/deliveriesresponses:managePer-destination delivery health and the recent outbox. Destinations are named, not spelled out: webhook target URLs stay in the dashboard.
POST/api/v1/manage/forms/{form}/deliveries/retryresponses:manageRepair failed deliveries by response, destination, row, or all.
POST/api/v1/manage/forms/{form}/deliveries/redeliverresponses:manageSend responses to every current destination again.
GET/api/v1/manage/forms/{form}/integrations/{provider}integrations:manageWhether this form sends to google_sheets, notion, slack, or hubspot.
PUT/api/v1/manage/forms/{form}/integrations/{provider}integrations:manageStart sending this form's responses there, or change how.
DELETE/api/v1/manage/forms/{form}/integrations/{provider}integrations:manageStop sending; records already created stay where they are.
GET/api/v1/manage/integrations/connectionsintegrations:manageThe workspace's provider accounts and which one this project uses.
PUT/api/v1/manage/integrations/connections/{provider}integrations:managePoint this project at one of those accounts.
DELETE/api/v1/manage/integrations/connections/{provider}integrations:manageDetach this project's selected account. Other projects keep theirs.
DELETE/api/v1/manage/integrations/accounts/{id}integrations:manage + workspace:manageRemove an account from the whole workspace. confirm = its exact name; the key acts as the person who minted it.
PATCH/api/v1/manage/integrations/discord/accounts/{id}integrations:manageName (or un-name) one connected Discord channel.
DELETE/api/v1/manage/integrations/discord/servers/{guildId}integrations:manage + workspace:manageDisconnect a Discord server from the whole workspace. confirm = the server id; the key acts as the person who minted it.
GET/api/v1/manage/integrations/hubspot/propertiesintegrations:manageWritable HubSpot Contact properties you can map onto.
GET/api/v1/manage/integrations/hubspot/pipelinesintegrations:manageHubSpot Deal pipelines and their stage ids.
GET/api/v1/manage/respondentsrespondents:readLook up respondents by externalId or email.
GET/api/v1/manage/respondents/{id}/responsesrespondents:read + responses:readOne person's response history.
DELETE/api/v1/manage/respondents/{id}respondents:deleteForget a respondent. confirm = their external id.
PATCH/api/v1/manage/workspaceworkspace:manageRename the workspace.
PATCH/api/v1/manage/projectworkspace:manageRename the key's project.
GET/api/v1/manage/project/code-syncworkspace:manageThe code-sync policy in force for this project.
PATCH/api/v1/manage/project/code-syncworkspace:manageChoose who may stage code-defined schemas.
GET/api/v1/manage/project/originsworkspace:manageThe origins the publishable key may sync from.
PUT/api/v1/manage/project/originsworkspace:manageReplace that allow-list; an empty array means any origin.
GET/api/v1/manage/project/identityworkspace:manageIdentity-verification state and how many forms depend on it.
POST/api/v1/manage/project/identityworkspace:manageMint the identity secret; it is returned exactly once.
DELETE/api/v1/manage/project/identityworkspace:manageTurn verification off. confirm = the project slug.
GET/api/v1/manage/membersmembers:manageWorkspace members and pending invitations.
POST/api/v1/manage/members/invitesmembers:manageInvite someone by email; role defaults to member.
DELETE/api/v1/manage/members/invites/{id}members:manageCancel a pending invitation.
PATCH/api/v1/manage/members/{id}members:manageChange a member's role.
DELETE/api/v1/manage/members/{id}members:manageRemove a member. confirm = their email address.
GET/api/v1/manage/tokensworkspace:manageConnector tokens on this project, metadata only.
DELETE/api/v1/manage/tokens/{id}workspace:manageRevoke a connector token. confirm = the token id.
GET/api/v1/manage/sync-tokensworkspace:manageThe project's form sync tokens.
POST/api/v1/manage/sync-tokensworkspace:manageMint a stage-only sync token; shown exactly once.
DELETE/api/v1/manage/sync-tokens/{id}workspace:manageRevoke a sync token. confirm = the token id.
GET/api/v1/manage/agentsworkspace:manageMCP clients authorized on this project.
DELETE/api/v1/manage/agents/{id}workspace:manageDisconnect one MCP client. confirm = the grant id.

A response row looks like this:

response object
{
  "id": "resp_…",
  "formId": "form_…",
  "data": { "fld_score": 9 },
  "meta": { "source": "app.example.com/feedback", "respondent": { "id": "user_42", "verified": true } },
  "formVersionId": "ver_…",
  "createdAt": "2026-07-13T10:00:00.000Z",
  "updatedAt": null
}

Pagination and filters

List endpoints return { data, nextCursor }. Pass nextCursor back as cursor for the next page; a null cursor means the last page. limit is 1–100 (default 50). Response filters use the same grammar as the responses grid and CSV export — range, q, source, respondent, and a repeatable where=fieldId:op:value.

Accepted responses only by default. The withheld queue is reachable through held=1, which swaps the rows rather than mixing them into a page and needs responses:manage on top of responses:read.

list + filter + paginate
# Filter + walk pages. 'where' is repeatable as fieldId:op:value.
curl -G "https://fillo.so/api/v1/manage/forms/FORM_ID/responses" \
  -H "Authorization: Bearer $FILLO_KEY" \
  --data-urlencode "range=30d" \
  --data-urlencode "where=fld_score:gt:8" \
  --data-urlencode "limit=100"

# → { "data": [], "nextCursor": "resp_abc" }
# Next page: add --data-urlencode "cursor=resp_abc"
delete a response
curl -X DELETE "https://fillo.so/api/v1/manage/responses/RESPONSE_ID" \
  -H "Authorization: Bearer $FILLO_KEY"
# → { "id": "RESPONSE_ID", "deleted": true }

Versioning and deprecation

The public contract is versioned in the URL under /api/v1. Compatible additions can ship within v1; a breaking request or response change requires a new URL version. No v1 operation is currently deprecated. If that changes, the affected response will carry the RFC 9745 Deprecation date, an RFC 8594 Sunset date when removal is scheduled, and a rel="deprecation" link to migration instructions. The changelog and OpenAPI contract will name the replacement before the sunset date.

Errors and rate limits

Errors are a stable { "error": "…" } body with the matching status. Every response advertises its quota with RateLimit-Policy; RateLimit-Limit supports clients using the earlier convention. Auth responses with current limiter state also include RateLimit, RateLimit-Remaining, RateLimit-Reset, and an authoritative Retry-After on a 429. Treat Retry-After as authoritative.

StatusMeaning
400Malformed request — a body on a GET/DELETE, an unknown key, or a missing parameter.
401Missing, malformed, unknown, revoked, or expired key.
403The key is valid but lacks the required scope.
404No such resource in this project — also returned for ids in other projects.
409Refused in this state — an unmet precondition, or a confirm value that did not match. Nothing changed.
413The request body exceeded the 16 KiB management cap.
429Per-key (120/min) or per-workspace (240/min) rate limit; honor Retry-After.

Remote MCP

The same scopes back Fillo’s remote MCP server at /api/mcp (Streamable HTTP, stateless): connect a hosted MCP client to run forms and read responses over OAuth instead of pasting a bearer key. It uses OAuth 2.1 with PKCE and Dynamic Client Registration, discoverable at /.well-known/oauth-protected-resource. Consent is shown in product language with the read-only scopes pre-selected; approving mints a backing project key that appears in your keys list as MCP: <client> and is revocable there. When a grant asks for per-publish approval, the publish tool returns an approval URL instead of publishing directly. See the MCP server docs for setup, tools, and the local stdio server.

This page for agents: /docs/api.md · index at /llms.txt

Updated

Was this page helpful?