Overview
Build a form in the console, submit to it from anywhere without a token, and trigger a workflow.
A form is a schema plus a public endpoint. You define the fields in the console,
the submission is stored as a collection record, and form.submitted fires as a
workflow trigger.
Forms are the only module besides public plans with unauthenticated endpoints. A submission needs no API token, which is what makes a form embeddable on a marketing site.
curl -X POST \
https://api.console.buildbase.app/api/forms/public/$ORG_ID/$FORM_ID/submit \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","message":"Interested in the Scale plan"}'{ "success": true, "message": "Form submitted successfully" }Before you start
Create the form in the BuildBase console under Forms. It is backed by a collection, so a collection and a published version must exist first. The console creates both when you build a form from scratch.
The three public endpoints
None of these require an Authorization header.
| Method | Path | Returns |
|---|---|---|
GET | /api/forms/public/:orgId/:formId | The form document — title, settings, collection references |
GET | /api/forms/public/:orgId/:formId/fields | The field schema array from the published collection version |
POST | /api/forms/public/:orgId/:formId/submit | { success, message } |
:orgId must be a valid ObjectId or the request is rejected before lookup. An
unknown formId returns 404 with
{"error": true, "message": "Form with id ... not found"}.
Submitting
The request body is the data object. Keys are field slugs — there is no wrapper:
{ "email": "[email protected]", "plan": "scale", "seats": 12 }Each submission creates a collection record carrying collectionId,
versionId, formId, and your data, then emits form.submitted with the
form ID, the new record ID, and the submitted data.
Validation
The server validates the payload against the published field schema before writing anything.
A collection field is one of eight types. Only four are validated on submit:
| Field type | Rule |
|---|---|
any, required: true | Rejected when undefined, null, or '' |
number | Must be a number or a numeric string |
email | Must match a basic [email protected] pattern |
link | Must start with http:// or https:// |
date | Must parse with Date.parse() |
text, rich-text, color | Stored as sent, no format check |
bool | Not validated — see below |
Empty optional values skip every check, so a rule only applies to a field the submitter actually filled in.
Warning
Boolean fields are not validated. The validator branches on the string
boolean, but the type's stored value is bool, so the branch never runs.
Any value at all — "yes", 42, an object — is accepted into a bool field
and stored verbatim.
Coerce booleans client-side, and treat the stored value as untrusted when you read records back.
Note that email and link only validate when the submitted value is a
string. A number or object sent for those fields skips the format check the
same way.
A failure returns 400 with every error at once, not just the first:
{
"success": false,
"message": "Validation failed",
"errors": ["Email must be a valid email", "Seats is required"]
}Note this uses success: false, unlike most API errors which use
error: true. Branch on the HTTP status rather than the body shape.
Warning
The public submit endpoint has no CAPTCHA and no spam filtering. The only limit in front of it is the global API rate limiter — 500 requests per 3 seconds per IP — which is a flood guard, not bot protection. A single client can submit far faster than any human and stay well under it.
Put your own bot protection in front of a publicly embedded form, or proxy submissions through your backend.
Triggering a workflow
form.submitted is a workflow trigger under the Forms category, and one of
86+ events in
the system event catalog. A common shape:
- Visitor submits the form.
- Record is written to the collection.
form.submittedfires.- A workflow sends a confirmation email and posts to Slack.
The event payload carries formId, recordId, and the full data object, so
merge tags in the workflow can address submitted fields directly.
Next Steps
- Custom forms — render your own UI against the field schema.
- Workflows — act on
form.submitted. - Admin API — read submissions back through the collections API.