Forms API
Build and publish forms, read responses and submit as a signed-in caller.
Forms collect data from people: a contact form, a job application, an order enquiry. This API builds forms, publishes them, reads the responses, and submits on behalf of a signed-in caller. The public side of a published form, which anyone on the internet can use, is described in Public endpoints.
If the Forms app is switched off in Settings › Apps, these routes answer 404 NOT_FOUND.
Endpoints
Permissions are checked against the form (its key is the resource), so a role can be limited to particular forms.
| Method | Path | Permission | What it does |
|---|---|---|---|
GET |
/orgs/{orgId}/forms |
forms:read |
List forms you can see |
POST |
/orgs/{orgId}/forms |
forms:manage |
Create a form (as a draft) |
GET |
/orgs/{orgId}/forms/{formId} |
forms:read |
Get a form |
PATCH |
/orgs/{orgId}/forms/{formId} |
forms:manage |
Replace a form's definition |
DELETE |
/orgs/{orgId}/forms/{formId} |
forms:manage |
Delete a form and its responses (answers 200 with {"deleted": true}) |
POST |
/orgs/{orgId}/forms/{formId}/publish |
forms:manage |
Make it live |
POST |
/orgs/{orgId}/forms/{formId}/unpublish |
forms:manage |
Take it back to draft |
GET |
/orgs/{orgId}/forms/{formId}/submissions |
forms:responses |
Read responses |
POST |
/orgs/{orgId}/forms/{formId}/submit |
forms:submit |
Submit as the caller |
POST |
/orgs/{orgId}/forms/generate |
forms:manage |
Draft fields from a description |
GET /forms answers { "forms": [ … ] }.
Reading what others submitted (forms:responses) is deliberately separate from being allowed to fill in a form (forms:submit).
The form object
{
"id": "0190f400-…",
"key": "contact-us",
"name": "Contact us",
"description": "Ask us anything",
"fields": [
{ "key": "name", "label": "Your name", "type": "string", "required": true },
{ "key": "email", "label": "Email", "type": "email", "required": true },
{ "key": "topic", "label": "Topic", "type": "select", "rules": { "options": ["Sales", "Support"] } },
{ "key": "message", "label": "Message", "type": "text", "help": "Tell us a little more", "placeholder": "…" }
],
"object_type": "leads",
"function_id": "",
"status": "draft",
"settings": { "submit_label": "Send", "success_message": "Thanks, we will be in touch.", "redirect_url": "" },
"created_at": "2026-10-01T08:00:00Z",
"updated_at": "2026-10-01T08:00:00Z"
}
POST /orgs/{orgId}/forms
PATCH takes the same body and replaces the whole definition, so send everything you want to keep.
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | Yes | Up to 200 characters |
key |
string | No | The form's address segment: lowercase letters, digits and dashes. Made from the name when omitted. Unique in the workspace |
description |
string | No | |
fields |
array | Yes | 1 to 60 fields. See below |
object_type |
string | No | A record type key. Each submission also creates a record of that type. You need records:create on that type to save or publish the form |
function_id |
string | No | A function that is handed each submission |
settings.submit_label |
string | No | Button text |
settings.success_message |
string | No | Shown after submitting |
settings.redirect_url |
string | No | Where to send the browser after submitting. Must be an absolute http or https address |
Fields
| Key | Notes |
|---|---|
key |
Starts with a lowercase letter; lowercase letters, digits, underscore. Unique in the form. Leading underscores are reserved |
label |
Shown to the person filling in. Defaults to the key |
type |
string, text, number, bool, date, datetime, currency, select, multiselect, email or url. References and files are not allowed on forms |
required |
Boolean |
help, placeholder |
Optional text |
rules |
The same rules as record fields (computed formula fields are refused): options (required for select and multiselect), min, max, min_len, max_len, pattern, unit, default. See Record types and fields |
Submitted values are checked with the same validator as record fields, so a form can never accept something a record would refuse. If object_type is set, use that type's own field keys.
New forms are created as draft. A draft is invisible to the public.
Errors: 400 VALIDATION_ERROR (the message names the problem), 403 FORBIDDEN, 409 KEY_TAKEN.
Publish and unpublish
Both answer 200 with the form. Publishing does not by itself open the form to the world: a published form is reachable by visitors who are not signed in only when the workspace's public-visitor role grants forms:read and forms:submit on it. See Public endpoints.
GET /orgs/{orgId}/forms/{formId}/submissions
Cursor paging, newest first.
| Parameter | Notes |
|---|---|
limit |
Maximum 100 |
cursor |
The next_cursor of the previous page |
{
"data": {
"submissions": [
{
"id": "0190f410-…",
"values": { "name": "Asha", "email": "asha@example.com", "message": "Hello" },
"record_id": "0190f411-…",
"status": "processed",
"created_at": "2026-10-04T09:30:00Z"
}
],
"total": 58,
"next_cursor": "0190f410-…"
}
}
status is received, processed or failed. A submission is always saved first. If creating the record or running the function then fails, the submission stays, is marked failed with an error, and the submitter still sees success. submitted_by is present for signed-in submitters.
POST /orgs/{orgId}/forms/{formId}/submit
Submit as the caller, with the caller's permissions.
{ "values": { "name": "Asha", "email": "asha@example.com", "message": "Hello" } }
Response 201 with the submission object. 400 VALIDATION_ERROR names the field that failed. The form must be published (400 VALIDATION_ERROR: "this form is not published yet").
POST /orgs/{orgId}/forms/generate
Body {"prompt": "a booking enquiry with dates and party size"}. Returns {"fields": [ … ]} as a proposal. Nothing is saved. Answers 503 AGENT_DISABLED when AI drafting is unavailable.
Related
- Public endpoints
- Permissions reference
- In the product: Forms