Submit Form

Submits a lead from one of the account's forms. This writes a new contact into the account's CRM and notifies the site owner, so send only real visitor data, or clearly fictional data when you test.

POST/v1/forms/submit

Authentication

Public key?key=pk_…

Public key in the `key` query parameter. No OAuth, no secret. Optional: add the signed-in visitor's access token as `Authorization: Bearer` so the submission attaches to that visitor's existing contact (see `userId`). Browsers can call it only from the site's own origins. Server-side requests without an Origin header are accepted.

Edge cache: None (no-store)

Query Parameters

keystring
required

The site's public key (pk_…). It selects the account. Safe to ship to the browser.

Body Parameters

formIdstring
required

The id of the form, from `form._id` of Get Form By Spot or Get Form By ID. It must belong to the account of the key. The example is fictional, replace it with one of your form ids.

formDataobject
required

The answers, as an object keyed by field `id` from the form `schema`. Values are checked against each field's type, required flag, options and limits. Unknown keys are dropped. The example values are fictional.

sourcestring
optional

A label for where the lead came from. It is saved as the contact's source and on the activity. The default is `website-form`.

Default:website-form
contextDataobject
optional

Optional context shown with the lead, such as `mlsId`, `propertyAddress` or `popupId`. Values are strings. The values you send are saved as is.

pageUrlstring
optional

URL of the page where the form was sent. Used for analytics.

pageTitlestring
optional

Title of that page. Used for analytics.

utmSourcestring
optional

UTM source of the visit.

utmMediumstring
optional

UTM medium of the visit.

utmCampaignstring
optional

UTM campaign of the visit.

utmTermstring
optional

UTM term of the visit.

utmContentstring
optional

UTM content of the visit.

userIdstring
optional

Contact id of a signed-in visitor. It is kept only when the `Authorization: Bearer` token belongs to that same contact, otherwise it is ignored. With a valid `userId` the name, email and phone fields become optional and the lead is added to that existing contact.

analyticsobject
optional

Optional `{ visitorId, sessionId }`, two UUIDs, the same ones you send to the analytics endpoint. It attributes the `form_submission` event to the visitor.

Responses

200Success
application/json
titlestring

Success text. It is the same as `message`.

messagestring

Success text you can show to the visitor.

hideToastboolean

Always `false`. It is a hint for the website's own toast.

typestring

`success` on a saved lead.

variantstring

`default` on success, `destructive` on error.

sampleboolean

Only with a sample key: `true`, and nothing was saved.

Errors

400Every failure answers 400 with `type: "error"`, `message`, an inner `status` and, for validation failures, `errors` keyed by field id (for example `errors.field_1700000000003.message`). Causes: form not found or from another account (`Form not found`), a missing or invalid field (inner status 422, `Please check the form for errors`), no field mapped to name and email (`Form must include name and email fields`), a `formId` that is not a valid id, or a failure while saving or sending the owner's notification email.
401Missing or invalid public key.
403The account has no active subscription. The body also carries `code: "TENANT_INACTIVE"`.
500The request body is not valid JSON, or an unexpected server error happened. The body is `{ "message": "Internal Server Error" }`.

Notes

  • This endpoint writes to the account's CRM. A successful call creates a contact (type lead, stage new-lead), adds a form activity and an incoming message, notifies the owner in the app and by email, and increments the form's submission count.
  • It does not look for an existing contact with the same email. Submitting twice creates two contacts, unless `userId` points to an existing contact. There is no rate limit in the endpoint, and bot detection is observe-only, so add your own protection on the page (for example a honeypot or a captcha).
  • Unless `userId` is valid, the form must contain a field mapped to name and a field mapped to email, and both must have a value. The notification email is part of the save: if it fails, nothing is saved and you get a 400.
  • A sample key (`pk_..._s_...`, used for catalog template previews) answers the same success with `sample: true` and writes nothing.
Request
curl --request POST \
--url "https://api.pandaidx.com/v1/forms/submit?key={{public_key}}" \
--header "Content-Type: application/json" \
--data '{"formId":"6a1f3c9d2b7e4a0012ab34cd","formData":{"field_1700000000001":"Alex Example","field_1700000000003":"alex.example@example.com"}}'
Response200 OK
{
"title": "Thank you! We'll be in touch soon.",
"message": "Thank you! We'll be in touch soon.",
"hideToast": false,
"type": "success",
"variant": "default"
}