Lead Intake API v1

{{ url('/api') }}

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.

Note: To obtain an API key for your office, contact the system administrator. Each office has one unique key visible in the CRM office settings.

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:

HeaderDescription
X-RateLimit-LimitMax requests per minute
X-RateLimit-RemainingRemaining requests in the current window
Retry-AfterSeconds to wait before retrying (on 429)

Endpoints

POST /v1/leads Submit a new lead

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)
email 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

201 Created
Lead was created successfully.
{
    "success": true,
    "message": "Lead created successfully.",
    "data": {
        "id": 4821,
        "fname": "John",
        "lname": "Doe",
        "email": "john.doe@example.com",
        "phone": "+14155550123"
    }
}
409 Conflict
A lead with the same email or phone already exists.
{
    "success": false,
    "message": "Lead already exists in the system.",
    "existing_by": "email",
    "lead_id": 3102
}
422 Unprocessable
One or more required fields are missing or invalid.
{
    "success": false,
    "message": "Validation failed.",
    "errors": {
        "email": ["The email field is required."],
        "phone": ["The phone field is required."]
    }
}
401 Unauthorized
API key is missing or invalid.
{
    "success": false,
    "message": "Invalid API key."
}
429 Too Many Requests
Rate limit exceeded. Wait and retry.
{
    "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:

FieldTypeDescription
successbooleanAlways false on errors
messagestringHuman-readable error description
errorsobjectField-level validation errors (422 only)
existing_bystringemail or phone — which field caused the duplicate (409 only)
lead_idintegerID of the existing lead (409 only)

Lead Intake API — {{ config('app.name') }} — v1.0