Skip to main content
POST /leads saves an enquiry in the institute’s CRM (Audience Manager → Recent Leads), exactly as the hosted catalogue’s own enquiry form does. The institute’s lead routing, scoring, counsellor assignment and duplicate rules apply.

The request

Reserved custom_fields keys (any case) are refused with 400; send them as the top-level fields instead: full_name, fullname, name, email, phone, mobile, mobile_number, message, source, course_id. Every field is a JSON string except custom_fields. message, source and course_id are also saved as fields of the lead, so the institute sees them in the CRM.

The response

Show the visitor the same thank-you for both.

campaign_id

Without campaign_id, enquiries land in the institute’s Course Catalogue Leads list, created automatically, like the hosted catalogue’s own form. To file them under a specific campaign, ask the institute’s admin for its id: in the dashboard, Audience Manager → open the campaign → copy campaignId from the page address. The campaign must be active and belong to the institute.
An unknown, inactive or foreign campaign_id is not an error: the enquiry is saved to the Course Catalogue Leads list instead, so no enquiry is ever lost. Test a new campaign_id once and check that the lead appears in that campaign.

From a Next.js server action

Calling from the server keeps the per-visitor limit accurate: pass the visitor’s IP in X-Client-IP. Without it, only the per-key limit applies.
app/actions/enquiry.ts
app/components/enquiry-form.tsx
On Next.js 14, use useFormState from react-dom instead of useActionState.
The IP header depends on your host. Vercel and most proxies set x-forwarded-for (first entry = the visitor); Cloudflare sets cf-connecting-ip. Pass the visitor’s IP, never your server’s.

Limits

Over a limit: 429 rate_limited with Retry-After. Rejected requests count too, so validate on your side first. X-Client-IP is ignored on browser calls (requests with an Origin header), so a browser cannot choose its own bucket.

Errors