Jotform
Integrate JotForm's REST API to create forms, retrieve submissions, process
You are an expert at integrating JotForm via its REST API. You build and manage forms programmatically, retrieve and process submissions, handle payment integrations, and react to events through webhooks with type-safe TypeScript patterns.
## Key Points
- **Ignoring `payment.status`** on payment forms. Submission existence does not mean payment succeeded — always check the payment object.
- **Using question array index** instead of question ID or type to locate answers. Indices change when questions are reordered.
- **Fetching all submissions without pagination**. Forms with thousands of entries will time out or hit rate limits.
- **Storing JotForm data without the submission ID**. You need it for updates, deletions, and deduplication.
- Building form-driven workflows where submissions trigger backend processes (CRM updates, order fulfillment).
- Integrating payment collection forms with order management systems.
- Programmatically creating many similar forms from templates.
- Migrating or syncing JotForm submission data to external databases.
- Adding webhook-based automation to existing JotForm setups.
## Quick Example
```bash
JOTFORM_API_KEY=your-api-key
# Use EU endpoint if your account is in EU:
# JOTFORM_BASE_URL=https://eu-api.jotform.com
```
```typescript
await jotformFetch(`/form/${formId}/webhooks`, {
method: "POST",
body: new URLSearchParams({ webhookURL: "https://myapp.com/webhooks/jotform" }),
});
```skilldb get form-survey-services-skills/JotformFull skill: 235 linesJotForm API Skill
You are an expert at integrating JotForm via its REST API. You build and manage forms programmatically, retrieve and process submissions, handle payment integrations, and react to events through webhooks with type-safe TypeScript patterns.
Core Philosophy
Resource-Oriented API Design
JotForm's API is RESTful with clear resource hierarchies: users own forms, forms contain questions and submissions. Navigate this hierarchy consistently — always scope requests by form ID, and use query parameters for filtering rather than fetching everything client-side.
Submission as Source of Truth
Treat JotForm submissions as your canonical data source. Each submission has a unique ID, a status, and a created date. Use the submission endpoint for backfills and webhooks for real-time processing. Always store the JotForm submission ID to enable updates and lookups.
Payment-Aware Form Handling
JotForm natively integrates with payment gateways (Stripe, PayPal, Square). When processing payment forms, always check the payment object in submissions for transaction status before fulfilling orders. Never assume payment succeeded based on submission existence alone.
Setup
Configure API authentication:
const JOTFORM_API_KEY = process.env.JOTFORM_API_KEY;
const BASE_URL = "https://api.jotform.com";
async function jotformFetch<T>(path: string, init?: RequestInit): Promise<T> {
const url = new URL(`${BASE_URL}${path}`);
url.searchParams.set("apiKey", JOTFORM_API_KEY!);
const res = await fetch(url.toString(), {
...init,
headers: { "Content-Type": "application/json", ...init?.headers },
});
if (!res.ok) throw new Error(`JotForm ${res.status}: ${await res.text()}`);
const json = await res.json();
return json.content as T;
}
Environment variables:
JOTFORM_API_KEY=your-api-key
# Use EU endpoint if your account is in EU:
# JOTFORM_BASE_URL=https://eu-api.jotform.com
Key Patterns
Retrieve and Process Submissions
Do this — paginate through submissions with filtering:
interface JotFormSubmission {
id: string;
form_id: string;
status: "ACTIVE" | "DELETED" | "OVERQUOTA";
created_at: string;
answers: Record<string, {
name: string;
text: string;
answer: string | Record<string, string>;
type: string;
prettyFormat?: string;
}>;
payment?: {
total: string;
currency: string;
status: "PAID" | "PENDING" | "FAILED";
transactionId: string;
};
}
async function getSubmissions(
formId: string,
opts: { offset?: number; limit?: number; status?: string } = {}
): Promise<JotFormSubmission[]> {
const params = new URLSearchParams({
offset: String(opts.offset ?? 0),
limit: String(opts.limit ?? 100),
...(opts.status ? { "filter[status]": opts.status } : {}),
});
return jotformFetch<JotFormSubmission[]>(`/form/${formId}/submissions?${params}`);
}
// Usage
const activeSubmissions = await getSubmissions("240000000000", { status: "ACTIVE", limit: 50 });
for (const sub of activeSubmissions) {
const email = Object.values(sub.answers).find((a) => a.type === "control_email")?.answer;
console.log(`Submission ${sub.id}: ${email}`);
}
Not this — fetching all submissions without pagination or filtering, causing timeouts on high-volume forms.
Handle Payment Submissions
Do this — verify payment status before fulfillment:
async function processPaymentSubmission(submissionId: string): Promise<void> {
const submission = await jotformFetch<JotFormSubmission>(`/submission/${submissionId}`);
if (!submission.payment) {
throw new Error("Not a payment submission");
}
switch (submission.payment.status) {
case "PAID":
await fulfillOrder({
transactionId: submission.payment.transactionId,
amount: parseFloat(submission.payment.total),
currency: submission.payment.currency,
answers: submission.answers,
});
break;
case "PENDING":
await markOrderPending(submissionId);
break;
case "FAILED":
await notifyPaymentFailure(submissionId);
break;
}
}
Not this — treating every submission as a successful purchase without checking payment.status.
Create Forms with Questions Programmatically
Do this — build forms via the API with typed question properties:
interface JotFormQuestion {
type: "control_email" | "control_textbox" | "control_dropdown" | "control_fullname" | "control_payment";
text: string;
name: string;
required: "Yes" | "No";
order: string;
options?: string; // pipe-separated for dropdowns
}
const formId = await jotformFetch<string>("/user/forms", {
method: "POST",
body: new URLSearchParams({
"properties[title]": "Order Form",
"questions[1][type]": "control_fullname",
"questions[1][text]": "Full Name",
"questions[1][name]": "fullName",
"questions[1][required]": "Yes",
"questions[1][order]": "1",
"questions[2][type]": "control_email",
"questions[2][text]": "Email",
"questions[2][name]": "email",
"questions[2][required]": "Yes",
"questions[2][order]": "2",
"questions[3][type]": "control_dropdown",
"questions[3][text]": "Plan",
"questions[3][name]": "plan",
"questions[3][options]": "Starter|Pro|Enterprise",
"questions[3][order]": "3",
}),
});
Not this — manually creating forms in the UI when you need many similar forms; use the API for templated creation.
Common Patterns
Register a Webhook
await jotformFetch(`/form/${formId}/webhooks`, {
method: "POST",
body: new URLSearchParams({ webhookURL: "https://myapp.com/webhooks/jotform" }),
});
Get Form Questions
interface JotFormQuestionMap {
[questionId: string]: JotFormQuestion & { qid: string };
}
const questions = await jotformFetch<JotFormQuestionMap>(`/form/${formId}/questions`);
const emailQuestion = Object.values(questions).find((q) => q.type === "control_email");
Update Submission Status
await jotformFetch(`/submission/${submissionId}`, {
method: "POST",
body: new URLSearchParams({ "submission[status]": "ACTIVE" }),
});
List User Forms with Search
interface JotFormMeta {
id: string;
title: string;
status: "ENABLED" | "DISABLED" | "DELETED";
count: string; // submission count as string
created_at: string;
}
const forms = await jotformFetch<JotFormMeta[]>(
`/user/forms?filter={"status":"ENABLED"}&orderby=created_at`
);
Anti-Patterns
- Ignoring
payment.statuson payment forms. Submission existence does not mean payment succeeded — always check the payment object. - Using question array index instead of question ID or type to locate answers. Indices change when questions are reordered.
- Fetching all submissions without pagination. Forms with thousands of entries will time out or hit rate limits.
- Storing JotForm data without the submission ID. You need it for updates, deletions, and deduplication.
When to Use
- Building form-driven workflows where submissions trigger backend processes (CRM updates, order fulfillment).
- Integrating payment collection forms with order management systems.
- Programmatically creating many similar forms from templates.
- Migrating or syncing JotForm submission data to external databases.
- Adding webhook-based automation to existing JotForm setups.
Install this skill directly: skilldb add form-survey-services-skills
Related Skills
Formbricks
Integrate Formbricks open-source surveys for in-app, website, and link-based
Formik
Build React forms with Formik using useFormik hook, Field components, and
React Hook Form
Build performant React forms with React Hook Form using register, validation,
Surveymonkey
Integrate SurveyMonkey's REST API to create surveys, collect responses via
Tally
Integrate Tally forms via its API and embed SDK to handle submissions, configure
Typeform
Integrate Typeform APIs to create forms, retrieve responses, configure webhooks,