Marketing Leads
Public landing-form intake and the commercial pipeline list
Marketing Leads
The previous deployment had no /api/v1/leads: its landing page wrote the leads collection directly, through POST /api/collections/leads/records, and the commercial portal listed the same collection. Both 404 on this stack. These two routes are the replacement.
The leads table has no organization_id. It is platform-wide marketing intake, not a tenant collection.
Quick Reference
| Endpoint | Auth | Purpose |
|---|---|---|
POST /api/v1/leads | No | Landing contact form |
GET /api/v1/leads | Commercial | Pipeline list, newest first, cap 500 |
The Lead Object
{
"id": "abc123xyz789def",
"name": "Ada Lovelace",
"email": "ada@example.com",
"phone": "+966500000000",
"position": "CTO",
"plan": "Flat Rate (interest)",
"erp": "",
"contact_reason": "Website: Get in Touch",
"notes": "Company: DFS",
"created": "2026-09-10 10:00:00.000Z"
}Fields
| Field | Type | Description |
|---|---|---|
id | string | Column default (pb_new_id()) |
name | string | Required on create. Compared untrimmed — whitespace-only is accepted |
email | string | Required first. Must look like local@host.tld |
phone | string | Optional |
position | string | Optional |
plan | string | Optional interest plan label from the landing form |
erp | string | Optional; JSON null stores as empty |
contact_reason | string | Optional landing-form reason |
notes | string | Optional; JSON null stores as empty |
created | string | Column default timestamp |
Create a Lead
Public. No token. This is what the React landing createLead posts.
POST /api/v1/leadscurl -X POST "https://api.linkit.works/v1/leads" \
-H "Content-Type: application/json" \
-d '{
"name": "Ada Lovelace",
"email": "ada@example.com",
"phone": "+966500000000",
"position": "CTO",
"plan": "Flat Rate (interest)",
"erp": null,
"notes": "Company: DFS",
"contact_reason": "Website: Get in Touch"
}'const lead = await fetch("/api/v1/leads", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "Ada Lovelace",
email: "ada@example.com",
phone: "+966500000000",
position: "CTO",
plan: "Flat Rate (interest)",
erp: null,
notes: "Company: DFS",
contact_reason: "Website: Get in Touch",
}),
}).then((r) => r.json());Response 200
The stored row (same shape as the object above).
Validation order
The same ladder as Go validateLeadRecord / hooks::leads:
| Order | Condition | Message |
|---|---|---|
| 1 | email empty | missing required field: email |
| 2 | email not local@host.tld | invalid email format |
| 3 | name empty | missing required field: name |
Store failure is failed to create lead. Error bodies carry the platform envelope {data, message, status} with Sentenize on message.
Do not post /api/collections/leads/records against this host. The collection API is not served.
List Leads
Commercial-gated. Merchant JWTs receive 403. Newest first, hard cap 500 (COMMERCIAL_MAX_FETCH_SIZE).
GET /api/v1/leadsResponse 200
{
"leads": [
{
"id": "abc123xyz789def",
"name": "Ada Lovelace",
"email": "ada@example.com",
"phone": "+966500000000",
"position": "CTO",
"plan": "Flat Rate (interest)",
"erp": "",
"contact_reason": "Website: Get in Touch",
"notes": "Company: DFS",
"created": "2026-09-10 10:00:00.000Z"
}
]
}The envelope is {leads:[…]} so the React commercial pipeline does not guess a collection path. List failure is failed to list leads.