Submitting Leads
This guide covers how to submit leads to Ledly via the Vendor API.
API Endpoint
POST https://api.ledly.io/api/leads/inboundFor bulk submissions:
POST https://api.ledly.io/api/leads/inbound/batchAuthentication
Include your API key in the Authorization header:
Authorization: Bearer your_vendor_api_keyNever expose your API key in client-side code, URLs, or logs.
Single Lead Submission
Request
curl -X POST https://api.ledly.io/api/leads/inbound \
-H "Authorization: Bearer your_vendor_api_key" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"first_name": "Jane",
"last_name": "Doe",
"phone": "+1-555-123-4567",
"program_interest": "MBA",
"source": "landing-page-a",
"custom_fields": {
"preferred_start": "Fall 2025",
"time_preference": "Evening"
}
}'Response
Success (201 Created):
{
"success": true,
"lead_id": "lead_abc123xyz",
"message": "Lead accepted",
"timestamp": "2024-12-25T10:30:00Z"
}Validation Error (400 Bad Request):
{
"success": false,
"error": "Validation failed",
"details": [
{ "field": "email", "message": "Invalid email format" },
{ "field": "phone", "message": "Invalid phone number" }
]
}Duplicate (409 Conflict):
{
"success": false,
"error": "Duplicate lead detected",
"duplicate_of": "lead_existing123",
"similarity_score": 95
}Bulk Lead Submission
Submit up to 100 leads in a single request.
Request
curl -X POST https://api.ledly.io/api/leads/inbound/batch \
-H "Authorization: Bearer your_vendor_api_key" \
-H "Content-Type: application/json" \
-d '{
"leads": [
{
"email": "[email protected]",
"first_name": "John",
"last_name": "Smith"
},
{
"email": "[email protected]",
"first_name": "Jane",
"last_name": "Doe"
}
]
}'Response
{
"success": true,
"total": 2,
"accepted": 1,
"rejected": 1,
"results": [
{
"index": 0,
"success": true,
"lead_id": "lead_abc123"
},
{
"index": 1,
"success": false,
"error": "Duplicate lead detected"
}
]
}Field Reference
Standard Fields
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Lead’s email address |
first_name | string | No | First name |
last_name | string | No | Last name |
phone | string | No | Phone number (any format) |
program_interest | string | No | Program of interest |
source | string | No | Lead source identifier |
ip_address | string | No | Lead’s IP address |
user_agent | string | No | Browser user agent |
landing_page | string | No | Landing page URL |
referrer | string | No | Referrer URL |
UTM Parameters
Track marketing attribution:
| Field | Type | Description |
|---|---|---|
utm_source | string | Campaign source |
utm_medium | string | Campaign medium |
utm_campaign | string | Campaign name |
utm_term | string | Campaign term |
utm_content | string | Campaign content |
Custom Fields
Include additional fields in the custom_fields object:
{
"email": "[email protected]",
"custom_fields": {
"preferred_start": "Fall 2025",
"education_level": "Bachelor's",
"years_experience": 5,
"interested_in_scholarship": true
}
}Custom field names must be configured by your organization. Check with your administrator for available fields.
Phone Number Formatting
Phone numbers are automatically normalized. These are all equivalent:
+1-555-123-4567
(555) 123-4567
555.123.4567
5551234567For international numbers, include the country code:
+44 20 7123 4567 (UK)
+49 30 12345678 (Germany)
+61 2 1234 5678 (Australia)Email Validation
Emails are validated for:
- Format - Must be valid email syntax
- Domain - Must have valid MX records
- Disposable - Temporary email services may be blocked
- Typos - Common typos are flagged (gmial.com, etc.)
Disposable Domains (Blocked)
Common blocked domains include:
- mailinator.com
- tempmail.com
- guerrillamail.com
- 10minutemail.com
Disposable email blocking is organization-specific. Some organizations may allow them.
Error Handling
HTTP Status Codes
| Code | Meaning |
|---|---|
| 201 | Lead created successfully |
| 400 | Bad request (validation error) |
| 401 | Unauthorized (invalid API key) |
| 409 | Conflict (duplicate lead) |
| 422 | Unprocessable (lead rejected by rules) |
| 429 | Too many requests (rate limited) |
| 500 | Server error |
Retry Strategy
Implement exponential backoff for retries:
async function submitWithRetry(lead, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await submitLead(lead);
if (response.status === 429) {
// Rate limited - wait and retry
const retryAfter = response.headers.get('Retry-After') || 30;
await sleep(retryAfter * 1000);
continue;
}
if (response.status >= 500) {
// Server error - retry with backoff
await sleep(Math.pow(2, attempt) * 1000);
continue;
}
return await response.json();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
await sleep(Math.pow(2, attempt) * 1000);
}
}
}Tracking Submissions
Request ID
Each request receives a unique ID in the response headers:
X-Request-ID: req_abc123xyzInclude this ID when contacting support about specific submissions.
Checking Lead Status
After submission, you can check lead status:
GET /api/vendor-portal/leads/{lead_id}Response:
{
"id": "lead_abc123",
"status": "accepted",
"submitted_at": "2024-12-25T10:30:00Z",
"processed_at": "2024-12-25T10:30:01Z",
"crm_status": "synced",
"crm_id": "00Q1234567890ABC"
}Best Practices
-
Validate before submitting - Check email format and required fields client-side
-
Handle errors gracefully - Don’t retry 400-level errors (fix the data first)
-
Use bulk endpoint for batches - More efficient for multiple leads
-
Include source tracking - Always include
sourceand UTM parameters -
Log request IDs - Store
X-Request-IDfor debugging -
Monitor acceptance rates - Low rates indicate data quality issues
-
Test in sandbox first - Use the sandbox environment before production
Sandbox Environment
Test your integration without affecting production:
Endpoint: https://sandbox.api.ledly.io/api/leads/inbound
API Key: Your sandbox API key (different from production)Sandbox leads are automatically deleted after 24 hours.