Formaly Developer Documentation
Formaly is API-first and agent-native. Every form can be created, read, published, answered, and analyzed by software - over a REST API, over the Model Context Protocol, from a JavaScript SDK, or inside an iframe embed. This is the index of Formaly's developer resources.
Formaly developer resources
| Resource | URL | What it is |
|---|---|---|
| Formaly REST API reference | /docs/api | Every /api/v1 endpoint, with parameters and error codes |
| Formaly OpenAPI spec | /openapi.json | OpenAPI 3.1 description of the public REST API |
| Formaly MCP server | /docs/mcp | Model Context Protocol endpoint and tool catalogue |
| Formaly agent guide | /AGENTS.md | When and how an autonomous agent should call Formaly |
| Formaly llms.txt | /llms.txt | Machine-readable site summary and endpoint index |
| Formaly JavaScript SDK | formaly-sdk on npm | In-product surveys, also served at /formaly-sdk.js |
| Formaly embed script | /embed.js | Drop-in iframe embed for any website |
Authentication
The public API and the MCP server share one credential: a Formaly API key. Create keys in the dashboard under Settings → API Keys. A key looks like fml_<prefix>_<secret>; only a SHA-256 hash is stored, so the secret is shown once at creation and cannot be recovered afterwards.
Send the key as a bearer token on every request. Session cookies authenticate the dashboard only - they are not accepted on /api/v1.
curl https://www.formaly.io/api/v1/forms \
-H "Authorization: Bearer fml_abc123_your_secret_here"Scopes
Each key carries an explicit set of scopes. A request without the required scope fails with FORBIDDEN (403), which is not retryable - it needs a different key, not a second attempt.
| Scope | Grants |
|---|---|
forms:read | List and read forms, read the form schema, read credit balance |
forms:write | Create, update, publish, and delete forms |
analytics:read | Read responses and analytics |
Rate limits
Every API response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. When a limit is exceeded the request fails with RATE_LIMITED (429) and a Retry-After header - back off for that many seconds rather than retrying immediately.
Errors
Every failure from the Formaly API is a JSON object with a stable machine-readable code. Branch on code; show message to a human; read details for per-field validation errors.
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request body is invalid. See details for specific fields.",
"status": 400,
"details": { "script.questions": "Script must include at least one question." }
}
}| Code | Status | Meaning |
|---|---|---|
VALIDATION_ERROR | 400 | The request body or query is malformed. Fix and retry. |
UNAUTHORIZED | 401 | Missing, malformed, or revoked API key. |
INSUFFICIENT_CREDITS | 402 | The account is out of credits. The user must top up. |
FORBIDDEN | 403 | The key lacks the required scope. Not retryable. |
NOT_FOUND | 404 | No such form, or it belongs to another account. |
CONFLICT | 409 | expected_version did not match. Re-read and retry. |
RATE_LIMITED | 429 | Too many requests. Honour Retry-After. |
INTERNAL_ERROR | 500 | Something broke on our side. Retry with backoff. |
Safe writes
Two mechanisms make write operations safe to retry, which matters most when the caller is an autonomous agent.
- Idempotency.
POST /api/v1/formsaccepts anIdempotency-Keyheader. Send a UUID with every create; a retry with the same key returns the original form instead of making a duplicate. - Optimistic concurrency.
PATCH /api/v1/forms/{id}acceptsexpected_version(from a prior read) in the body, or the same value as anIf-Matchheader. A mismatch fails withCONFLICT(409) rather than silently overwriting someone else's edit.
Embedding and in-product surveys
Any published form has an embeddable URL at /embed/{id} which sets frame-ancestors *, so it can be iframed from any origin. For a drop-in script, /embed.js renders the iframe and handles sizing.
For surveys shown inside your own product - triggered on an event, targeted at a user segment - use the formaly-sdk npm package, or load the CDN bundle from /formaly-sdk.js.
<script src="https://www.formaly.io/embed.js" data-formaly-id="YOUR_FORM_ID" async></script>Webhooks
Formaly can POST to your endpoint when a response is submitted or a form is closed. Configure webhook endpoints per form in the dashboard. Deliveries are signed and retried with exponential backoff; the delivery log in the dashboard shows every attempt and lets you replay one.