Developer Documentation

Integrate YourForm directly into your application. Build custom forms, handle submissions programmatically, and manage your data with our powerful API and SDKs.


Authentication

Build safe and secure integrations by managing your API keys in the dashboard.

Client-Side Usage

If you are using the SDK or API on the client-side (in a browser), you must whitelist your domain in the Dashboard settings. Backend usage does not require origin whitelisting.


Rate Limiting

YourForm API uses a rate-limiting system to ensure stability and fair usage.

ScopeLimitDescription
Form Submission (Per IP)20 req / 1 minLimit per user IP address to prevent abuse.
Form Submission (Daily Limit)1000 req / dayPer Form. Total submissions allowed per day.
Retrieve Form (API)60 req / 1 minPer API Key For fetching form details.
Public Form Access100 req / 1 minPer IP. For viewing public forms.

Response Headers

X-RateLimit-Limit
Max requests
X-RateLimit-Remaining
Remaining requests
X-RateLimit-Reset
Reset timestamp

SDK Reference

The YourForm SDK provides a type-safe interface for interacting with your forms programmatically.

Installation

Terminal
npm install @yourform/sdk

Basic Usage

Initialize Client
typescript
import { YourForm } from '@yourform/sdk';

// Initialize the client
const client = new YourForm({
  apiKey: process.env.YOURFORM_API_KEY // Your API Key
});

Advanced Examples

List Forms

List All Forms
typescript
import { YourForm } from '@yourform/sdk';

const client = new YourForm({ apiKey: process.env.YOURFORM_API_KEY });

async function main() {
  const forms = await client.forms.list();
  
  forms.forEach(form => {
    console.log(`${form.title} (${form.id})`);
  });
}

main();

Get Form Details

Get Single Form
typescript
import { YourForm } from '@yourform/sdk';

const client = new YourForm({ apiKey: process.env.YOURFORM_API_KEY });

async function main() {
  const formId = 'form_12345';
  const form = await client.forms.get(formId);
  
  console.log('Title:', form.title);
  console.log('Questions:', form.fields.length);
}

main();

Create the Form

Define your questions and set unique slugs. Slugs act as the durable API keys for your data fields.

Step 1: Create Schema
typescript
const form = await client.forms.create({
  title: 'User Survey',
  style: 'step', // Optional
  questions: [
    { 
      slug: 'user_email', // this is important
      type: 'email', // check supported question types below 
      title: 'Email Address' 
    },
    { 
      slug: 'rating', 
      type: 'rating', 
      title: 'Satisfaction' 
    }
  ]
});

Supported Question Types

short_textlong_textmultiple_choicecheckboxesdropdownemailphonenumberratingdatewebsitefile_uploadyes_noopinion_scale

Form Styles

classicstepchatcardaudio

Go Live

Forms start as DRAFT. Use the publish() method to enable public submissions and generate your Google Sheet.

Step 2: Publish
typescript
await client.forms.publish(form.id);

Receive Data

Submit responses using a clean JSON payload. Mapping is simple: the key is your Slug, and the value is the User's Answer.

Step 3: Submit Response
typescript
await client.responses.submit(form.id, {
  'user_email': 'dev@yourform.com', // Key (Slug) : Value (Answer)
  'rating': 5                     // Key (Slug) : Value (Answer)
});

Error Handling

All SDK methods throw standard errors that you can catch and handle. Common errors include ValidationError, AuthenticationError, and RateLimitError.

Robust Error Handling
typescript
import { YourForm, ValidationError, RateLimitError } from '@yourform/sdk';

const client = new YourForm({ apiKey: process.env.YOURFORM_API_KEY });

try {
  await client.forms.create({ title: '' });
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Invalid Data:', error.message);
  } else if (error instanceof RateLimitError) {
    console.error('Too many requests. Please slow down.');
  }
}

Headless Implementation (Next.js)

Build a 100% custom UI while YourForm handles the backend, Google Sheets, and integrations. This "Twin-Pattern" keeps your API keys secure by splitting logic between Server and Client.

01

Find your Form ID

To start, you need the formId. You can get this from the dashboard URL or programmatically using the SDK.

Option A: List All Forms
typescript
const sdk = new YourForm({ apiKey: "YOUR_KEY" });

const forms = await sdk.forms.list();
console.log(forms.map(f => ({ title: f.title, id: f.id })));
Option B: On Creation
typescript
const form = await sdk.forms.create({ title: "New Form" });
console.log("Your new ID:", form.id);
02

Fetch & Cache Schema (Server-side)

Fetch your form definition once. **Do not call this API on every user request.** Cache the response in a variable or a file. Re-fetch only if you change your questions or slugs in the dashboard.

app/survey/page.tsx
typescript
import { YourForm } from '@yourform/sdk';
import { MyCustomUI } from './MyCustomUI';

const sdk = new YourForm({ apiKey: process.env.YOURFORM_SECRET });

export default async function Page() {
  // Fetch & store this in a constant
  const form = await sdk.forms.get('form_abc123'); 

  return <MyCustomUI form={form} />;
}
03

Build Secure Submission Flow

For 100% security, we recommend the "Twin-Pattern": fetch schema on the server, but also submit from the server. This keeps your API keys completely hidden from the browser.

3.1 Configure Environment

Create a .env.local file in your project root. Add your Secret Key here. Never commit this file to Git.

.env.local
typescript
YOURFORM_SECRET=yf_live_123456789...

3.2 Create Server Action

Create a Server Action to handle the sensitive submission logic. This code runs only on your server.

app/actions.ts
typescript
'use server';

import { YourForm } from '@yourform/sdk';

// 1. Initialize SDK with Secret Key (Safe only on server)
const sdk = new YourForm({ apiKey: process.env.YOURFORM_SECRET });

export async function submitContactForm(prevState: any, formData: FormData) {
  const formId = 'YOUR_FORM_ID'; // Or pass as hidden field
  
  // 2. Extract Data (Map to your Slugs)
  const answers = {
    'full_name': formData.get('fullName'),
    'email_address': formData.get('email'),
    'message_body': formData.get('message')
  };

  try {
    // 3. Submit to YourForm
    await sdk.responses.submit(formId, answers);
    return { success: true, message: 'Message sent successfully!' };
  } catch (error) {
    return { success: false, message: 'Failed to send message.' };
  }
}

3.3 Build Client Form

Now build your UI component. Use useActionState (available in React 19 / Next.js 15) to handle the server action response gracefully.

components/ContactForm.tsx
typescript
'use client';

import { useActionState } from 'react';
import { submitContactForm } from '@/app/actions';

const initialState = { success: false, message: '' };

export function ContactForm() {
  const [state, formAction] = useActionState(submitContactForm, initialState);

  if (state.success) {
    return <p className="text-green-500">{state.message}</p>;
  }

  return (
    <form action={formAction} className="space-y-4">
      <div>
        <label className="block text-sm font-medium mb-1">Full Name</label>
        <input name="fullName" required className="w-full p-2 border rounded bg-transparent" />
      </div>
      
      <div>
        <label className="block text-sm font-medium mb-1">Email</label>
        <input name="email" type="email" required className="w-full p-2 border rounded bg-transparent" />
      </div>

      <div>
        <label className="block text-sm font-medium mb-1">Message</label>
        <textarea name="message" required className="w-full p-2 border rounded bg-transparent" />
      </div>

      <button type="submit" className="bg-[#4EC9B0] text-black px-4 py-2 rounded font-bold hover:opacity-90">
        Send Message
      </button>
      
      {state.message && <p className="text-red-500 text-sm">{state.message}</p>}
    </form>
  );
}

Securing Webhooks

Verify the HMAC Signature sent in the headers to ensure payloads are authentic.

Verification Example (Next.js App Router)

Create an API route at app/api/webhook/route.ts to handle incoming events.

app/api/webhook/route.ts
typescript
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';

const WEBHOOK_SECRET = process.env.YOURFORM_WEBHOOK_SECRET!;

export async function POST(req: NextRequest) {
  try {
    // 1. Get Headers
    const signature = req.headers.get('x-yourform-signature');
    const timestamp = req.headers.get('x-yourform-timestamp');
    
    if (!signature || !timestamp) {
      return NextResponse.json({ error: 'Missing headers' }, { status: 401 });
    }

    // 2. Get Raw Body (Text)
    const rawBody = await req.text();

    // 3. Verify Signature
    // Formula: HMAC_SHA256(timestamp + "." + rawBody, secret)
    const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
    hmac.update(`${timestamp}.${rawBody}`);
    const expectedSignature = hmac.digest('hex');

    // Timing Safe Comparison
    const isValid = crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expectedSignature)
    );

    if (!isValid) {
      return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
    }

    // 4. Process Event
    const event = JSON.parse(rawBody);
    console.log('Received Event:', event.type);

    return NextResponse.json({ received: true });
  } catch (error) {
    return NextResponse.json({ error: 'Webhook handler failed' }, { status: 500 });
  }
}