API Reference
Overview
LeadFlags scores inbound leads with predefined quality criteria and returns a numeric score, quality level, risk flags, positive signals, and a recommended action.
Content-Type: application/json. No form-encoded or XML.
Use LeadFlags before CRM ingestion to filter low-quality leads, reduce wasted calls, and focus your team on leads that convert.
Try in Postman — import the collection and run requests from Postman.
Base URL
All API requests use this base:
https://leadflags.com
Example: POST https://leadflags.com/api/score-lead/
Endpoint
POST /api/score-lead
Accepts a JSON body with lead data. No required fields; send only what you have.
Headers (required)
| Header | Description |
|---|---|
Content-Type | Must be application/json |
X-API-Key | Your API key (required). Get it from the dashboard. |
X-API-Key is supported.
Body
Arbitrary JSON object—no fixed schema. We detect scoring fields by key aliases (case-insensitive, - and _ treated the same). Unknown keys are stored as metadata and not used for scoring.
| Canonical field | Accepted keys (examples) | Description |
|---|---|---|
| name | name, fullName, first_name, last_name, contact | Contact name (first + last combined if sent separately) |
email, e-mail, mail, emailAddress | Email address | |
| phone | phone, telephone, mobile, phoneNumber, cell | Phone (E.164 or national format) |
| message | message, body, notes, comment, content | Message or notes |
| source | source, utm_source, lead_source, channel | Lead source (e.g. website, campaign) |
| country | country, geo, countryCode, country_iso | Country (ISO2 or name). Consistency only (e.g. phone vs country) |
| job_title | job_title, title, position, role | Job title (optional). Can influence score positively for senior B2B roles. |
| industry | industry, sector, vertical | Industry (optional). Recognized; not used in scoring. |
| company_size | company_size, companysize, size, employees | Company size (optional). Recognized; not used in scoring. |
| website | website, url, homepage, domain | Optional. Used for cross-field check with email. |
| — | Any other key | Returned in unrecognized_fields when present; not used in scoring |
Schema-flexible input, deterministic output
LeadFlags accepts any JSON structure—nested objects, arrays, mixed keys. There is no fixed schema. We detect canonical fields (name, email, phone, message, source, country, etc.) via known key aliases and optional _leadflags_map overrides. Same input with the same detector_version always yields the same normalized result.
Optional mapping override: If your payload uses non-standard keys, you can tell us explicitly with a reserved object _leadflags_map. Map a JSON path or key to a canonical field:
{
"contactEmail": "user@example.com",
"tel": "+34987654321",
"_leadflags_map": {
"contactEmail": "email",
"tel": "phone"
}
}
Only known canonical field names are allowed in the map. If a path is missing, a warning is added and detection continues with key matching or heuristics.
Normalize-only endpoint: POST https://leadflags.comapi/normalize-lead returns the full normalization result (detector_version, canonical_lead, detected_fields, missing_fields, conflicts, warnings) and optionally score/quality/flags. Use it to inspect how we interpreted your payload without changing scoring logic. Query ?debug_mode=1 for full conflict and source_path details.
detected_fields indicates which canonical field was filled, from which path, and by which method (override, key alias match, or heuristic). It does not expose how the score is computed.
Request examples
All examples send the same JSON body and require X-API-Key. Replace YOUR_API_KEY with your key.
curl -X POST "https://leadflags.com/api/score-lead/" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"name":"Alex","email":"alex@fastgrowth-labs.co","phone":"+44 7700 900123","company":"FastGrowth Labs","job_title":"Consultant","industry":"Growth Consulting","company_size":"1-10","country":"UK","source":"facebook_ads","message":"Hi, looking for more info."}'
fetch("https://leadflags.com/api/score-lead/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "YOUR_API_KEY"
},
body: JSON.stringify({
name: "Alex",
email: "alex@fastgrowth-labs.co",
phone: "+44 7700 900123",
company: "FastGrowth Labs",
job_title: "Consultant",
industry: "Growth Consulting",
company_size: "1-10",
country: "UK",
source: "facebook_ads",
message: "Hi, looking for more info."
})
})
.then(res => res.json())
.then(data => console.log(data));
$ch = curl_init("https://leadflags.com/api/score-lead/");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Alex',
'email' => 'alex@fastgrowth-labs.co',
'phone' => '+44 7700 900123',
'company' => 'FastGrowth Labs',
'job_title' => 'Consultant',
'industry' => 'Growth Consulting',
'company_size' => '1-10',
'country' => 'UK',
'source' => 'facebook_ads',
'message' => 'Hi, looking for more info.'
]),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: YOUR_API_KEY'
],
CURLOPT_RETURNTRANSFER => true
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
import requests
response = requests.post(
"https://leadflags.com/api/score-lead/",
headers={
"Content-Type": "application/json",
"X-API-Key": "YOUR_API_KEY"
},
json={
"name": "Alex",
"email": "alex@fastgrowth-labs.co",
"phone": "+44 7700 900123",
"company": "FastGrowth Labs",
"job_title": "Consultant",
"industry": "Growth Consulting",
"company_size": "1-10",
"country": "UK",
"source": "facebook_ads",
"message": "Hi, looking for more info."
}
)
data = response.json()
require 'net/http'
require 'json'
uri = URI("https://leadflags.com/api/score-lead/")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == 'https')
req = Net::HTTP::Post.new(uri)
req['Content-Type'] = 'application/json'
req['X-API-Key'] = 'YOUR_API_KEY'
req.body = {
name: 'Alex',
email: 'alex@fastgrowth-labs.co',
phone: '+44 7700 900123',
company: 'FastGrowth Labs',
job_title: 'Consultant',
industry: 'Growth Consulting',
company_size: '1-10',
country: 'UK',
source: 'facebook_ads',
message: 'Hi, looking for more info.'
}.to_json
res = http.request(req)
data = JSON.parse(res.body)
Response format
On success (200), the response body includes score, quality, risk flags, positive signals, and recommended action. Example:
{
"score": 73,
"quality": "high",
"risk_flags": ["random_name"],
"positive_signals": ["quality_message", "business_domain_detected"],
"recommended_action": "standard_priority"
}
| Field | Type | Description |
|---|---|---|
score | number | 0–100. Higher = better quality. |
quality | string | strong | high | medium | low |
risk_flags | string[] | Signals that reduced the score (e.g. missing_phone, disposable_email, random_name). See Flags reference. |
positive_signals | string[] | Trust signals (e.g. business_domain_detected, consistent_identity, quality_message). |
recommended_action | string | high_priority | standard_priority | review | discard. CRM-agnostic; use to route or prioritize. |
unrecognized_fields | string[] (optional) | Top-level keys from the request that were not recognized. Only present when non-empty. |
detection_meta | object (optional) | Technical metadata about how fields were detected. Only when ?detection_meta=1 (or in /api/normalize-lead when score is included). |
Example response with detection_meta:
{
"score": 73,
"quality": "high",
"risk_flags": ["random_name"],
"positive_signals": ["quality_message", "business_domain_detected"],
"recommended_action": "standard_priority",
"detection_meta": {
"email": { "source": "contactEmail", "method": "explicit_map" },
"phone": { "source": "tel", "method": "synonym" }
}
}
Quality levels & recommended actions
Score is mapped to a quality level and a generic recommended action (customizable via ?mid=70&low=40 for Enterprise):
| Quality | Recommended action |
|---|---|
strong | high_priority |
high | standard_priority |
medium | review |
low | discard |
Use recommended_action to route leads in your CRM or workflow. Actions are CRM-agnostic (no call-specific wording).
Flags reference
The API returns two lists: risk_flags (signals that reduce the score) and positive_signals (trust signals). Internal detection details (e.g. which key was used for a field) are not exposed in the response. Below are the semantic flags you may see; relative severity is implied by their presence, not by numeric weights.
Name
| Flag | Description | Typical cause | Severity |
|---|---|---|---|
missing_name | Name not provided or empty | Field omitted or blank | Medium |
very_short_name | Name too short (e.g. single letter) | Placeholder or typo | Medium |
numeric_name | Name is mostly numbers | Test data or fake | High |
random_name | Name looks random (e.g. jumbled chars) | Spam or test | High |
| Flag | Description | Typical cause | Severity |
|---|---|---|---|
missing_email | Email not provided or empty | Field omitted | High |
invalid_email | Email format invalid | Typo or invalid format | High |
disposable_email | Disposable/temp email domain | One-time email services | High |
free_email_domain | Free provider (Gmail, Yahoo, etc.) | Personal vs business | Low |
role_email | Role-based address (info@, support@) | Generic mailbox | Medium |
Phone
| Flag | Description | Typical cause | Severity |
|---|---|---|---|
missing_phone | Phone not provided or empty | Field omitted | High |
invalid_phone | Phone format invalid | Wrong format or typo | Critical |
too_short_phone | Too few digits | Incomplete number | High |
repeated_digits_phone | Mostly repeated digits (e.g. 1111111) | Placeholder or fake | Medium |
fake_phone_pattern | Obvious fake pattern (e.g. all same digit, long sequential) | Test or placeholder number | High |
unlikely_phone_length_for_country | Digit count doesn’t match typical length for country | e.g. ES with 7 digits, US with 9 — suspicious, not invalid | Low |
Message
| Flag | Description | Typical cause | Severity |
|---|---|---|---|
missing_message | Message not provided or empty | Field omitted | Medium |
short_message | Message too short | Low intent or placeholder | Medium |
generic_message | Generic or template-like text | Copy-paste or bot | Medium |
spam_keywords | Spam-like wording detected | Spam or abuse | High |
suspicious_message | Message looks like junk, spam, or very low quality | URLs, spam terms, or gibberish | High |
Source
| Flag | Description | Typical cause | Severity |
|---|---|---|---|
missing_source | Source not provided or empty | Field omitted | Low |
test_source | Source indicates test (e.g. "test", "demo") | Test submission | High |
unknown_source | Source not in known list | New or custom source | Low |
Country
| Flag | Description | Typical cause | Severity |
|---|---|---|---|
missing_country | Country/geo not provided | Field omitted | Low |
invalid_country | Country value could not be normalized | Typo, unknown code, or language code (e.g. EN is English, not a country—use GB for UK) | Low |
country_phone_mismatch | Phone prefix does not match country | Wrong country or fake phone | High |
EN, ES) instead of a country code, we flag invalid_country. We may still infer a country from the phone prefix for consistency checks (e.g. country: "EN" + phone: "+31612345678" → we use NL for phone/country checks, so we do not flag country_phone_mismatch). If there is no phone, we infer a default from the language code (e.g. EN → GB) for internal consistency only; the flag invalid_country remains.
Cross-field & consistency
These flags compare two or more fields. They reduce confidence but do not invalidate the lead by themselves.
| Flag | Description | Typical cause | Severity |
|---|---|---|---|
name_email_mismatch | Name looks real but email local-part is random or initials don’t match | e.g. "Juan Pérez" + asdf123@gmail.com | Medium |
email_domain_website_mismatch | Personal email (Gmail, etc.) but website is a company domain | e.g. juan@gmail.com + empresa-industrial.com — often unserious or freelance | Medium |
source_email_inconsistency | Paid source (LinkedIn, Google Ads) but disposable or role email | Paid leads usually don’t use temp/role emails — reasonable suspicion | Medium |
Positive signals (positive_signals)
These appear in positive_signals and indicate trust or quality signals.
| Signal | Description |
|---|---|
business_domain_detected | Email is from a non-free, non-disposable domain (business email). |
consistent_identity | Name, email, phone, country and message look coherent and complete. |
quality_message | Message has sufficient length and clear intent (no spam/generic copy). |
commercial_intent | Message suggests buying or commercial interest. |
?detection_meta=1 or the normalize-lead endpoint if you need technical visibility into field detection.
Thresholds, rate limits & support
Thresholds
Thresholds define how score ranges map to quality and recommended action. Custom thresholds are available for Enterprise.
Custom thresholds (Enterprise)
Pass query parameters mid=X and low=X to set your own cutoffs (e.g. ?mid=70&low=40).
Optional query parameters
| Parameter | Description |
|---|---|
mid, low | Score thresholds (see above). |
detection_meta=1 | Include detection_meta in the response (how each canonical field was detected: source path and method). Useful for integrators and QA. Example: POST /api/score-lead?detection_meta=1. |
Rate limit
How many requests you can make per minute. Examples: Starter 60 req/min, Growth 300 req/min, Scale 1,000 req/min. This protects the server, prevents abuse, and prioritizes premium clients.
Priority processing
When there's load: Starter uses the normal queue; Growth and Scale use the fast queue. At first this can be symbolic, but it sends a clear value message.
Support
Email support: We reply when we can. No guaranteed response times.
Fast / priority support: Response in < 24h. Priority over Starter. You don't need a full support team for this.
IP allowlist (Enterprise)
Only traffic from authorised IPs is accepted. Clients ask for it for security and compliance.
Early access to new features
Growth (and similar) see new features first, help validate them, and feel premium.
Errors
Error responses use standard HTTP status codes and a JSON body with error and optional message.
400 Bad Request — invalid_json
Request body is not valid JSON.
{
"error": "invalid_json"
}
401 Unauthorized
Missing or invalid X-API-Key header.
{
"error": "Unauthorized",
"message": "Valid X-API-Key header required"
}
405 Method Not Allowed
Endpoint only accepts POST.
{
"error": "Method Not Allowed"
}
Response includes Allow: POST header.
200 OK
Success. Body contains score, quality, risk_flags, positive_signals, recommended_action.
Best practices
- Send only what you have. Don’t invent or guess data. Empty or omitted fields are scored accordingly.
- Use LeadFlags before CRM ingestion. Score at the gate so you don’t store or process low-quality leads.
- Log score and flags in your system. Keep a record for routing, analytics, and auditing.
- Treat
recommended_actionas a suggestion. Usehigh_priority,standard_priority,review, ordiscardto route leads in your CRM or workflow.