Overview
The Lead Intake API allows external sources such as landing pages, ad networks, and affiliate systems to submit leads directly into the CRM.
Each API key is tied to a specific office. Leads submitted with a given key are automatically assigned to that office. You can optionally include FTD status and a deposit amount at submission time.
Authentication
All requests to /api/v1/* endpoints must include a valid API key. Pass it using the X-API-Key HTTP header.
POST /api/v1/leads HTTP/1.1
Host: {{ request()->getHost() }}
X-API-Key: YOUR_OFFICE_API_KEY
Content-Type: application/json
Accept: application/json
Alternatively, you may pass the key as a query parameter ?api_key=YOUR_KEY, though the header approach is preferred for security.
Base URL
{{ url('/api') }}
All endpoints are relative to this base URL.
Rate Limiting
API endpoints are limited to 60 requests per minute per IP address. When the limit is exceeded, the API returns HTTP 429 Too Many Requests.
The following headers are included on every response:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Max requests per minute |
| X-RateLimit-Remaining | Remaining requests in the current window |
| Retry-After | Seconds to wait before retrying (on 429) |
Endpoints
Creates a new lead in the CRM assigned to the office corresponding to the API key. The system checks for duplicates by email and phone number before inserting.
Request Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| fname | string | Required | Lead's first name (max 100 chars) |
| lname | string | Required | Lead's last name (max 100 chars) |
| string | Required | Valid email address (max 150 chars). Used for duplicate detection. | |
| phone | string | Required | Phone number including country code, e.g. +14155550123 (max 30 chars). Used for duplicate detection. |
| brand | string | Required | Brand or product the lead signed up for (max 100 chars) |
| country | string | Optional | Lead's country name, e.g. Germany |
| ftd | boolean | Optional | Whether this lead has made a first-time deposit. Defaults to false. |
| amount | number | Optional | Deposit amount associated with the lead (must be ≥ 0) |
Response Codes
{
"success": true,
"message": "Lead created successfully.",
"data": {
"id": 4821,
"fname": "John",
"lname": "Doe",
"email": "john.doe@example.com",
"phone": "+14155550123"
}
}
{
"success": false,
"message": "Lead already exists in the system.",
"existing_by": "email",
"lead_id": 3102
}
{
"success": false,
"message": "Validation failed.",
"errors": {
"email": ["The email field is required."],
"phone": ["The phone field is required."]
}
}
{
"success": false,
"message": "Invalid API key."
}
{
"message": "Too Many Attempts."
}
Code Examples
cURL
curl -X POST "{{ url('/api/v1/leads') }}" \
-H "X-API-Key: YOUR_OFFICE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"fname": "John",
"lname": "Doe",
"email": "john.doe@example.com",
"phone": "+14155550123",
"brand": "TradeMax",
"country": "United States"
}'
PHP (cURL)
<?php
$payload = [
'fname' => 'John',
'lname' => 'Doe',
'email' => 'john.doe@example.com',
'phone' => '+14155550123',
'brand' => 'TradeMax',
'country' => 'United States',
];
$ch = curl_init('{{ url('/api/v1/leads') }}');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
'X-API-Key: YOUR_OFFICE_API_KEY',
'Content-Type: application/json',
'Accept: application/json',
],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status === 201) {
echo 'Lead created: #' . $data['data']['id'];
} elseif ($status === 409) {
echo 'Duplicate lead, existing ID: ' . $data['lead_id'];
} else {
echo 'Error: ' . $data['message'];
}
JavaScript (fetch)
const response = await fetch('{{ url('/api/v1/leads') }}', {
method: 'POST',
headers: {
'X-API-Key': 'YOUR_OFFICE_API_KEY',
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
fname: 'John',
lname: 'Doe',
email: 'john.doe@example.com',
phone: '+14155550123',
brand: 'TradeMax',
country: 'United States',
}),
});
const data = await response.json();
if (response.status === 201) {
console.log('Lead created:', data.data.id);
} else if (response.status === 409) {
console.warn('Duplicate:', data.existing_by, 'Lead ID:', data.lead_id);
} else {
console.error('Error:', data.message, data.errors ?? '');
}
Python (requests)
import requests
response = requests.post(
'{{ url('/api/v1/leads') }}',
headers={
'X-API-Key': 'YOUR_OFFICE_API_KEY',
'Accept': 'application/json',
},
json={
'fname': 'John',
'lname': 'Doe',
'email': 'john.doe@example.com',
'phone': '+14155550123',
'brand': 'TradeMax',
'country': 'United States',
}
)
data = response.json()
if response.status_code == 201:
print(f"Lead created: #{data['data']['id']}")
elif response.status_code == 409:
print(f"Duplicate by {data['existing_by']}, lead #{data['lead_id']}")
else:
print(f"Error: {data['message']}")
Error Format
All error responses follow a consistent JSON structure:
| Field | Type | Description |
|---|---|---|
| success | boolean | Always false on errors |
| message | string | Human-readable error description |
| errors | object | Field-level validation errors (422 only) |
| existing_by | string | email or phone — which field caused the duplicate (409 only) |
| lead_id | integer | ID of the existing lead (409 only) |
Lead Intake API — {{ config('app.name') }} — v1.0