Form Submissions
Public, throttled endpoints that let any frontend post a form submission without API credentials.
Endpoints
GET /api/v1/forms/{formSlug}
POST /api/v1/forms/{formSlug}/submit (throttle: 5/min/IP)
No API key required. The submit endpoint is rate-limited to 5 requests per minute per IP.
Get the form schema
Used by frontends to render the form dynamically.
GET /api/v1/forms/contact-us
200 OK
{
"data": {
"name": "Contact Us",
"slug": "contact-us",
"fields": [
{ "name": "name", "label": "Name", "type": "text", "required": true, "max": 120 },
{ "name": "email", "label": "Email", "type": "email", "required": true },
{ "name": "subject", "label": "Subject", "type": "select", "required": true,
"options": ["Sales", "Support", "Other"] },
{ "name": "message", "label": "Message", "type": "textarea", "required": true, "max": 5000 },
{ "name": "_company", "type": "honeypot" }
],
"success_message": "Thanks — we'll be in touch soon."
},
"error": null
}
Submit
POST /api/v1/forms/contact-us/submit
Content-Type: application/json
{
"name": "Ada Lovelace",
"email": "ada@example.com",
"subject": "Sales",
"message": "Tell me about your enterprise plan.",
"_company": ""
}
201 Created
{
"data": {
"id": "01H…",
"received_at": "2026-05-29T11:30:00Z",
"message": "Thanks — we'll be in touch soon."
},
"error": null
}
Spam & abuse
- Honeypot fields (any field type
honeypot) must be empty. Non-empty values are silently accepted but flagged asspam. - Rate limit: 5 successful submits per minute per IP. The 6th gets a
429. - If the form has reCAPTCHA configured, include the token under
g-recaptcha-response. - Common email blocklist / disposable address checks can be enabled per form.
Validation
Validation rules are derived from the form's field schema. A failed submit returns 422 with per-field details:
422 Unprocessable Entity
{
"data": null,
"error": {
"code": "VALIDATION_FAILED",
"message": "The given data was invalid.",
"details": {
"email": ["The email field must be a valid email address."],
"message": ["The message field is required."]
}
}
}
Inactive forms
If the form has is_active = false, the endpoint returns 410 Gone:
410 Gone
{ "data": null, "error": { "code": "FORM_INACTIVE", "message": "This form is no longer accepting submissions." } }
Side effects of a successful submit
- A row is created in
form_submissionswith statusnew. - Notification emails go out to every address in
notify_emails. - Any webhook subscribed to
form.submission.createdis delivered. - An
activity_logsentry is written (actor = anonymous, source = api).
Example: HTML form
<form id="contact" action="/api/v1/forms/contact-us/submit" method="post">
<input name="name" type="text" required />
<input name="email" type="email" required />
<select name="subject" required>
<option>Sales</option><option>Support</option><option>Other</option>
</select>
<textarea name="message" required></textarea>
<input name="_company" type="text" tabindex="-1" autocomplete="off"
style="position:absolute; left:-9999px" />
<button type="submit">Send</button>
</form>