Forms API
Use these anonymous endpoints to run GoodInbound forms from your own UI. No API key: never put one in visitor-side code.
Get form config#
GET /api/v1/forms/:projectSlug/:formSlug
Returns the form's pages, fields, validation rules, and visibility conditions, plus a loadToken. Send the token back as _token on your first call so Lead Screen can measure how long the form took to fill in.
curl "https://goodinbound.com/api/v1/forms/acme/contact"
{
"form": {
"id": "form_x1y2z3",
"name": "Contact Form",
"steps": [
{
"id": "step_1",
"sortOrder": 0,
"fields": [
{ "id": "full_name", "type": "text", "label": "Full Name", "required": true },
{ "id": "email", "type": "email", "label": "Email", "required": true }
]
}
]
},
"loadToken": "1791102815820.k3J…"
}
Save a step#
POST /api/v1/forms/:projectSlug/:formSlug/:stepIndex
Validate and save one page of a multi-step form. stepIndex is the page's position in the builder (conditional pages don't renumber). The first call returns a session; send it on later calls. Pages can be saved in any order, and each call checks that page's required fields and formats. Saving pages is free; only the submit counts as usage.
| Property | Type | Required | Description |
|---|---|---|---|
fields |
object | Yes | Field ID → value. Multi-select values can be arrays. |
session |
string | No | From the first call. Omit it on the first call. |
_token |
string | No | The loadToken from the form config, on the first call. |
curl -X POST "https://goodinbound.com/api/v1/forms/acme/contact/0" \
-H "Content-Type: application/json" \
-d '{ "fields": { "full_name": "Jane Smith", "email": "jane@example.com" } }'
{ "session": "resp_a1b2c3d4" }
To attach a file, send the call as multipart/form-data with the file under its field ID and the other values as plain fields. Uploaded files are private; download them with a project API key at GET /api/v1/forms/:projectSlug/:formSlug/responses/:responseId/files/:valueId.
Submit a form#
POST /api/v1/forms/:projectSlug/:formSlug
Finish the form and get the Lead Screen label back. With a session, it merges any final fields with the saved pages; without one, it submits a one-page form in a single call. Send Accept: application/json. The submit returns 422 when a required field on any page that applies is empty, listing each field and its page. Each submit is one usage.
curl -X POST "https://goodinbound.com/api/v1/forms/acme/contact" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{ "session": "resp_a1b2c3d4", "fields": { "message": "What does annual pricing look like?" } }'
{
"session": "resp_a1b2c3d4",
"status": "screened",
"label": "lead",
"label_name": "Real lead",
"reason": "Asks about pricing or plans",
"confidence": 0.94,
"action": "allow",
"checks": {
"behavior": { "result": "skipped", "summary": "No behavior data for this submission" },
"network": { "result": "pass", "summary": "Residential, London, GB" },
"identity": { "result": "pass", "summary": "Work email, active domain" },
"context": { "result": "pass", "summary": "Asks about pricing or plans" }
}
}
Missing required field (422)
{ "error": "validation_failed", "fields": { "email": { "message": "Please enter your email", "step": 0 } } }
Native HTML form#
Point a plain browser form at the submit URL. Without an Accept: application/json header, GoodInbound shows a hosted thank-you page or redirects, as configured in the form builder. Fields the builder doesn't define are kept with the response.
<form action="https://goodinbound.com/api/v1/forms/acme/contact" method="post" enctype="multipart/form-data">
<input type="text" name="full_name" required />
<input type="email" name="email" required />
<input type="file" name="resume" />
<textarea name="message"></textarea>
<button type="submit">Send</button>
</form>
Use your form's field IDs as the input name values; the form builder and its generated API prompt show the exact IDs. Include enctype="multipart/form-data" when the form has file inputs.
Other visitor endpoints#
Supporting anonymous routes used by public links, widgets, and analytics.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/event-types/:projectSlug/:eventSlug |
Get public event type configuration. |
| POST | /api/v1/t |
Record an anonymous visitor analytics event. |
| GET | /api/public/resolve/:projectSlug/:slug |
Resolve a share-link slug to a form or event type. |
| GET | /api/uploads/:key |
Download a public upload by object key. |
Public upload URLs contain unguessable object keys; don't treat them as authorization for sensitive private response files.