Forms API
Create, edit and publish intake forms from your own code, on behalf of your own users.
Intake forms collect what you need from a prospect before you can quote them. This API lets you build and manage those forms from your own application, so your users never have to leave your product to do it.
Each form belongs to one of your users. They see their own forms and nobody else's — you don't create InsurGrid accounts for them, and you don't manage permissions. You tell InsurGrid which of your users a form belongs to, and it handles the separation.
If you'd rather your users built forms visually than through code, the same forms are available in an embeddable builder — see Embed the form builder. The two work on the same data.
Submissions don't come back through this API. When someone completes a form, the submission is delivered to your webhook endpoint. See Submissions.
Before you start
Your API key
Every request carries your key as a bearer token:
Authorization: Bearer YOUR_API_KEYCreate and revoke keys in the dashboard under Settings → API keys. Two things to know:
The key must belong to an agency admin, otherwise you get
403 admin_required.Forms access must be enabled for the agent who owns the key. If it isn't, every call returns
403 feature_disabled— contact us to switch it on.
Revoking a key stops the API and any live embeds on their next request. See Get your API credentials.
Environments
API base URL
https://phoenix-staging.insurgrid.com/api/v1
https://phoenix.insurgrid.com/api/v1
Embed script
https://dashboard-staging.insurgrid.com/embed.js
https://dashboard.insurgrid.com/embed.js
Dashboard (API keys)
https://dashboard-staging.insurgrid.com
https://dashboard.insurgrid.com
Build against staging, then change the host and the key. The two are fully separate: a staging key works only on staging, and a form created on staging doesn't exist on production. More in Environments.
Which forms this API can see
externalUserId is how you tell InsurGrid whose form this is — a stable id from your own system, such as your internal user id. Any string up to 255 characters. You set it when you create a form; it can't be changed afterwards, and it's what decides who can see the form from then on.
A form is tied to two things: that externalUserId, and the key that created it. Both have to match for a form to be visible, which has two consequences worth planning around:
Forms built in the InsurGrid dashboard are invisible here. They have no
externalUserId, so they never appear inGET /forms, andGET /forms/{id}returns404. Only forms created through this API or the embedded builder are reachable.Keys don't share forms, even inside one agency. A form created with one admin's key returns
404to another admin's key. Use one key consistently for a given integration, and don't expect a replacement key to inherit the forms the old one made — if you rotate, you'll be starting a new set.
Quick start
Create a draft:
Leave out steps and the form starts with a working default: a welcome step, a contact info step that already collects name, email and phone, and a confirmation step. The response carries the form's id.
Publish it:
The response now includes shareUrl — the public link you send to customers, or embed. For a walkthrough that goes further, see Quickstart C.
Endpoints
GET
/forms
List forms.
POST
/forms
Create a draft form.
GET
/forms/{id}
Get one form with all its steps and questions.
PATCH
/forms/{id}
Update a form.
DELETE
/forms/{id}
Delete a form.
POST
/forms/{id}/duplicate
Copy a form for the same user.
PUT
/forms/{id}/publish
Publish a form and get its shareUrl.
PUT
/forms/{id}/archive
Take a form out of use.
PUT
/forms/{id}/reactivate
Make an archived form active again.
GET
/forms/templates
Starter forms you can build from.
GET
/forms/acord-catalog?line=
ACORD forms and questions. line is required.
POST
/embed-sessions
A short-lived token for the embedded builder.
Listing forms
Everything is optional. Omit externalUserId to list across all of your users. limit runs 1–100 and defaults to 50.
Each entry carries:
id
The form's id, used in the paths above.
public_id
The public identifier used in form links and embeds.
name, slug
The name, and the readable part of its web address.
status
draft, active or archived.
externalUserId
Which of your users owns it.
shareUrl
Where to send people to fill it in — null until published.
acord_numbers, policy_types, is_acord_ready
ACORD mapping, if any.
created_at, updated_at
Timestamps.
GET /forms/{id} returns all of that plus the form itself: steps, insurance_line, branding, redirect_url, auto_response_enabled, tracking and carrier_flow_enabled.
Creating a form
externalUserId
Yes
Whose form this is.
name
Yes
Up to 255 characters.
slug
No
e.g. home-intake. Generated from name if omitted, with a short random suffix to keep it unique. Lower-case words separated by single hyphens, up to 100 characters.
insurance_line
No
personal (default) or commercial.
steps
No
The pages and questions. See below.
redirect_url
No
Where to send the customer after they submit.
branding
No
Logo and colours.
Forms are always created as drafts. Publishing is a separate call.
Updating a form
PATCH /forms/{id} with only the fields you want to change. On update, unknown fields are rejected rather than ignored — a misspelled key is a 422. (On create they're ignored, so a typo there passes silently.)
⚠️
stepsreplaces rather than merges. If you send it, send the complete list — any step you leave out is deleted, including the default contact info step. See Keeping the contact block populated.
Statuses
draft
Not live. shareUrl is null.
active
Published. shareUrl is set.
archived
No longer live. shareUrl is null.
How a form is structured
A form is a list of steps — the pages someone moves through — and each step holds questions.
Step fields
type
Yes
welcome, contact_info, custom, confirmation or complete. Only one of each non-custom type per form.
name
Yes
Up to 255 characters.
segment
Yes
Where the step sits in the flow: before, carrier or after. Easy to miss — it has no default, and omitting it is a 422.
position
Yes
Order within the form, 0–1000.
enabled
No
Defaults to true.
questions
No
The step's questions. Up to 200.
A form must contain at least one custom or contact_info step — a form made only of welcome and confirmation pages collects nothing, and is rejected.
Question fields
type
Yes
One of the question types below.
label
Yes
The question text, up to 500 characters.
position
Yes
Order within the step, 0–1000.
required
No
Defaults to false.
placeholder
No
Up to 500 characters.
field_mapping
No
Tags this question as carrying a known value. This is what fills the webhook's contact block — see below.
options
For choice questions
Up to 500. Required for dropdown, single_choice and multi_choice.
initially_hidden
No
Hide the question until a rule reveals it.
rules
No
Conditional logic, up to 100 per question.
Step types: welcome, contact_info, custom, confirmation, complete
Question types: short_text, long_text, email, number, date, dropdown, single_choice, multi_choice, file_upload, address, data_table
A form can hold up to 50 steps.
Rather than assembling this by hand, start from GET /forms/templates, or build a form the way you want it in the embedded builder and fetch it with GET /forms/{id} — what comes back is exactly what you can send. (Forms built in the InsurGrid dashboard won't work for this; see Which forms this API can see.)
Carrier-connected forms — the ones that log into a carrier to pull declaration pages — can't be created or enabled through this API. Build those in the dashboard.
Keeping the contact block populated
Every submission webhook carries a contact object with the submitter's name, email and phone. InsurGrid fills it from questions tagged with field_mapping — not from a question's type, label or position.
field_mapping
Fills
FullName
contact.name
Email
contact.email
Phone
contact.phone
ServiceType
—
EventDate
—
A form created without steps gets a contact info step whose three questions are already tagged, so contact works out of the box. The moment you send your own steps, that step is gone — along with its tags — and every webhook from then on arrives with:
Nothing errors, and the answers are still there in answers[]. Only contact goes empty, so it is easy to miss until something downstream stops working.
So when you build your own structure, tag the equivalent questions:
If two questions share a mapping, the first non-empty answer wins.
Errors
Every error has the same shape:
400
invalid_request
The request is malformed.
401
invalid_api_key
The key is missing, wrong or revoked.
401
invalid_embed_token
The embed token is missing or expired.
403
admin_required
The key doesn't belong to an agency admin.
403
feature_disabled
Forms aren't enabled for the key's agent.
403
scope_required
The key isn't scoped for this API.
404
not_found
No such form, or it isn't visible to this key.
409
conflict
That slug is already taken.
422
validation_error
A field is invalid — see details.
422
external_user_required
externalUserId missing on create.
422
carrier_flow_disabled
Carrier steps aren't supported here.
429
rate_limited
Too many requests. Wait for Retry-After seconds.
503
auth_unavailable
A temporary problem. Try again.
Rate limits
300 requests per minute, counted per agency and shared across every API key the agency holds.
60 embed tokens per minute, counted per API key.
Calls the embedded builder makes with a token are counted separately, per end user, so they don't eat into the 300.
Over any of them you get 429 with a Retry-After header.
