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:
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.
| Scope | Grants |
|---|---|
| forms:read | List forms and versions; read a form's published schema and settings. |
| responses:read | List, read, and summarize responses, including one respondent's history. |
| responses:export | Export a form's responses as CSV. |
| respondents:read | Look up living respondent profiles. |
| webhooks:manage | List, add, update, and remove a form's signed webhooks. |
| settings:manage | Read and patch a form's operational settings. |
| responses:delete | Delete a response and its uploaded files. |
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.
| Method | Path | Scope | Summary |
|---|---|---|---|
| GET | /api/v1/manage/forms | forms:read | List the project's forms. |
| GET | /api/v1/manage/forms/{form} | forms:read | A form's published schema, settings, and hasDraft. |
| GET | /api/v1/manage/forms/{form}/versions | forms:read | The form's stored schema versions. |
| GET | /api/v1/manage/forms/{form}/settings | settings:manage | The form's operational settings. |
| PATCH | /api/v1/manage/forms/{form}/settings | settings:manage | Patch one or more operational settings. |
| GET | /api/v1/manage/forms/{form}/webhooks | webhooks:manage | List the form's webhooks (never the secret). |
| POST | /api/v1/manage/forms/{form}/webhooks | webhooks:manage | Add a webhook; the signing secret is returned once. |
| PATCH | /api/v1/manage/forms/{form}/webhooks/{id} | webhooks:manage | Update a webhook's events or write-only receiver authentication. |
| DELETE | /api/v1/manage/forms/{form}/webhooks/{id} | webhooks:manage | Delete a webhook. |
| GET | /api/v1/manage/forms/{form}/responses | responses:read | A keyset page of the form's responses. |
| GET | /api/v1/manage/forms/{form}/responses/export | responses:export | Stream the form's responses as a CSV download. |
| GET | /api/v1/manage/forms/{form}/responses/summary | responses:read | Totals, per-field answer rates, and choice distributions. |
| GET | /api/v1/manage/responses/{id} | responses:read | One response with its file references. |
| DELETE | /api/v1/manage/responses/{id} | responses:delete | Delete a response and its files. |
| GET | /api/v1/manage/respondents | respondents:read | Look up respondents by externalId or email. |
| GET | /api/v1/manage/respondents/{id}/responses | respondents:read + responses:read | One person's response history. |
A response row looks like this:
{
"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. Withheld submissions are never returned; the API sees accepted responses only.
# 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"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.
| Status | Meaning |
|---|---|
| 400 | Malformed request — a body on a GET/DELETE, or a missing required parameter. |
| 401 | Missing, malformed, unknown, revoked, or expired key. |
| 403 | The key is valid but lacks the required scope. |
| 404 | No such resource in this project — also returned for ids in other projects. |
| 429 | Per-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