Skip to main contentSkip to Content
Forms

Forms

Forms capture contact data. Every form has its own hosted page, an embed script for your website, and a public submit endpoint, all rendered with the same design.

Showing a form to visitors

WayAddressUse it when
Hosted pageGET /f/{formId}You want a link to share: in an email, a message, a button. The page carries the form’s title and description.
Embed script<div data-campaignos-form="FORM_ID"></div> followed by <script async src="https://your-domain.com/api/forms/FORM_ID/embed"></script>The form should sit inside your own page. It renders in a shadow root, so your site’s styles cannot break it, while it takes your site’s font and text colour.
Plain HTMLCopied from the builder’s Embed tabYou want full control of the markup and will style it yourself. It is a copy: paste it again after changing the form.

Both the hosted page and the script always show the saved form, so later edits appear without touching your website. The rendered form validates inline in the visitor’s language (the browser’s own messages), shows a loading state while submitting, then the success message or a redirect. A hidden spam-trap field is included; a submission that fills it is accepted but discarded.

Appearance

settings on the form controls how it looks: title and description above the fields, accentColor (a hex colour for the button, selected choices and focus), submitButtonText, and after submitting either successMessage or redirectUrl. honeypotField renames the spam-trap field (default cos_website).

List Forms

GET /api/workspaces/{workspaceId}/forms

Auth: Session required

Query Parameters:

ParamTypeDefaultDescription
pagenumber1Page number
limitnumber50Items per page

Response:

{ "data": [ { "id": "frm_abc123", "name": "Newsletter Signup", "fields": [...], "doubleOptIn": false, "settings": {}, "_count": { "submissions": 500 }, "createdAt": "2026-01-15T10:00:00.000Z" } ], "pagination": { "page": 1, "limit": 50, "total": 10, "totalPages": 1 } }

Create Form

POST /api/workspaces/{workspaceId}/forms

Auth: Session required

Request Body:

{ "name": "Newsletter Signup", "fields": [ { "name": "email", "type": "email", "label": "Email Address", "required": true }, { "name": "firstName", "type": "text", "label": "First Name", "required": false }, { "name": "interests", "type": "select", "label": "Interests", "options": ["tech", "marketing", "design"] } ], "settings": { "redirectUrl": "https://example.com/thank-you", "successMessage": "Thanks for signing up!" }, "doubleOptIn": true }

Field Definition

FieldTypeRequiredDescription
namestringYesField identifier (used as form key)
typestringYesStored type: text, email, tel, number, select, checkbox or textarea
builderTypestringNoHow the field is shown; see below
labelstringYesDisplay label
requiredbooleanNoWhether field is required
optionsstring[]NoChoices for dropdown, single-choice and multiple-choice fields
placeholderstringNoPlaceholder text
helpTextstringNoShown under the field
defaultValuestringNoPre-filled value, or the pre-selected choice
widthstringNofull (default) or half; two half fields share a row
validationobjectNominLength, maxLength, pattern (a regular expression), max (rating scale)

builderType picks the control: TEXT, TEXTAREA, NUMBER, EMAIL, PHONE, URL, DATE, SELECT (dropdown), MULTISELECT (checkboxes, submitted as an array), RADIO (one choice), RATING (a 1–5 scale, or 0–10 with validation.max: 10), BOOLEAN (a single checkbox), HIDDEN and SUBMIT_BUTTON (its label becomes the button text). Without it, the stored type decides.

A HIDDEN field named locale that has no value is filled with the visitor’s browser language when the form is rendered, so new contacts get a locale.

Update Form

PUT /api/workspaces/{workspaceId}/forms/{formId}

Auth: Session required (MEMBER or above)

Request Body: Any subset of name, fields, settings, doubleOptIn, actions, progressiveFields. Only the keys you send are changed; omitted keys keep their saved value. fields replaces the whole field list and field names must be unique.


Submit Form (Public)

POST /api/forms/{formId}/submit

Public endpoint for form submissions. CORS-enabled.

Auth: None required
Rate Limit: 10 requests per minute per IP + form combination

Request Body: Key-value pairs matching form field names.

{ "email": "jane@example.com", "firstName": "Jane", "interests": "marketing" }

Response (201):

{ "success": true, "submissionId": "sub_abc123" }

What Happens on Submit

  1. Validates required fields
  2. Creates or updates contact by email
  3. Sets consent to OPTED_IN (unless double opt-in is enabled)
  4. Fires a form.submitted event on the contact
  5. Triggers active automations whose trigger matches that event, in the background so the response is not delayed

Errors:

  • 404 — Form not found
  • 422 — Validation failed, with details such as ["email is required", "score is invalid"]; the rendered form maps each entry back to its field
  • 429 — Rate limited

A request with Content-Type: application/x-www-form-urlencoded (a plain HTML form without the script) is answered with a 303 redirect to redirectUrl when one is set.


Embed script and hosted page (public)

GET /api/forms/{formId}/embed

Returns the JavaScript that draws the form inside the element [data-campaignos-form="{formId}"] (or right before the script tag when there is none). Cached for one minute. CORS-enabled.

GET /f/{formId}

The hosted page. Its language follows the visitor’s Accept-Language, which selects the built-in validation messages.

Last updated on