API reference
Endpoint
Section titled “Endpoint”POST https://app.formkove.com/api/forms/{form_id}/submissions
Replace {form_id} with your form ID from the Formkove dashboard. The form ID in the URL is all you need. There’s no separate access key.
Content Types
Section titled “Content Types”Content-Type |
Typical use |
|---|---|
application/x-www-form-urlencoded |
A plain HTML <form method="POST"> |
multipart/form-data |
fetch(url, { body: new FormData(form) }) |
application/json |
fetch() with JSON.stringify(...) |
Send a flat object of fields. Any field names work. Nested objects are stored as JSON strings.
File uploads are not supported. File inputs are not stored.
JSON Mode vs HTML Mode
Section titled “JSON Mode vs HTML Mode”Formkove picks the response style from your request:
- JSON mode: when
Content-Typeisapplication/jsonor theAcceptheader includesapplication/json. You get a JSON body and a status code (below). Redirects are never used. - HTML mode: everything else (a normal browser form post). Formkove answers with a
303redirect: to your?redirect=URL, or to its own success page. Errors redirect to a Formkove error page.
Special Fields
Section titled “Special Fields”| Field | Description |
|---|---|
email / replyto / reply_to / reply-to / _replyto |
The visitor’s email. Used as the Reply-To of notification emails. The first non-empty valid email wins. |
| Your honeypot field | The field name you configured under Settings → Honeypot. If it has any value, Formkove pretends to accept the submission but stores nothing. |
cf-turnstile-response |
The Turnstile token, required when Turnstile is enabled. Stripped before storage. |
The notification email subject is always New Submission for "[Form Name]" and can’t be overridden.
Query Parameters
Section titled “Query Parameters”| Parameter | Description |
|---|---|
redirect |
HTML mode only. Where to send the visitor after success. Must be a full URL whose domain is in the form’s Allowed Origins; anything else falls back to Formkove’s success page. See Success Pages and Redirects. |
Headers
Section titled “Headers”| Header | Description |
|---|---|
Accept: application/json |
Forces JSON mode, even for FormData bodies |
Idempotency-Key |
Optional, any unique string per submission attempt. If a request with the same key already succeeded on this form within 24 hours, Formkove returns the original response instead of creating a duplicate. Use it when retrying after a network error. |
Example Request
Section titled “Example Request”const res = await fetch('https://app.formkove.com/api/forms/YOUR_FORM_ID/submissions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Idempotency-Key': crypto.randomUUID(), }, body: JSON.stringify({ name: 'Jane Smith', message: 'Hi, I would like to schedule a demo.', }),});const json = await res.json();Responses (JSON mode)
Section titled “Responses (JSON mode)”Every JSON response has a success boolean. Errors include an error message.
201 Created
Section titled “201 Created”{ "success": true, "id": "550e8400-e29b-41d4-a716-446655440000"}Errors
Section titled “Errors”| Status | error |
When |
|---|---|---|
400 |
Invalid submission body |
The body can’t be parsed, or isn’t a flat object |
400 |
Too many fields or a field value is too large |
More than 100 fields, or a field over 10 KB |
403 |
Security verification failed |
Turnstile is enabled and the token is missing or invalid |
403 |
Submission limit reached |
The workspace’s monthly submission limit is used up |
404 |
Form not found |
The form ID is wrong or the form was deleted |
409 |
Request already processing |
Another request with the same Idempotency-Key is still in flight |
410 |
This form has been disabled |
The form was disabled for abuse |
413 |
Payload too large |
Body over 100 KB |
415 |
Unsupported content type |
Not JSON, urlencoded, or multipart |
429 |
Too many submissions. Please try again later. |
Rate limit exceeded (see below) |
500 / 503 |
Failed to process submission / Service temporarily unavailable… |
Temporary server problem. Safe to retry with the same Idempotency-Key. |
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Request body | 100 KB |
| Fields per submission | 100 |
| Size per field | 10 KB |
| Rate limit (per IP, per form) | 10 per minute, 30 per hour |
429 responses include Retry-After and X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers. Successful JSON responses include the X-RateLimit-* headers too.
Monthly submission limits depend on your plan. See Pricing.
Cross-Origin Requests (CORS)
Section titled “Cross-Origin Requests (CORS)”Browser fetch() calls work directly, with no proxy, once your site’s origin is in the form’s Allowed Origins (Settings → Allowed Origins & Redirects). Enter full origins like https://example.com, or a bare domain like example.com to include its subdomains. Until you add it, the browser blocks the response (and, for JSON requests, the request itself).
Plain HTML form posts and server-to-server requests aren’t affected by CORS.
Outgoing Webhooks
Section titled “Outgoing Webhooks”For the payload Formkove sends to your endpoints and how to verify signatures, see Zapier and Webhooks.
For a complete fetch-based implementation, see JavaScript Form Submission.