---
name: makeform-headless
description: Create a Headless form with Makeform MCP, get its endpoint, and wire it into an app whose developer owns the UI. Use for API-backed forms, not hosted iframe embeds.
---

# Makeform Headless

Use this when the app owns the UI and Makeform should validate and store answers.
Connect your coding agent to the Streamable HTTP MCP server at
`https://mcp.makeform.ai` and sign in. Tool results are the authority for URLs,
permissions, limits and current state. Never paste secrets into chat.

1. Call `create_headless_form` with a name and `schema_mode: "strict"`.
   Add `domains: ["example.com"]` only when the owner wants browser submissions
   from that website. Omit domains for a server-only endpoint: it starts disabled.
2. The new Headless-native Form initially has zero fields. Use `get_form` to read
   its config and `updated_at` token, then `update_form` with the intended field names,
   types, required flags and options, preserving unrelated settings. Pass the exact `updated_at` as `expected_updated_at`, including null for a legacy
   Form. Use the current tool input schema; do not guess it.
   Preview and confirm with `publish_form` so the endpoint uses those fields.
3. Call `get_headless_endpoint` with `form_id`. Use its returned submit URL, keeping exact field names and option values from the published
   config. The existing schema
   route `/api/forms/{formId}/headless/schema` needs a signed-in Makeform session;
   the backend key does not grant management access.
4. For a browser integration, use the returned `embed.js` snippet with the
   allowed origin. It handles honeypot, validation errors and retry keys.
   Browser code needs no secret. For server use, have the owner open
   `/forms/{formId}/headless`, create a backend key, store it privately and enable
   the endpoint there. Keys are shown once: never put them in this agent's chat,
   context, browser bundle or repository. The MCP `create_headless_api_key` tool
   also returns a plaintext key once; do not call it in a conversation that
   cannot keep its result out of the agent context.
5. Check errors and retain the returned `submissionId` receipt. Reuse a submission
   intent's key and body for network retries; a changed payload needs a new key.
   Free includes one enabled endpoint and 50 Headless submissions per month;
   paid plans lift those limits. Paid respondent auto-reply is separate.

## Next.js App Router example (server only)

Create a published strict schema with `email`, `name`, and `company_size` fields.
This route belongs in `app/api/beta-signup/route.ts`. Replace the URL with the
actual submit URL. `MAKEFORM_HEADLESS_KEY` is a secret in your app's server
configuration, not a `NEXT_PUBLIC_` variable. The browser sends one UUID
`submissionKey` and reuses it only when retrying the same answers. Apply your
app's normal authentication and rate limiting to this route.

```ts
import { NextResponse } from 'next/server'

export async function POST(request: Request) {
  const { email, name, company_size, submissionKey } = await request.json()
  if (typeof email !== 'string' || typeof name !== 'string' ||
      typeof company_size !== 'string' || typeof submissionKey !== 'string' ||
      !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(submissionKey)) {
    return NextResponse.json({ error: 'Check your answers.' }, { status: 400 })
  }
  if (!process.env.MAKEFORM_HEADLESS_KEY) {
    return NextResponse.json({ error: 'Signup is not configured.' }, { status: 503 })
  }
  const response = await fetch('https://submit.makeform.ai/f/YOUR_ENDPOINT_ID', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.MAKEFORM_HEADLESS_KEY}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': submissionKey,
    },
    body: JSON.stringify({ email, name, company_size, _gotcha: '' }),
    cache: 'no-store',
  })
  const result = await response.json()
  return NextResponse.json(result, { status: response.status,
    headers: response.headers.get('Retry-After')
      ? { 'Retry-After': response.headers.get('Retry-After')! } : undefined })
}
```

Use the actual schema's options for `company_size`. Never forward arbitrary
browser headers (especially `Origin`) to the server-to-server request. Browser
requests with `Origin` use origin allowlisting even if they also send a key.
Backend-key submissions cannot satisfy respondent email verification.

Read the [Headless guide](https://www.makeform.ai/docs/headless),
[OpenAPI 3.1 contract](https://www.makeform.ai/openapi.json), and
[MCP runtime](https://mcp.makeform.ai) for the current contract.
