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.
From a Next.js server action
Calling from the server keeps the per-visitor limit accurate: pass the visitor’s IP inX-Client-IP. Without it, only the per-key limit applies.
app/actions/enquiry.ts
app/components/enquiry-form.tsx
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.