Custom API Integration

6 min read

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

MethodBest forDirection
ZapierNo-code workflows, quick setupWittyForm → other apps
WebhooksReal-time push to your serverWittyForm → your server
REST APIPull data on demand, create submissions, full CRUDYour 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

  1. Go to Dashboard → Settings.
  2. Locate the API section and click Generate key.
  3. 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/v1

Authentication 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_abc123def456

Example: 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"
  }
}
StatusCodeMeaning
401UNAUTHENTICATED / KEY_INVALID / KEY_EXPIREDMissing, invalid, or expired API key
403INSUFFICIENT_SCOPE / PLAN_REQUIRED / RESPONSE_LIMIT_REACHEDThe key lacks the scope, the plan does not include the API, or a limit was hit
404NOT_FOUNDThe resource does not exist
415UNSUPPORTED_MEDIA_TYPEThe request was not sent as application/json
422VALIDATION_ERROR / FORM_NOT_PUBLISHED / FORM_CLOSEDThe body failed validation, or the form cannot accept the response
429RATE_LIMITEDSlow down and retry after the Retry-After window
500INTERNAL_ERRORServer 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.

$37 · paid once

Ready to Build Forms That Actually Convert?

Start free and keep the five forms for good, or pay once and unlock all of it — unlimited forms, ten team members, AI, CRM, white label and the API. No renewal, ever.

14-day money-backInstant accessNo subscription, ever
Custom API Integration