Docs · Results

API

Read your forms and responses from your own code with an API key.

Plan: Business

The Formz API lets your own code, CRM or reporting tools read your forms and their responses. It's read-only: it can't change forms or answers. To have responses pushed to you as they arrive, use a webhook instead.

Create a key

Open Account → API keys, give the key a name (for example "CRM sync") and select Create key. Copy it straight away: Formz shows the full key only once. You can have up to 10 keys and revoke any of them at any time; a revoked key stops working at once. Only the workspace owner can create keys.

The API keys page with a key name field, a Create key button and two keys
API keys, in your account.

A key can read every form and response in your workspace, so treat it like a password. Keep it on your server and never put it in a web page or mobile app.

Make a request

Send the key in the Authorization header. All requests go to https://joinformz.com/api/v1.

curl https://joinformz.com/api/v1/forms \
  -H "Authorization: Bearer fz_live_…"

Endpoints

GET /forms

Your forms, oldest first.

{
  "data": [
    {
      "id": "5b7c…",
      "title": "Mint allowlist",
      "description": null,
      "status": "live",
      "type": "waitlist",
      "published_at": "2026-10-01T09:00:00+00:00",
      "created_at": "2026-09-30T18:12:40+00:00",
      "updated_at": "2026-10-02T11:30:05+00:00"
    }
  ],
  "next_cursor": null
}

GET /forms/{id}

One form and its questions, in order. choices lists the options for choice questions and is null for the rest.

{
  "data": {
    "id": "5b7c…",
    "title": "Mint allowlist",
    "status": "live",
    "questions": [
      {
        "id": "block_1727…",
        "type": "single_select",
        "question": "Which chain do you use most?",
        "help_text": null,
        "required": true,
        "position": 0,
        "choices": ["Ethereum", "Solana", "Base"]
      }
    ]
  }
}

GET /forms/{id}/responses

The form's responses, oldest first. Each answer's question_id matches a question's id. Wallet and social blocks carry their verified details as the answer, the same as in your CSV export.

{
  "data": [
    {
      "id": "9e21…",
      "respondent_email": "ada@example.com",
      "started_at": "2026-10-01T14:03:22.114+00:00",
      "completed_at": "2026-10-01T14:03:22.114+00:00",
      "total_points": 0,
      "referral_code": "9e21…",
      "referred_by": null,
      "referred_count": 0,
      "answers": [
        {
          "question_id": "block_1727…",
          "question": "Which chain do you use most?",
          "type": "single_select",
          "answer": "Base"
        }
      ]
    }
  ],
  "next_cursor": "MjAyNi0xMC0wMVQx…"
}

Add ?since=2026-10-01T00:00:00Z to get only responses from that time on. It's the easiest way to sync: save the time of your last run and pass it next time.

Pages

Lists return 50 items per page. Set ?limit= to anything from 1 to 100. When there's more, next_cursor is set: pass it back as ?cursor= (with the same other parameters) to get the next page. When it's null, you have everything.

Limits and errors

Each key can make 60 requests a minute. Errors come back with an HTTP status and a body like {"error": {"code": "rate_limited", "message": "…"}}:

  • 401 unauthorized: the key is missing, wrong or revoked.
  • 403 plan_required: the workspace isn't on Business. Keys are kept and work again after an upgrade.
  • 404 not_found: no form with that id in your workspace.
  • 400 invalid_request: a parameter isn't valid; the message says which.
  • 429 rate_limited: too many requests. Wait for the seconds in the Retry-After header.