Forms API
Authenticated Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /forms | List forms (query: status, search, page, limit) |
| GET | /forms/:id | Get form by ID |
| POST | /forms | Create form |
| PUT | /forms/:id | Update form |
| DELETE | /forms/:id | Soft delete form |
| POST | /forms/:id/duplicate | Duplicate form as draft |
| GET | /forms/:id/submissions | List submissions (query: page, limit, status, dateFrom, dateTo) |
| GET | /forms/:id/analytics | Submission analytics (stats, trends, field breakdowns) |
| POST | /forms/:id/submissions/:submissionId/retry-webhook | Retry failed webhook action |
Public Endpoints (No Auth)
| Method | Endpoint | Description |
|---|---|---|
| GET | /forms/public/:tenantSlug/:token | Get public form for rendering |
| POST | /forms/public/:tenantSlug/:token/submit | Submit form data |
Submission Request
The submission body is a flat JSON object keyed by each field's name (the name shown in the form builder's field settings). Custom fields use the cf_ prefix.
{
"firstName": "Jane",
"lastName": "Doe",
"email": "jane@example.com",
"phone": "+971501234567",
"cf_lead_score": 80,
"__recaptchaToken": "<v3 token>"
}
__recaptchaTokenis only required when the form'ssettings.requireCaptchais enabled.- Phone numbers are auto-normalised to E.164 server-side.
- The response includes the created entity IDs per chained submit action.
Sample Requests
cURL
curl -X POST 'https://api.example.com/api/forms/public/acme/abc123token/submit' \
-H 'Content-Type: application/json' \
-d '{"firstName":"Jane","email":"jane@example.com"}'
JavaScript (fetch)
const response = await fetch(
'https://api.example.com/api/forms/public/acme/abc123token/submit',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ firstName: 'Jane', email: 'jane@example.com' }),
},
);
const result = await response.json();
PHP (cURL)
$ch = curl_init('https://api.example.com/api/forms/public/acme/abc123token/submit');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'firstName' => 'Jane',
'email' => 'jane@example.com',
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$response = curl_exec($ch);
curl_close($ch);
Python (requests)
import requests
response = requests.post(
'https://api.example.com/api/forms/public/acme/abc123token/submit',
json={'firstName': 'Jane', 'email': 'jane@example.com'},
)
print(response.json())
Flutter (Dart, package:http)
import 'dart:convert';
import 'package:http/http.dart' as http;
final response = await http.post(
Uri.parse('https://api.example.com/api/forms/public/acme/abc123token/submit'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'firstName': 'Jane', 'email': 'jane@example.com'}),
);
tip
The form builder's Embed button (top bar of the builder) opens a "Share & Integrate" modal that generates these snippets pre-filled with the form's actual URL, token, and field keys — just copy and paste.
Form Object
{
"id": "uuid",
"name": "Contact Us",
"description": "Main contact form",
"status": "active",
"type": "standard",
"token": "abc123",
"tenantSlug": "acme",
"fields": [
{
"id": "field-1",
"type": "text",
"label": "Full Name",
"name": "full_name",
"required": true,
"width": "full"
}
],
"settings": {
"successMessage": "Thank you!",
"redirectUrl": null,
"allowMultiple": false,
"requireCaptcha": true,
"notifyEmails": ["admin@acme.com"]
},
"submitActions": [
{
"type": "create_lead",
"enabled": true,
"fieldMapping": {
"first_name": "full_name",
"email": "email_field"
}
}
],
"branding": {
"logoUrl": "https://...",
"primaryColor": "#7C3AED",
"backgroundColor": "#FFFFFF"
},
"submissionCount": 42,
"createdAt": "2026-01-15T...",
"updatedAt": "2026-03-19T..."
}
Field Types
| Type | Input | Has Options |
|---|---|---|
| text | Single-line text | No |
| Email with validation | No | |
| phone | Phone number | No |
| number | Numeric input | No |
| date | Date picker | No |
| textarea | Multi-line text | No |
| select | Dropdown | Yes |
| radio | Radio buttons | Yes |
| checkbox | Checkboxes | Yes |
| file | File upload | No |
| heading | Display only | No |
| paragraph | Display only | No |
| divider | Display only | No |
Field Width
The width property on each field controls how much of the row it occupies. Adjacent non-full fields auto-pack into the same row on the rendered form (12-column grid, mobile-responsive).
| Width | Fraction |
|---|---|
full | 1/1 (default) |
two-thirds | 2/3 |
half | 1/2 |
third | 1/3 |
quarter | 1/4 |
heading, paragraph, and divider fields always span the full row regardless of the width value.
Submit Action Types
| Type | Description | Config |
|---|---|---|
| create_lead | Create CRM lead | fieldMapping |
| create_contact | Create CRM contact | fieldMapping |
| create_account | Create CRM account | fieldMapping |
| webhook | POST to external URL | webhookUrl |
| send_email | Email to submitter | emailFieldName, subject, body |