POST /api/v1/leads
Create lead
The key identifies both the site and organization. Content-Type must be application/json and the total request body must be 64 KB or less.
curl --request POST \
--url https://api.leadpending.com/api/v1/leads \
--header "Authorization: Bearer lp_live_<secret>" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: contact_018f47f2" \
--data '{
"external_id": "contact-form-018f47f2",
"name": "Jane Doe",
"email": "[email protected]",
"company": "Example Inc",
"subject": "SEO audit",
"message": "Can you help us with three websites?",
"page_url": "https://example.org/pricing",
"locale": "en",
"metadata": { "utm_source": "google" }
}'Fields
| Field | Type | Rule | Description |
|---|---|---|---|
| string | Required | Lead email address. | |
| message | string | Required | Request text, up to 20,000 characters. |
| external_id | string | Optional | Stable ID unique inside this site. |
| name · phone · company | string | Optional | Contact details shown to the operator. |
| subject | string | Optional | Defaults to “Inquiry from {site name}”. |
| page_url | URL | Optional | The page where the request was submitted. |
| locale | string | Optional | Preferred reply language, for example en or ru. |
| metadata | object | Optional | Extra JSON such as UTM or budget range, up to 20 KB. |
Make every form submission retry-safe
Send a stable Idempotency-Key for the same browser submission. Repeating the same key and payload returns the original lead with 200. Reusing the key with different content returns 409.
Responses
201 creates a new lead. An idempotent repeat by Idempotency-Key or external_id returns the same resource with 200.
HTTP/1.1 201 Created
Content-Type: application/json
{
"data": {
"id": "0198…",
"status": "pending",
"created_at": "2026-08-02T12:00:00Z"
}
}Error contract
| HTTP | Code | Action |
|---|---|---|
| 400 | VALIDATION_FAILED | A field, payload size, or JSON value is invalid. |
| 401 | INVALID_API_KEY | The site key is missing, revoked, or does not match an active site. |
| 409 | IDEMPOTENCY_CONFLICT | The idempotency key was already used for a different payload. |
| 429 | RATE_LIMITED | The key exceeded its configured intake rate. |
| 503 | SERVICE_UNAVAILABLE | Intake is temporarily unavailable; retry with the same key. |
Next.js server route
Validate the form and Turnstile before this call. Return success to the browser only after LeadPending responds with 200 or 201.
import { NextResponse } from 'next/server'
export async function POST(request: Request) {
const form = await request.json()
// Validate fields and Turnstile here first.
const response = await fetch(
'https://api.leadpending.com/api/v1/leads',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LEADPENDING_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': form.submissionId,
},
body: JSON.stringify({
external_id: form.submissionId,
name: form.name,
email: form.email,
message: form.message,
page_url: form.pageUrl,
}),
},
)
const payload = await response.json()
if (response.status !== 200 && response.status !== 201) {
return NextResponse.json(
{ error: payload?.error?.code ?? 'LEAD_NOT_ACCEPTED' },
{ status: response.status },
)
}
return NextResponse.json({ accepted: true })
}Production checklist
- 01Store LEADPENDING_API_KEY as a server secret.
- 02Generate one stable idempotency key per form submission.
- 03Log status and error code without logging email or message.
- 04Treat only 200 and 201 as accepted.
- 05Send a test lead before switching the live form.