Skip to main content
Menu
On this page

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 workspace 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.
responses:exportExport a form's responses as CSV.
respondents:readLook up living respondent profiles.
webhooks:manageList, add, update, and remove a form's signed webhooks.
settings:manageRead and patch a form's operational settings.
responses:deleteDelete a response and its uploaded files.

Endpoints

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

MethodPathScopeSummary
GET/api/v1/manage/formsforms:readList the workspace's forms.
GET/api/v1/manage/forms/{form}forms:readA form's published schema, settings, and hasDraft.
GET/api/v1/manage/forms/{form}/versionsforms:readThe form's stored schema versions.
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.
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:manageToggle a webhook's abandoned-draft delivery.
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.
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.
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/respondentsrespondents:readLook up respondents by externalId or email.
GET/api/v1/manage/respondents/{id}/responsesrespondents:read + responses:readOne person's response history.

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. limitis 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. Withheld submissions are never returned; the API sees accepted responses only.

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 }

Errors and rate limits

Errors are a stable { "error": "…" } body with the matching status. Rate-limit responses carry a Retry-After header in seconds.

StatusMeaning
400Malformed request — a body on a GET/DELETE, or a missing required parameter.
401Missing, malformed, unknown, revoked, or expired key.
403The key is valid but lacks the required scope.
404No such resource in this workspace — also returned for ids in other workspaces.
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 workspace 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.

Updated

Was this page helpful?