Custom API Integration
For workflows that go beyond Zapier or webhooks, you can interact with WittyForm programmatically using the REST API. This guide walks through the most common scenarios with working code examples.
When to use the API vs webhooks vs Zapier
| Method | Best for | Direction |
|---|---|---|
| Zapier | No-code workflows, quick setup | WittyForm → other apps |
| Webhooks | Real-time push to your server | WittyForm → your server |
| REST API | Pull data on demand, create submissions, full CRUD | Your server ↔ WittyForm |
Use the REST API when you need to pull data on your own schedule, create submissions from an external system, or build a custom dashboard on top of WittyForm data.
Getting an API key
- Go to Dashboard → Settings.
- Locate the API section and click Generate key.
- Copy the key immediately: it is only shown once.
Store your API key in an environment variable. Never commit it to version control.
Base URL
https://wittyform.com/api/v1Authentication header
Include your API key as a Bearer token in every request (or use the X-API-Key header instead). API access requires a Pro or higher plan; on the Free tier requests return a 403 with the code PLAN_REQUIRED. Do not pass the key in the URL: keys supplied as a query parameter are rejected with a 400 (KEY_INVALID).
Authorization: Bearer wf_live_abc123def456Example: Fetch form responses with curl
curl -X GET "https://wittyform.com/api/v1/forms/b3f1c2d4-1a2b-4c3d-9e8f-001122334455/responses?page=1&per_page=25" \
-H "Authorization: Bearer wf_live_abc123def456" \
-H "Content-Type: application/json"Every successful response uses the envelope { "success": true, "data": { ... } }:
{
"success": true,
"data": {
"responses": [
{
"id": "e6a4f5b7-4d5e-4f70-91b2-334455667788",
"form_id": "b3f1c2d4-1a2b-4c3d-9e8f-001122334455",
"answers": {
"full_name": "Jane Smith",
"email": "[email protected]",
"message": "Hello!"
},
"submitted_at": "2025-09-15T14:32:07.000Z"
}
],
"pagination": {
"page": 1,
"per_page": 25,
"total": 142,
"total_pages": 6
}
}
}Example: Create a submission via API
You can programmatically submit data to any form. This is useful for migrating data from another platform or submitting from a server-side process.
curl -X POST "https://wittyform.com/api/v1/forms/b3f1c2d4-1a2b-4c3d-9e8f-001122334455/responses" \
-H "Authorization: Bearer wf_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"answers": {
"full_name": "John Doe",
"email": "[email protected]",
"message": "Submitted via API"
}
}'The answers object is keyed by field ID, and you must send Content-Type: application/json (other content types return a 415). The form must be published. Response (201 Created):
{
"success": true,
"data": {
"response": {
"id": "a8c6b7d9-6f70-4192-b3d4-556677889900",
"form_id": "b3f1c2d4-1a2b-4c3d-9e8f-001122334455",
"answers": {
"full_name": "John Doe",
"email": "[email protected]",
"message": "Submitted via API"
},
"metadata": { "source": "api" },
"submitted_at": "2025-09-15T15:00:00.000Z"
}
}
}JavaScript fetch example
const response = await fetch(
'https://wittyform.com/api/v1/forms/b3f1c2d4-1a2b-4c3d-9e8f-001122334455/responses?page=1&per_page=50',
{
headers: {
'Authorization': 'Bearer ' + process.env.WITTYFORM_API_KEY,
'Content-Type': 'application/json',
},
}
);
// Successful responses are wrapped in { success, data }.
const { data } = await response.json();
const { responses, pagination } = data;
console.log(`Page ${pagination.page} of ${pagination.total_pages}`);
responses.forEach((submission) => {
console.log(submission.answers.email);
});Pagination
All list endpoints return paginated results. Use these query parameters:
page: page number (default: 1)per_page: results per page (default: 25, max: 100)
The response includes a pagination object with total and total_pages so you can iterate through all results.
Error handling
Errors use the same envelope as success, with success: false and an error object holding a stable code and a human-readable message (some errors also include an error.detail):
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Form not found"
}
}| Status | Code | Meaning |
|---|---|---|
401 | UNAUTHENTICATED / KEY_INVALID / KEY_EXPIRED | Missing, invalid, or expired API key |
403 | INSUFFICIENT_SCOPE / PLAN_REQUIRED / RESPONSE_LIMIT_REACHED | The key lacks the scope, the plan does not include the API, or a limit was hit |
404 | NOT_FOUND | The resource does not exist |
415 | UNSUPPORTED_MEDIA_TYPE | The request was not sent as application/json |
422 | VALIDATION_ERROR / FORM_NOT_PUBLISHED / FORM_CLOSED | The body failed validation, or the form cannot accept the response |
429 | RATE_LIMITED | Slow down and retry after the Retry-After window |
500 | INTERNAL_ERROR | Server error: retry with exponential backoff |
Rate limits
API requests are rate limited per API key. See the Rate Limits guide for full details on limits by plan and how to handle 429 responses.