Form Backend API
Use GoodInbound as the backend for forms built in your own website or application. Load the form definition, save multi-step progress, accept file uploads, and submit with Lead Screen spam protection included.
The visitor endpoints in this guide are anonymous. Do not put a secret API key in browser code. Server-side form administration and response retrieval use the separate management API.
Before you start#
Create an active page form in your project. Copy its project slug, form slug, and field IDs from the form builder or generated API instructions. Examples below use acme/contact; replace those values and the example field IDs with your own.
If your existing form submits to another service and you only need screening, use the Lead Screening API instead. If you want a ready-made interface, use the form widget.
Load definition → Render fields → Save steps if needed → Submit
↓
Screening and lead routing
1. Load the form#
GET /api/v1/forms/:projectSlug/:formSlug
curl 'https://goodinbound.com/api/v1/forms/acme/contact'
The response includes form.steps, each step's fields, visibility and validation settings, and a top-level loadToken. Render the configured fields and keep this token for the first save or submit. It lets screening measure time since the form loaded.
Use field IDs, not display labels, as submitted keys. In JSON mode, unknown field IDs are not saved as configured field values. Conditional steps keep their original zero-based index; skipping a page does not renumber later pages.
2. Submit a single-page form#
POST /api/v1/forms/:projectSlug/:formSlug
curl --request POST 'https://goodinbound.com/api/v1/forms/acme/contact' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data '{
"_token": "LOAD_TOKEN_FROM_GET",
"fields": {
"NAME_FIELD_ID": "Alex Morgan",
"EMAIL_FIELD_ID": "alex@example.com",
"MESSAGE_FIELD_ID": "Can we arrange a product demo?"
}
}'
Replace LOAD_TOKEN_FROM_GET and all three field IDs before sending. Include every required field on the pages that apply to the visitor.
| Request field | Meaning |
|---|---|
fields |
Object keyed by configured field ID. Strings, numbers, booleans, and arrays are normalized to stored text. |
_token |
loadToken returned by the form configuration request. Send it when starting the response. |
session |
Existing form response ID from a previous step save; omit for a new response. |
clearedFieldIds |
Optional array of saved field IDs to clear. |
wait_ms |
Optional wait for the context check, clamped to 0–3000 ms; default 800. |
signals |
Optional behavior measurements from the supported browser integration. |
attribution |
Optional origin snapshot: utm, click_ids, page_host, page_path, referrer, and numeric captured_at. |
Use short UTM keys such as source, medium, and campaign inside attribution.utm. Origin values reported by your browser are attribution data, not proof of a paid click.
Example successful response, showing selected fields:
{
"session": "c9ff8d11-5cb5-47aa-834c-549f91897f4c",
"status": "screened",
"label": "lead",
"action": "allow",
"screening": { "state": "final", "revision": 1 },
"conversion": {
"eligible": true,
"reason": "accepted_lead",
"event_id": "c9ff8d11-5cb5-47aa-834c-549f91897f4c",
"occurred_at": "2026-10-14T09:00:00.000Z"
}
}
The returned session identifies the form response. It is different from a signed Lead Screen session used with /leadscreen/screens.
The full response also includes a reason, confidence, individual checks, and processing duration. A result may be pending or unscreened. Report a lead conversion only when conversion.eligible is true; use the result_token polling flow for pending results. See Lead Status Webhooks.
Save multi-step progress#
Save a page with POST /api/v1/forms/:projectSlug/:formSlug/:stepIndex:
curl --request POST 'https://goodinbound.com/api/v1/forms/acme/contact/0' \
--header 'Content-Type: application/json' \
--data '{
"_token": "LOAD_TOKEN_FROM_GET",
"fields": { "EMAIL_FIELD_ID": "alex@example.com" }
}'
This validates the selected page and returns { "session": "RESPONSE_ID" }. Send that session on every later save. When the visitor finishes, POST to the submit URL without a step index, including the same session and any final field values. The server merges saved values and validates the complete applicable form.
Step saves do not consume a screened submission. Completing the response does. A completed session cannot be submitted again; after a timeout, avoid starting a fresh response blindly because it can create a duplicate. This visitor flow does not implement the Lead Screen API's Idempotency-Key contract.
Submit a native HTML form#
For a form that navigates after submission, use the same endpoint as its action. Replace the sample IDs with your configured field IDs:
<form action="https://goodinbound.com/api/v1/forms/acme/contact" method="post">
<label>Name <input name="NAME_FIELD_ID" required></label>
<label>Email <input name="EMAIL_FIELD_ID" type="email" required></label>
<label>Message <textarea name="MESSAGE_FIELD_ID" required></textarea></label>
<button type="submit">Send request</button>
</form>
Normal browser form posts receive the configured redirect or a hosted thank-you page. Unlike JSON submissions, extra fields not defined in the builder are retained with a native HTML response. JSON clients should set Accept: application/json. A hidden honeypot named website must stay empty; do not use that reserved name for a visible company-website field.
Upload and download files#
Use multipart/form-data, with each file under its configured field ID. Put text values, session, and _token at the top level of the form body. For JSON responses, keep the Accept header; let your HTTP client set the multipart boundary.
curl --request POST 'https://goodinbound.com/api/v1/forms/acme/contact' \
--header 'Accept: application/json' \
--form 'EMAIL_FIELD_ID=alex@example.com' \
--form 'FILE_FIELD_ID=@./brief.pdf'
Include any other required fields or an existing saved session. File type, size, and workspace storage limits are checked. Response files are private. Retrieve response and value IDs through the management API, then download from your server:
GET /api/v1/forms/:projectSlug/:formSlug/responses/:responseId/files/:valueId
Authorization: Bearer gi_live_YOUR_SECRET_KEY
Do not treat the public /api/uploads/:key endpoint as a way to access private response files.
Manage forms and responses from your server#
Use a secret project API key for /api/projects/:projectId/*. These operations also have MCP equivalents:
| Operation | REST route |
|---|---|
| Create or list forms | POST / GET /api/projects/:projectId/forms |
| Read a form | GET /api/projects/:projectId/forms/:formId |
| Manage pages | /api/projects/:projectId/forms/:formId/steps |
| Manage fields | /api/projects/:projectId/forms/:formId/fields |
| Read responses | GET /api/projects/:projectId/forms/:formId/responses |
See Forms and Responses for administration and MCP Server for agent access. Accepted leads can create contacts and start configured workflows; a successful HTTP response alone does not guarantee that outcome.
Limits and errors#
Free includes 500 screened submissions per workspace per month; Pro includes 2,500 plus optional monthly tiers. Public final submits are limited to 30 requests/minute per IP, and step saves to 60/minute per IP. Management API requests are limited to 120/minute per key.
| Status or result | What to do |
|---|---|
| 404 | Check project/form slugs, active status, step index, and whether the session was already completed. |
422 validation_failed |
Show each error beside its field and return the visitor to the relevant step. |
| 429 | Inspect rate, submission, or storage allowance details and back off where appropriate. |
| 409 | Check the returned conflict, including unavailable project storage. |
| 500 or network failure | Keep the visitor's values and handle the uncertain submission without silently starting duplicates. |
status: "not_screened" |
Treat the response as unscreened, not as a verified lead or conversion. |
Example field validation error:
{
"error": "validation_failed",
"fields": {
"EMAIL_FIELD_ID": { "message": "Please enter your email", "step": 0 }
}
}
Related: Lead Screening & Spam Protection API · Lead Enrichment API · Scheduling API.