formbasedocs
Go to appApp

Developers

API methods

Complete reference for every method exposed by the formbase REST API. Each method shows its parameters, example requests, and response shapes.


The same reference is available as an OpenAPI 3.1 description for code generators, API clients, and agents.

Single endpoint, many methods

Every method is POST https://api.formbase.so/api/v1 with a JSON body {"method": "...", "params": {...}} and an Authorization: Bearer fb_… header. See API overview for auth and error handling, and API tokens for the token itself.

Conventions

  • params may be omitted; it defaults to {}. An unknown method is 404 METHOD_NOT_FOUND.

  • A token is bound to one workspace. Naming another workspace, or a form in one, is 403 FORBIDDEN even when you belong to both.

  • Pagination. List methods return { items, nextCursor, hasMore }; most also return canPaginate, which is false when hasMore is true but no cursor can resume (fuzzy search). Pass nextCursor back as cursor. limit is 1–100, default 20 — except requests.list, whose default is 25.

  • Rate limits. 120 calls per minute per token, shared with the MCP server; requests.create has its own 60 per minute. Failed authentication is limited separately, 30 per 15 minutes per IP, after which bad tokens see RATE_LIMITED instead of UNAUTHORIZED.

  • Body size. 1 MiB. Larger bodies are rejected with VALIDATION_ERROR.

  • Versioning. The path carries the version. Breaking changes ship as /api/v2; new methods and new response fields do not.

Forms

forms.list

List forms in a workspace. Supports cursor pagination and optional fuzzy name search.

POSThttps://api.formbase.so/api/v1
Parameters5
workspaceIdstringrequired

Workspace ID.

folderIdstring | nulloptional

Filter by folder. Pass null for root-level forms only. Omit to list all.

querystringoptional

Fuzzy name search. Results capped at limit; not cursor-paginated.

limitnumberoptionaldefault: 20

Page size (1–100).

cursorstringoptional

Pagination cursor from a previous response.

bash
curl -X POST https://api.formbase.so/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "forms.list",
    "params": { "workspaceId": "ws_abc123" }
  }'
200Success
json
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "frm_abc123",
        "name": "Contact Form",
        "folderId": null,
        "workspaceId": "ws_abc123",
        "isPublished": true,
        "publishedAt": 1714041851000,
        "unpublishedAt": null,
        "createdAt": 1714041800000,
        "lastEditedAt": 1714042000000,
        "emoji": null
      }
    ],
    "nextCursor": null,
    "hasMore": false,
    "canPaginate": false
  }
}
400Missing workspaceId
401Invalid or missing API token
429Rate limit exceeded

forms.get

Get full details for a single form, including questions, cover, logo, and a preview URL.

POSThttps://api.formbase.so/api/v1
Parameters1
formIdstringrequired

Form ID.

bash
curl -X POST https://api.formbase.so/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "forms.get",
    "params": { "formId": "frm_abc123" }
  }'
200Success
json
{
  "ok": true,
  "data": {
    "id": "frm_abc123",
    "name": "Event Feedback",
    "folderId": null,
    "workspaceId": "ws_abc123",
    "isPublished": true,
    "publishedAt": 1714041851000,
    "unpublishedAt": null,
    "createdAt": 1714041800000,
    "lastEditedAt": 1714042000000,
    "emoji": null,
    "questions": [
      {
        "id": "q_1",
        "type": "email-input",
        "inputType": "email",
        "title": "Your email",
        "metadata": null
      },
      {
        "id": "q_2",
        "type": "rating-input",
        "inputType": "rating",
        "title": "Overall experience",
        "metadata": { "kind": "rating", "maxStars": 5 }
      }
    ],
    "hasContent": true,
    "cover": null,
    "logo": null,
    "isDeleted": false,
    "trashedAt": null,
    "previewUrl": "https://formbase.so/preview/abc..."
  }
}
400Missing formId
404Form not found

forms.create

Create a new empty form. Returns the form and a preview URL.

POSThttps://api.formbase.so/api/v1
Parameters3
namestringrequired

Form name (1–255 characters).

workspaceIdstringrequired

Workspace ID.

folderIdstringoptional

Place the form in a folder. Omit to create at workspace root.

200Form created
json
{
  "ok": true,
  "data": {
    "id": "frm_new123",
    "name": "Contact",
    "workspaceId": "ws_abc123",
    "folderId": null,
    "isPublished": false,
    "createdAt": 1714041851000,
    "previewUrl": "https://formbase.so/preview/abc..."
  }
}
400Missing name or workspaceId
401Invalid or missing API token

forms.update

Update form metadata: name, folder, emoji, cover, or logo. Does not update form content (use the editor tools for that).

POSThttps://api.formbase.so/api/v1
Parameters6
formIdstringrequired

Form ID.

namestringoptional

New form name (1–255 characters).

folderIdstring | nulloptional

Move form to a folder. Pass null to move to workspace root.

emojistring | nulloptional

Form emoji (max 10 characters). Pass null to clear.

coverobjectoptional

Cover. {"type": "color", "color": "#ffffff"}, {"type": "image", "url": "https://...", "offsetY": 50} ( offsetY 0–100, default 50), or {"type": "none"} to remove. Image URLs must be http(s) or a data:image URI.

logoobjectoptional

Logo. {"type": "icon", "name": "HeartIcon"}, {"type": "image", "url": "https://..."}, or {"type": "none"} to remove. Icon names are fixed: QuestionMarkIcon, ListBulletsIcon, ChartBarIcon, ClockCountdownIcon, HeartIcon, LightbulbIcon, CheckCircleIcon, MagnifyingGlassIcon, TrendUpIcon, EnvelopeIcon, PhoneIcon, CalendarIcon, LinkIcon, UsersIcon.

Pass at least one of the five updatable fields. It does not change form content — use the MCP editor tools for that.

bash
curl -X POST https://api.formbase.so/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "forms.update",
    "params": {
      "formId": "frm_abc123",
      "name": "Updated Name",
      "emoji": "📋"
    }
  }'
200Form updated
json
{
  "ok": true,
  "data": {
    "id": "frm_abc123",
    "name": "Updated Name",
    "folderId": null,
    "emoji": "📋"
  }
}

cover and logo come back only when you sent them. A payload that matched the current state on every scalar field adds noChange: true.

forms.publish

Publish a form so it can accept responses, and freeze its field keys into a new snapshot. Idempotent: an already-published form returns success with alreadyPublished: true, and an unpublished form is republished from its last snapshot.

POSThttps://api.formbase.so/api/v1
Parameters1
formIdstringrequired

Form ID.

A form with content blocks but no questions publishes with a warning. A form with no content at all cannot be published. Publishing does not create a public URL — call shareLinks.create for that.

forms.unpublish

Take a form offline. Respondents can no longer open it. Idempotent — a form that is not published returns alreadyUnpublished: true. Reversible with forms.publish.

POSThttps://api.formbase.so/api/v1
Parameters1
formIdstringrequired

Form ID.

forms.delete

Move a form to trash. Its active share links are revoked, so their public URLs stop serving.

POSThttps://api.formbase.so/api/v1
Parameters1
formIdstringrequired

Form ID.

forms.restore

Restore a form from trash.

POSThttps://api.formbase.so/api/v1
Parameters2
formIdstringrequired

Form ID.

folderIdstring | nulloptional

Where to restore it. Omit for its original folder, null for workspace root, or a folder ID.

A form that is not in trash returns alreadyRestored: true.

formSettings.get

Read a form’s behavior settings.

POSThttps://api.formbase.so/api/v1
Parameters1
formIdstringrequired

Form ID.

Returns { settings, isDefault, availableEmailDomains, defaultFromAddress, payment }. isDefault is true when the form has no saved settings row yet and you are seeing the defaults. availableEmailDomains holds the verified domain ids you can pass as emailDomainId, and payment reports whether Stripe is connected (connecting it is a dashboard step).

formSettings.update

Update a form’s behavior settings. A partial update: only the fields you send are written.

POSThttps://api.formbase.so/api/v1
Parameters8
formIdstringrequired

Form ID.

Accessgroupoptional

language (BCP-47, default “en”), requireAuthentication, showBranding, captchaEnabled, passwordEnabled, password (4 characters or more; a string implies passwordEnabled: true, null clears the gate).

Owner notificationsgroupoptional

notifyOnSubmission, notificationEmails (array), selfNotificationSubject, selfNotificationEmailBody, pdfGenerationEnabled, showViewSubmissionButton. Owner emails are not translatable — write them in the language you want.

Respondent notificationsgroupoptional

respondentNotificationEnabled, respondentNotificationTo (the field id of an email question, or null), respondentNotificationSubject, respondentNotificationBody, respondentNotificationPdfEnabled.

Remindersgroupoptional

respondentReminderEnabled, respondentReminderTo, respondentReminderSubject, respondentReminderBody, respondentReminderRequiredFieldIds, and reminderSteps — idle offsets like [“1d”,“3d”,“1w”], at most 5, sorted and deduplicated on save, [] for none. The schedule applies to abandoned public-link responses and to requests alike. Pro.

After submitgroupoptional

redirectUrl (http(s); null or “” clears), redirectQueryParams ( [{ paramName, fieldId }]), allowAnotherResponse (mutually exclusive with a redirect), maxSubmissionsPerRespondent (0 = unlimited, max 1000), editAfterSubmit, maxEdits (max 3; 0 means unlimited on Pro and Business, 3 on Free).

Retentiongroupoptional

draftRetentionDays and submissionRetentionDays (0–36500, null reverts to the default). Submission retention is Business, and setting it clears any fixed deletion date configured in the builder.

emailDomainIdstring | nulloptional

A verified email-domain id from formSettings.get, for a custom From address. null resets to the default sender.

Subjects and bodies are plain text and accept {{variable}} placeholders; newlines become paragraphs. Customizing a respondent subject or body makes it translatable, so its keys appear in translations.listEntries right away.

Submissions

submissions.list

List a form’s submissions, newest page first, with cursor pagination.

POSThttps://api.formbase.so/api/v1
Parameters5
formIdstringrequired

Form ID.

includeDraftsbooleanoptionaldefault: true

Include responses that were started but never submitted. Drafts are a Pro feature: on Free only completed submissions are listed.

translationLanguagestringoptional

Attach stored AI translations of the answers under items[].translation.display, keyed like display. items[].answers and items[].display always stay the original.

limitnumberoptionaldefault: 20

Page size (1–100).

cursorstringoptional

Pagination cursor from a previous response.

200Success
json
{
  "ok": true,
  "data": {
    "formId": "frm_abc123",
    "formName": "Event Feedback",
    "items": [
      {
        "id": "sub_xyz789",
        "submittedAt": "2026-05-18 18:02:28",
        "isCompleted": true,
        "createdAt": "2026-05-18 18:02:19",
        "answers": {
          "email": "user@example.com",
          "plan": "pro",
          "contacts": [{ "name": "Ada" }, { "name": "Grace" }]
        },
        "display": {
          "email": "user@example.com",
          "plan": "Pro",
          "contacts": "Ada, Grace"
        }
      }
    ],
    "nextCursor": null,
    "hasMore": false,
    "canPaginate": false
  }
}

Same answers as webhooks and callbacks

Each item carries answers keyed by field key and display with the same keys as readable text — the shape a webhook payload, a request callback and requests.get carry. A choice answer is its option key, a repeating group an array of instances. Call fields.list for each key’s title and option labels. This method returns no totals.

submissions.pdf

Get a link to one submission’s PDF. Built for the Zapier connector: it returns a result only when the form has an active Zapier integration configured to include the PDF, and the PDF was retained.

POSThttps://api.formbase.so/api/v1
Parameters2
formIdstringrequired

Form ID.

submissionIdstringrequired

Submission ID. Must belong to that form and be completed.

200Success
json
{
  "ok": true,
  "data": {
    "url": "https://api.formbase.so/api/storage/...",
    "filename": "formbase-submission-sub_xyz789.pdf",
    "contentType": "application/pdf",
    "byteLength": 148213
  }
}
404No retained PDF for a Zapier integration on this submission

submissions.sample

Build a sample submission payload for a form, without any real data. It is the exact shape a share-link submission delivery carries, so connectors use it for field discovery; a request-born submission reaches a subscription as request.completed instead, sampled by

requests.sample. data.form.snapshotId is the form’s current published version, the same id live events carry, or null while the form is unpublished.

POSThttps://api.formbase.so/api/v1
Parameters1
formIdstringrequired

Form ID.

200Sample generated
json
{
  "ok": true,
  "data": {
    "id": "evt_example000000000000",
    "type": "submission.completed",
    "createdAt": "2026-05-18T19:00:00.000Z",
    "apiVersion": "2026-09-24",
    "test": true,
    "data": {
      "form": { "id": "frm_abc123", "name": "Event Feedback", "snapshotId": "js7abc123" },
      "submission": {
        "id": "sub_example000000000000",
        "respondentEmail": "respondent@example.com",
        "submittedAt": "2026-05-18T19:00:00.000Z",
        "updatedAt": null,
        "editCount": 0,
        "pdfUrl": null,
        "language": "en"
      },
      "answers": { "your_email": "john@example.com" },
      "display": { "your_email": "john@example.com" }
    }
  }
}

Field and payload semantics are documented once, in the webhooks reference.

Fields

fields.list

List every field of a form’s current published version, with the key to address each one by. Call this before requests.create instead of hard-coding keys. See Field keys.

POSThttps://api.formbase.so/api/v1
Parameters1
formIdstringrequired

Form ID. A form that has never been published has no field keys yet and answers published: false with no items.

bash
curl -X POST https://api.formbase.so/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "fields.list",
    "params": { "formId": "j57..." }
  }'
200Success
json
{
  "ok": true,
  "data": {
    "published": true,
    "items": [
      { "key": "company_name", "type": "text", "title": "Company", "required": true, "prefillable": true },
      {
        "key": "company_size", "type": "select", "title": "Company size", "required": false, "prefillable": true,
        "options": [
          { "key": "1_50", "label": "1–50" },
          { "key": "51_200", "label": "51–200" }
        ]
      },
      {
        "key": "satisfaction", "type": "matrix", "title": "How did we do?", "required": false, "prefillable": true,
        "rows": [{ "key": "delivery_speed", "label": "Delivery speed" }],
        "columns": [{ "key": "very_good", "label": "Very good" }, { "key": "poor", "label": "Poor" }]
      },
      { "key": "case_id", "type": "hidden", "title": "Case", "required": false, "prefillable": false, "context": true },
      { "key": "total", "type": "number", "title": "Total", "required": false, "prefillable": false, "calculated": true }
    ],
    "hasMore": false
  }
}

Reading the flags

context: true is a hidden field — its value belongs in context, never in prefill. prefillable: false marks a field nobody can supply a value for (file, signature, payment, appointment, documents). For a choice question, send the option key, not its label; a matrix lists its rows and columns the same way and takes { "row_key": "column_key" }. calculated: true is a calculated field: the form works out its value, you read it back in answers, and nothing can send it.

A repeating group is type: “group” with repeating: true and a members array. A Documents block is type: “documents” and carries documents: [{ name }], the authored files every respondent already sees.

400Missing formId
404Form not found

Requests

A request assigns one published form to one person and calls you back when it ends. The conceptual guide lives in Creating a request; this is the parameter list.

requests.create

Create a request. Spends one unit of the workspace’s monthly allowance, whether or not the recipient answers.

POSThttps://api.formbase.so/api/v1
Parameters16
formIdstringrequired

The published form to assign.

recipientobjectoptional

{ email?, name? }. An email is required when delivery is “email”; otherwise it only identifies the person in the Requests page and on their answers.

prefillobjectoptional

Initial answers by field key. The recipient sees them and can change them.

readonlystring[]optional

Prefilled keys the recipient cannot change. Every key here must also appear in prefill, and a locked required field must be prefilled with a non-empty value.

contextobjectoptional

Values for the form’s hidden fields, by field key. Trusted, unchangeable, and echoed back in the callback. An unknown key is rejected with UNKNOWN_FIELD_KEY.

metadataobjectoptional

Your own bookkeeping. Never reaches the form; comes back in callbacks and reads.

languagestringoptional

One of the form’s published languages. Defaults to the form’s own default.

deliverystringoptionaldefault: none

“email” to have formbase send the invitation (needs a recipient email, and Pro or Business or one of a Free account’s 10 free invitations), or “none” to deliver the link yourself.

remindersstring[]optional

Override the form’s reminder schedule for this request. An empty array switches reminders off.

expiresAtnumberoptional

Epoch milliseconds. Defaults to 30 days out; 365 days is the maximum.

callbackUrlstringoptional

Where formbase POSTs the callback when the request ends. HTTPS only, and the host must resolve to a public address.

externalIdstringoptional

Your own id for this request. Filterable in requests.list.

idempotencyKeystringoptional

Repeating it with the same body returns the original request with deduplicated: true. A different body is rejected. Keys live 30 days.

domainIdstringoptional

Mint the link on one of your custom domains. REST API only.

documentsobject[]optional

[{ documentId, field?, name? }] — files handed to this one recipient, uploaded first with documents.create.

testbooleanoptionaldefault: false

A dry run: nothing is emailed, the callback carries “test”: true, and the submission counts nowhere. The link closes within 24 hours, and on Free a workspace may create 10 test requests a day.

bash
curl -X POST https://api.formbase.so/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "requests.create",
    "params": {
      "formId": "j57...",
      "recipient": { "email": "ada@acme.com", "name": "Ada" },
      "prefill": { "company_name": "Acme" },
      "readonly": ["company_name"],
      "context": { "case_id": "CASE-9" },
      "delivery": "email",
      "externalId": "run-42",
      "callbackUrl": "https://automation.example/webhook/resume-abc",
      "idempotencyKey": "run-42"
    }
  }'
200Success
json
{
  "ok": true,
  "data": {
    "id": "kd7...",
    "status": "pending",
    "url": "https://form.formbase.so/r/rq_...",
    "deliveryStatus": "queued",
    "expiresAt": 1794787200000,
    "createdAt": 1789379200000,
    "externalId": "run-42",
    "deduplicated": false
  }
}

deliveryStatus is not_requested until an invitation is queued, then queued → sent or failed, and bounced once the mail provider reports a hard bounce or a complaint.

400Unknown field key, wrong value shape, or a locked key that is not prefilled
400Form not published (FORM_NOT_PUBLISHED), or callbackUrl not allowed (CALLBACK_URL_NOT_ALLOWED)
402Monthly allowance spent (MONTHLY_ALLOWANCE_REACHED), Free invitations spent (FREE_INVITATIONS_USED), or reminders below Pro
404Form not found
409Idempotency key reused with a different body (IDEMPOTENCY_CONFLICT)
429More than 60 requests.create calls in a minute on this token, or a Free workspace's 11th test request in a day (TEST_REQUEST_LIMIT_REACHED)

requests.get

Get one request in full: status, outcome, what was prefilled, its timeline, and — once completed — answers and display keyed by field key, the same two maps the callback carries.

POSThttps://api.formbase.so/api/v1
Parameters1
requestIdstringrequired

Request ID.

200Success
json
{
  "ok": true,
  "data": {
    "id": "kd7...",
    "formId": "j57...",
    "status": "completed",
    "outcome": "approve",
    "isTest": false,
    "recipient": { "email": "ada@acme.com", "name": "Ada" },
    "language": "en",
    "externalId": "run-42",
    "metadata": null,
    "context": { "case_id": "CASE-9" },
    "prefill": { "company_name": "Acme" },
    "readonlyKeys": ["company_name"],
    "delivery": "email",
    "deliveryStatus": "sent",
    "hasCallback": true,
    "callbackFailedAt": null,
    "submissionId": "kp2...",
    "url": "https://form.formbase.so/r/rq_...",
    "answers": { "company_name": "Acme", "decision": "approve" },
    "display": { "company_name": "Acme", "decision": "Approve" },
    "timeline": [
      { "id": "kd7...:created", "type": "created", "at": 1789379200000 },
      { "id": "kd7...:completed", "type": "completed", "at": 1789465600000 }
    ],
    "expiresAt": 1794787200000,
    "completedAt": 1789465600000
  }
}

outcome vs status

status says whether the request finished; outcome says what the recipient decided — approve, decline, changes, or null on anything but a completed request whose recipient picked one of the three — including a form with no decision question. The callback URL itself is never returned; hasCallback only says whether one is set.

The example above is trimmed. A full response also carries workspaceId, formSnapshotId, createdVia, documents, reminderStep, remindersSent, reminderDueAt, dataPurgedAt, and the rest of the timestamps (updatedAt, openedAt, startedAt, lastActivityAt, expiredAt, canceledAt, canceledBy, cancelReason).

Two fields tell you when the copy in front of you is the only copy. callbackFailedAt is set while this request's callback has run out of attempts, and cleared once one gets through or you replay it. dataPurgedAt is set once retention stripped the request: context, prefill and metadata come back empty, readonlyKeys and documents are [], and submissionId, answers and display are null.

timeline is derived, oldest first. Each entry has an id, an at, and a type — created, invitation, reminder, opened, started, completed, expired, canceled, callback. Delivery entries add deliveryStatus and attemptCount, and callbacks add eventType. Delivery rows are kept 30 days, so older timelines thin back to the timestamps.

404Request not found (REQUEST_NOT_FOUND)

requests.list

List requests in a workspace or on one form, newest first. Test requests are left out unless you ask for them.

POSThttps://api.formbase.so/api/v1
Parameters8
workspaceIdstringoptional

Scope to a workspace. Give this or formId.

formIdstringoptional

Scope to one form.

statusstringoptional

pending, completed, expired, or canceled.

outcomestringoptional

approve, decline, or changes. Implies completed requests only.

externalIdstringoptional

Your own id, to find the request a run created.

includeTestbooleanoptionaldefault: false

Include requests created with test: true.

limitnumberoptionaldefault: 25

Page size (1–100).

cursorstringoptional

Pagination cursor from a previous response.

200Success
json
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "kd7...",
        "formId": "j57...",
        "status": "pending",
        "outcome": null,
        "recipient": { "email": "ada@acme.com", "name": "Ada" },
        "externalId": "run-42",
        "deliveryStatus": "sent",
        "expiresAt": 1794787200000,
        "createdAt": 1789379200000
      }
    ],
    "nextCursor": null,
    "hasMore": false
  }
}

List items carry the same fields as requests.get minus url, answers, display, and timeline, and each one carries isTest. Give workspaceId or formId — neither is 400 VALIDATION_ERROR with reason SCOPE_REQUIRED. outcome overrides status, since only a completed request has a verdict.

requests.cancel

Withdraw a pending request. The link stops working, the recipient sees a withdrawn notice, and a request.canceled callback fires.

POSThttps://api.formbase.so/api/v1
Parameters2
requestIdstringrequired

Request ID.

reasonstringoptional

Your note for why, kept on the request and sent in the callback.

200The canceled request
409Already completed, expired, or canceled (REQUEST_NOT_PENDING)

requests.remind

Email the recipient now, without touching the reminder schedule. Needs a recipient email and a Pro or Business plan.

POSThttps://api.formbase.so/api/v1
Parameters1
requestIdstringrequired

Request ID. Must still be pending, and not a test request.

Two floors apply: at least 10 minutes between manual reminders, and at most 8 reminders per request in total, manual and scheduled together. The automatic schedule is untouched — reminderStep and reminderDueAt stay where they were.

200The request, with remindersSent incremented
400No recipient email on the request (RECIPIENT_EMAIL_REQUIRED)
402Request reminders require Pro or Business (UPGRADE_REQUIRED)
409Not pending (REQUEST_NOT_PENDING), too soon (REMINDER_TOO_SOON, with details.retryAfterMs), cap reached (REMINDER_CAP_REACHED), or a test request (TEST_REQUEST)

requests.replayCallback

Re-send the callback a request fired when it ended — same payload, same event id, so a receiver that already handled it can deduplicate. Use it after fixing a broken endpoint.

POSThttps://api.formbase.so/api/v1
Parameters1
requestIdstringrequired

Request ID. Must be completed, expired, or canceled.

200Success
json
{
  "ok": true,
  "data": { "dispatchId": "kf9...", "eventId": "evt_kf9..." }
}
409Still pending, so there is no terminal callback to replay (REQUEST_NOT_TERMINAL)
409The request was created without a callbackUrl (NO_CALLBACK_TO_REPLAY)

requests.sample

Build a sample request event for a form, without any real request. It is the exact envelope a request_* subscription created with webhooks.create receives, so connectors use it for field discovery. A completed sample carries the same example answers submissions.sample shows; an expired or canceled sample carries the request block alone.

POSThttps://api.formbase.so/api/v1
Parameters2
formIdstringrequired

Form ID.

eventTypestringrequired

Which ending to sample, in the webhooks.create spelling. The envelope’s type is the dotted form.

request_completedrequest_expiredrequest_canceled
200Sample generated
json
{
  "ok": true,
  "data": {
    "id": "evt_example000000000000",
    "type": "request.completed",
    "createdAt": "2026-05-18T19:00:00.000Z",
    "apiVersion": "2026-09-24",
    "test": true,
    "data": {
      "request": {
        "id": "req_example000000000000",
        "externalId": "order-1234",
        "status": "completed",
        "language": "en",
        "recipient": { "email": "recipient@example.com", "name": "Sample Recipient" },
        "metadata": { "source": "sample" },
        "context": {},
        "createdAt": "2026-05-18T18:00:00.000Z",
        "completedAt": "2026-05-18T19:00:00.000Z"
      },
      "form": { "id": "frm_abc123", "name": "Event Feedback", "snapshotId": "js7abc123" },
      "submission": {
        "id": "sub_example000000000000",
        "respondentEmail": "respondent@example.com",
        "submittedAt": "2026-05-18T19:00:00.000Z",
        "updatedAt": null,
        "editCount": 0,
        "pdfUrl": null,
        "language": "en"
      },
      "answers": { "your_email": "john@example.com" },
      "display": { "your_email": "john@example.com" }
    }
  }
}

The request block and the outcome are documented on the callbacks page; the submission half on the webhooks reference. Sample ids are the fixed placeholders shown above and test is true, so a receiver can tell a sample from a live event.

documents.create

Reserve an upload for a file you will hand to one recipient through the form’s Documents block. Bytes never travel through this API: you get a presigned PUT, you upload, and requests.create verifies the object before the request exists.

POSThttps://api.formbase.so/api/v1
Parameters5
formIdstringrequired

The form whose Documents block will show the file. Scopes the upload to that workspace.

namestringrequired

Display name the recipient sees (1–200 characters). Overridable per request.

contentTypestringrequired

application/pdf or an image type: image/png, image/jpeg, image/webp, image/gif, image/svg+xml, image/avif, image/bmp, image/tiff. Office documents are not accepted.

sizenumberrequired

Exact byte length. Maximum 25 MB (26,214,400).

sha256stringoptional

Hex digest of the bytes. Verified after upload when given.

200Upload reserved
json
{
  "ok": true,
  "data": {
    "id": "kn7...",
    "name": "Lease contract draft",
    "contentType": "application/pdf",
    "size": 412000,
    "uploadUrl": "https://...",
    "expiresAt": 1792198800000
  }
}

PUT the raw bytes to uploadUrl within the hour, with Content-Type set to the type you declared, then reference the id from requests.create:

json
{
  "method": "requests.create",
  "params": {
    "formId": "j57...",
    "documents": [
      { "documentId": "kn7...", "name": "Your lease contract" },
      { "documentId": "kn8...", "field": "attachments" }
    ]
  }
}
  • field is the Documents block’s field key. Optional when the form has exactly one block; required with two or more.

  • The block’s authored documents stay; yours appear below them, for this one recipient.
  • Caps: 25 MB per document, 100 MB of documents per request, 20 documents shown per block including the authored ones.
  • One upload can be referenced by any number of requests. An upload nobody references ages out. Bytes count against the workspace owner’s storage until the last request referencing them is stripped by retention.

Every failure here is 400 VALIDATION_ERROR with a details.reason: DOCUMENT_TYPE_NOT_ALLOWED, DOCUMENT_TOO_LARGE, INVALID_DOCUMENT_NAME or INVALID_DOCUMENT_SHA256 from this method, and DOCUMENT_NOT_FOUND, DOCUMENT_NOT_UPLOADED (you skipped the PUT), DOCUMENT_INVALID, INVALID_DOCUMENT_TARGET, DOCUMENTS_TOO_LARGE or DOCUMENTS_TOO_MANY from requests.create.

Webhooks

webhooks.list

List webhook subscriptions for a form.

POSThttps://api.formbase.so/api/v1
Parameters1
formIdstringrequired

Form ID.

200Success
json
{
  "ok": true,
  "data": {
    "items": [
      {
        "subscriptionId": "int_abc123",
        "formId": "frm_abc123",
        "provider": "zapier",
        "targetUrl": "https://hooks.zapier.com/...",
        "eventType": "submission_created",
        "status": "active",
        "createdAt": 1714041851000
      }
    ],
    "hasMore": false
  }
}

webhooks.create

Subscribe a URL to form events: new or abandoned submissions, or requests on the form ending. The URL must use HTTPS.

POSThttps://api.formbase.so/api/v1
Parameters6
formIdstringrequired

Form ID.

targetUrlstringrequired

HTTPS URL to receive webhook payloads.

providerstringrequired

Which tool the subscription belongs to. It is a label for your own bookkeeping — there is no marketplace app to install, and every provider behaves the same.

zapiermaken8n
eventTypestringoptionaldefault: submission_created

Event type to subscribe to. The three submission_ types deliver the submission payload: submission_created a first submission, submission_updated a respondent’s edit, and submission_abandoned an idle draft. The three request_ types deliver the matching request event whenever a request on the form ends that way, signed with this subscription’s secret; test requests reach no subscription.

submission_createdsubmission_updatedsubmission_abandonedrequest_completedrequest_expiredrequest_canceled
idleWindowstringoptional

Required when eventType is submission_abandoned; rejected for every other type.

12h1d3d1w
signingSecretstringoptional

Optional HMAC signing secret, 32–255 characters. When supplied, deliveries include X-formbase-Signature. Secret is stored but never returned by API.

bash
curl -X POST https://api.formbase.so/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "webhooks.create",
    "params": {
      "formId": "frm_abc123",
      "targetUrl": "https://hooks.zapier.com/hooks/catch/...",
      "provider": "zapier",
      "eventType": "submission_created",
      "signingSecret": "whsec_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
    }
  }'
200Webhook created
json
{
  "ok": true,
  "data": {
    "subscriptionId": "int_new123",
    "formId": "frm_abc123",
    "provider": "zapier",
    "targetUrl": "https://hooks.zapier.com/hooks/catch/...",
    "eventType": "submission_created"
  }
}

Abandoned-submission subscriptions return the selected idleWindow from both webhooks.create and webhooks.list. Every other subscription omits it.

A request subscription hears the same events a callback does, but as its own delivery: its own event id, its own signature, and its own five-attempt retry budget, after which the subscription pauses. A request created with a callbackUrl on a form with a request_completed subscription therefore fires twice, once to each receiver. requests.replayCallback re-sends the callback only. Use requests.sample to see the payload before any request has ended.

webhooks.delete

Remove a webhook subscription.

POSThttps://api.formbase.so/api/v1
Parameters1
subscriptionIdstringrequired

Subscription ID from webhooks.list or webhooks.create.

200Webhook deleted
json
{
  "ok": true,
  "data": {
    "subscriptionId": "int_abc123",
    "deleted": true
  }
}

Analytics

analytics.get

Get aggregated analytics metrics for a form. Supports date range, device, traffic source, and country filters.

Analytics are a Pro feature, and the rule follows the workspace owner’s plan, the same way the dashboard’s Analytics tab does. If the owner is not on Pro, this returns UPGRADE_REQUIRED — including for history recorded while they were. A free member of a Pro owner’s workspace gets the data.

POSThttps://api.formbase.so/api/v1
Parameters7
formIdstringrequired

Form ID.

fromnumberoptional

Start of date range as Unix timestamp in milliseconds. Must be less than or equal to to when both are set.

tonumberoptional

End of date range as Unix timestamp in milliseconds. Omit both for all time — period then comes back as { "from": null, "to": null }.

devicestringoptionaldefault: all

Filter by device type.

alldesktopmobiletablet
trafficSourcestringoptional

Filter by traffic source (e.g. “Direct”, “Google”).

countrystringoptional

Filter by 2-letter country code (e.g. “US”, “DE”).

includeEventsbooleanoptionaldefault: false

Also return the sanitized analytics events behind the metrics, for your own analysis. No visitor ids.

Rates are numbers from 0 to 100, counts are integers, and totalEvents is the raw event row count before deduplication into unique visitors.

200Success
json
{
  "ok": true,
  "data": {
    "formId": "frm_abc123",
    "period": { "from": null, "to": null },
    "totalEvents": 17,
    "metrics": {
      "views": 7,
      "uniqueVisitors": 7,
      "engaged": 6,
      "submissions": 4,
      "engagementRate": 86,
      "completionRate": 57,
      "bounceRate": 14
    },
    "breakdown": {
      "byBrowser": { "Chrome": 17 },
      "byCountry": { "US": 10, "DE": 7 },
      "byDevice": { "desktop": 14, "mobile": 3 },
      "bySource": { "Direct": 12, "Google": 5 }
    }
  }
}

Workspaces

workspaces.list

List the workspaces your token can reach. No parameters.

An API token is bound to one workspace, so this returns exactly that one — even when your account belongs to several.

POSThttps://api.formbase.so/api/v1
Parameters0
200Success
json
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "ws_abc123",
        "name": "Acme Inc",
        "createdAt": 1714041800000,
        "role": "owner"
      }
    ],
    "nextCursor": null,
    "hasMore": false,
    "canPaginate": false
  }
}

workspaces.createInvite

Create an invite link for a workspace.

POSThttps://api.formbase.so/api/v1
Parameters3
workspaceIdstringrequired

Workspace ID.

expiresAtnumberoptional

Expiration as a future Unix timestamp in milliseconds.

maxUsesnumberoptional

Maximum number of times the invite can be used.

200Invite created
json
{
  "ok": true,
  "data": {
    "id": "inv_abc123",
    "workspaceId": "ws_abc123",
    "code": "aBcDeFgH",
    "expiresAt": null,
    "maxUses": null,
    "uses": 0,
    "createdAt": 1714041851000
  }
}

workspaces.getInvite

Get one workspace invite. Returns the same shape as workspaces.createInvite.

POSThttps://api.formbase.so/api/v1
Parameters1
inviteIdstringrequired

Invite ID.

404Invite not found

workspaces.updateInvite

Update an existing workspace invite. Provide at least one of expiresAt or maxUses, or the call is rejected. Returns the updated invite.

POSThttps://api.formbase.so/api/v1
Parameters3
inviteIdstringrequired

Invite ID.

expiresAtnumberoptional

New expiration timestamp in milliseconds.

maxUsesnumberoptional

New max uses limit.

workspaces.revokeInvite

Permanently revoke a workspace invite.

POSThttps://api.formbase.so/api/v1
Parameters1
inviteIdstringrequired

Invite ID.

200Invite revoked
json
{
  "ok": true,
  "data": {
    "inviteId": "inv_abc123",
    "revoked": true
  }
}

Folders

folders.list

List folders in a workspace.

POSThttps://api.formbase.so/api/v1
Parameters3
workspaceIdstringrequired

Workspace ID.

limitnumberoptionaldefault: 20

Page size (1–100).

cursorstringoptional

Pagination cursor.

200Success
json
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "fld_abc123",
        "name": "Customer Feedback",
        "workspaceId": "ws_abc123",
        "parentId": null,
        "createdAt": 1714041800000
      }
    ],
    "nextCursor": null,
    "hasMore": false,
    "canPaginate": false
  }
}

folders.create

Create a folder in a workspace. Idempotent — returns the existing folder if a folder with the same name already exists.

POSThttps://api.formbase.so/api/v1
Parameters3
workspaceIdstringrequired

Workspace ID.

namestringrequired

Folder name (1–255 characters).

parentIdstring | nulloptional

Parent folder ID for nesting. Omit for root level.

200Folder created
json
{
  "ok": true,
  "data": {
    "id": "fld_new123",
    "name": "Customer Feedback",
    "workspaceId": "ws_abc123",
    "parentId": null,
    "createdAt": 1714041851000,
    "alreadyExisted": false
  }
}

folders.update

Rename a folder or move it to a different parent.

POSThttps://api.formbase.so/api/v1
Parameters3
folderIdstringrequired

Folder ID.

namestringoptional

New folder name (1–255 characters).

parentIdstring | nulloptional

New parent folder. Pass null to move to root.

folders.delete

Permanently delete a folder and all its contents (subfolders and forms).

POSThttps://api.formbase.so/api/v1
Parameters1
folderIdstringrequired

Folder ID.

200Folder deleted
json
{
  "ok": true,
  "data": {
    "folderId": "fld_abc123",
    "deletedFolderIds": ["fld_abc123"],
    "deletedFormIds": ["frm_in_folder"],
    "message": "Folder and 1 form deleted."
  }
}

Translations

translations.listLanguages

List all languages configured on a form.

POSThttps://api.formbase.so/api/v1
Parameters1
formIdstringrequired

Form ID.

200Success
json
{
  "ok": true,
  "data": {
    "formId": "frm_abc123",
    "items": [
      {
        "language": "es",
        "completion": { "total": 15, "current": 12, "outdated": 2, "missing": 1, "suggested": 0 },
        "lastUpdatedAt": 1714041851000
      }
    ],
    "nextCursor": null,
    "hasMore": false,
    "canPaginate": false
  }
}

translations.addLanguage

Register a language on a form. Every other translation method fails with 404 NOT_FOUND until you do.

POSThttps://api.formbase.so/api/v1
Parameters2
formIdstringrequired

Form ID.

languagestringrequired

BCP-47 language tag (e.g. “es”, “pt-BR”).

200Language added
json
{
  "ok": true,
  "data": {
    "formId": "frm_abc123",
    "language": "es",
    "rowId": "tl_new123"
  }
}

translations.removeLanguage

Remove a language and all its translations from a form.

POSThttps://api.formbase.so/api/v1
Parameters2
formIdstringrequired

Form ID.

languagestringrequired

BCP-47 language tag.

translations.listEntries

List every source key for one language on a form, with its current state. This is how you discover the key values translations.setEntry takes.

POSThttps://api.formbase.so/api/v1
Parameters2
formIdstringrequired

Form ID.

languagestringrequired

BCP-47 language tag. Must already be on the form.

200Success
json
{
  "ok": true,
  "data": {
    "formId": "frm_abc123",
    "language": "es",
    "completion": { "total": 15, "current": 12, "outdated": 2, "missing": 1, "suggested": 0 },
    "items": [
      {
        "key": "block_q_1.title",
        "status": "current",
        "value": "[{\"text\":\"Tu correo electrónico\"}]",
        "sourceFragment": "[{\"text\":\"Your email\"}]",
        "updatedAt": 1714041851000,
        "block": { "id": "q_1", "type": "email-input", "label": "Your email" }
      }
    ],
    "hasMore": false
  }
}

status is missing (nothing stored), outdated (the source changed since), current, or suggested (an AI suggestion staged but not accepted). Keys cover form content (block_<id>.) and, once an author has customized them, the respondent confirmation and reminder emails (email.confirmation., email.reminder.*).

translations.setEntry

Set a single translation entry. The language must have been added via translations.addLanguage first.

POSThttps://api.formbase.so/api/v1
Parameters4
formIdstringrequired

Form ID.

languagestringrequired

BCP-47 language tag.

keystringrequired

A key from translations.listEntries. Do not construct one by hand.

valuestringrequired

The translated fragment, JSON-stringified. Its mark structure must match the source fragment.

bash
curl -X POST https://api.formbase.so/api/v1 \
  -H "Authorization: Bearer fb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "method": "translations.setEntry",
    "params": {
      "formId": "frm_abc123",
      "language": "es",
      "key": "block_q_1.title",
      "value": "[{\"text\":\"Tu correo electrónico\"}]"
    }
  }'

Returns { formId, language, key }.

translations.deleteEntry

Delete a single translation entry, reverting that key to the form’s default language. Idempotent. When the last entry for a language goes, the language drops off the form’s published languages.

POSThttps://api.formbase.so/api/v1
Parameters3
formIdstringrequired

Form ID.

languagestringrequired

BCP-47 language tag.

keystringrequired

Translation key to delete.

Account

me.get

Get information about the authenticated user.

POSThttps://api.formbase.so/api/v1
Parameters0
200Success
json
{
  "ok": true,
  "data": {
    "id": "usr_abc123",
    "email": "you@example.com",
    "name": "Jane Doe"
  }
}

Meta

methods.list

List every method name this deployment serves, sorted. The authoritative answer when this page and the server disagree.

POSThttps://api.formbase.so/api/v1
Parameters0
200Success
json
{
  "ok": true,
  "data": {
    "methods": [
      "methods.list",
      "analytics.get",
      "folders.create",
      "folders.delete",
      "folders.list",
      "folders.update",
      "formSettings.get",
      "formSettings.update",
      "forms.create",
      "forms.delete",
      "forms.get",
      "forms.list",
      "..."
    ]
  }
}

Error reference

Every error response has the same shape. The top-level code set is closed on purpose: a new failure mode never adds a code, it adds a reason. Branch on code for the HTTP-level outcome and on details.reason for the fix.

error response
json
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "No field with key \"company\" on this form.",
    "details": { "reason": "UNKNOWN_FIELD_KEY", "field": "prefill.company", "validKeys": ["company_name", "contacts"] }
  }
}

details is present whenever the server can name the cause. Besides reason, it may carry field (the offending parameter, dotted for nesting), validKeys, validValues (the option values a choice question accepts), expectedType, feature (on UPGRADE_REQUIRED), and retryAfterMs (on a throttled call). Reasons for the request surface are listed with each method above.

These are all the codes:

Error codes
VALIDATION_ERROR400optional

Invalid or missing parameters in the request.

UNAUTHORIZED401optional

Missing or invalid API token.

FORBIDDEN403optional

Token lacks access to the requested resource.

NOT_FOUND404optional

Resource does not exist.

METHOD_NOT_FOUND404optional

Unknown method name. Use methods.list to see available methods.

CONFLICT409optional

The resource is not in a state that allows this call — a request that is no longer pending, an idempotency key reused with a different body.

RATE_LIMITED429optional

Over 120 calls a minute on this token, over 60 requests.create calls a minute, or too many failed authentications from this IP.

UPGRADE_REQUIRED402optional

Feature requires a higher subscription tier, the workspace has spent its monthly allowance (reason MONTHLY_ALLOWANCE_REACHED), or a Free account has spent its 10 free invitations (reason FREE_INVITATIONS_USED ).

INTERNAL_ERROR500optional

Unexpected server error. Try again later.

Next steps