Phone Spam Check
Reported-caller complaints for a phone number from public FTC Do Not Call and FCC consumer complaint data: how many reports, when, and what kind of calls were reported. Complaints are unverified consumer reports, not a verdict about the caller.
POST https://YOUR_API_HOST/v1/phone-spam
Credits and caching
- Success billing. 1 credit per number with complaints on record. You are charged only when the lookup finds something; a not-found result is free.
- Results are cached per account for 7 days. Repeating the same lookup in that window is free and returns
cache_hit: true. - Invalid input, rate limits and errors are never charged.
- Read the billing rules for the full picture.
Request
Send a JSON body with Authorization: Bearer <key>. The body can be up to 64 KB.
| Field | Type | Required | Notes |
|---|---|---|---|
phone | string | yes | Up to 64 characters. |
Example request
curl -X POST https://YOUR_API_HOST/v1/phone-spam \
-H "Authorization: Bearer $LOOKVALID_API_KEY" \
-H "Content-Type: application/json" \
-d '{"phone":"+12025550143"}'
const response = await fetch("https://YOUR_API_HOST/v1/phone-spam", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.LOOKVALID_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"phone": "+12025550143"
}),
});
const result = await response.json();
if (!response.ok) throw new Error(`${result.error.code}: ${result.error.fix}`);
console.log(result.found, result.data, result.credits_charged);
import os
import requests
response = requests.post(
"https://YOUR_API_HOST/v1/phone-spam",
headers={"Authorization": f"Bearer {os.environ['LOOKVALID_API_KEY']}"},
json={
"phone": "+12025550143",
},
timeout=30,
)
result = response.json()
if not response.ok:
raise RuntimeError(f"{result['error']['code']}: {result['error']['fix']}")
print(result["found"], result["data"], result["credits_charged"])
Response
The status is 200 whether or not anything was found. Check found. Every response also carries the X-Request-Id, X-Credits-Charged, X-Credits-Remaining and X-RateLimit-* headers.
Envelope
| Field | Type | Notes |
|---|---|---|
request_id | string | - |
product | "phone-spam" | - |
found | boolean | False means the lookup ran and found nothing; it is free. |
reason | string or null | Why nothing was found; null when found. |
sources | array of string | Where the data came from, including any attribution the licence requires. |
credits_charged | integer | 0 for a cache hit, a not-found result or an error. |
credits_remaining | integer | - |
cache_hit | boolean | True when the account already paid for this lookup within the cache window. |
cache | object | - |
The data object
Present when found is true, otherwise null.
| Field | Type | Notes |
|---|---|---|
phone_e164 | string | - |
complaints_total | integer | Minimum 0. |
first_reported | string or null | - |
last_reported | string or null | - |
issue_types | array of string | Distinct issue types reported, most serious first |
sources_used | array of object | - |
sources_used[].id | string | One of fcc, ftc. |
sources_used[].name | string | - |
sources_used[].complaints | integer | Minimum 0. |
sources_used[].dataset_built_at | string | - |
spam_score | integer | Computed as of cache.cached_at; a cached answer keeps its score, so the recency part can be up to the cache lifetime out of date Minimum 0. Maximum 100. |
risk_level | string | One of none, low, medium, high. |
score_basis | object | - |
score_basis.components | object | - |
score_basis.components.volume | number | - |
score_basis.components.recency | number | - |
score_basis.components.issue_type | number | - |
score_basis.formula | string | - |
disclaimer | string | - |
Captured example (2026-10-18)
A real run of this lookup on 2026-10-18, using the request above, which is a public input. The envelope fields are left out.
{
"product": "phone-spam",
"found": true,
"data": {
"phone_e164": "+12025550143",
"complaints_total": 2,
"first_reported": "2017-03-23",
"last_reported": "2017-03-23",
"issue_types": [
"prerecorded voice",
"unwanted calls"
],
"sources_used": [
{
"id": "fcc",
"name": "FCC Consumer Complaints (opendata.fcc.gov, public domain)",
"complaints": 2,
"dataset_built_at": "2026-10-17T20:41:34.966Z"
}
],
"spam_score": 32,
"risk_level": "medium",
"score_basis": {
"components": {
"volume": 11.9,
"recency": 0,
"issue_type": 20
},
"formula": "spam_score = round(volume + recency + issue_type). volume = 50 * min(1, log10(1 + complaints) / log10(101)). recency = 30 * 0.5^(age_days / 90) where age_days is the age of the newest complaint. issue_type = 20 * weight of the most serious reported issue (1.0 scam, fraud, robocall, prerecorded, spoofing or impersonation; 0.6 telemarketing, sales or other unwanted contact; 0.3 anything else)."
},
"disclaimer": "Complaints are unverified consumer reports and phone numbers can be spoofed, so a score describes how often a number was reported, not who is calling."
},
"sources": [
"FCC consumer complaints, public domain (opendata.fcc.gov)"
]
}
Errors
400invalid_input: The request input is invalid.401unauthorized: The API key is missing, malformed, revoked or unknown.402insufficient_credits: The account does not have enough credits for this lookup. Error code: insufficient_credits. During a trial whose allowance is used up the code is trial_allowance_exhausted: nothing is charged and the customer starts the plan from the billing page.405method_not_allowed: This endpoint does not accept that HTTP method.413payload_too_large: The request body is too large.429rate_limited: Too many requests for this API key.500internal_error: An unexpected error occurred.503provider_unavailable: The data source for this product is unavailable right now.