TrustScore API
One API call, one verdict. Send a phone, email, and/or IP —
get back allow, review,
or block, plus the signals behind it.
Authentication
Every request to /v1/check needs a Bearer key:
Authorization: Bearer <your-api-key>
A missing or invalid key returns 401. The MVP issues one static key per account — request one here.
Try it live
No key yet? This box hits the public, unauthenticated demo endpoint. Have a key? Paste it in and it calls the real /v1/check endpoint instead.
live tester
POST /v1/check
Validate any combination of a phone number, email address, and IP address in one call.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
phone | string | No* | Any format libphonenumber-js can parse. International format is most reliable. |
email | string | No* | Email address to validate. |
ip | string | No* | IPv4 address. IPv6 is accepted but not checked against the hosting/VPN list. |
* At least one of the three is required — omitting all three returns 400.
curl -X POST https://trustscore.titung.com/v1/check \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+14155552671",
"email": "someone@example.com",
"ip": "203.0.113.42"
}'
Example response — 200 OK
{
"verdict": "allow",
"score": 10,
"signals": {
"phone": { "valid": true, "country": "US", "line_type": "MOBILE", "risk": "low" },
"email": { "valid": true, "disposable": false, "has_mx": true, "risk": "low" },
"ip": { "valid": true, "country": "US", "hosting": false, "vpn": false, "risk": "low" }
}
}
The signals object only contains keys for the fields you sent.
Response fields
| Field | Type | Description |
|---|---|---|
verdict | allow / review / block | The single decision to act on. |
score | 0–100 | Underlying risk score. Higher = riskier. |
signals.phone.valid | boolean | Whether it's a real, parseable number. |
signals.phone.country | string | null | ISO country code, if determinable. |
signals.phone.line_type | string | e.g. MOBILE, FIXED_LINE_OR_MOBILE, VOIP, PAGER, unknown. |
signals.phone.risk | low / medium / high | Per-signal risk before combining. |
signals.email.valid | boolean | Syntactically valid address. |
signals.email.disposable | boolean | Known disposable/burner domain. |
signals.email.has_mx | boolean | Domain has mail-exchange DNS records. |
signals.email.risk | low / medium / high | Per-signal risk before combining. |
signals.ip.valid | boolean | Syntactically valid IP. |
signals.ip.country | string | null | GeoIP country, if available. |
signals.ip.hosting | boolean | Falls in a known cloud/hosting range. |
signals.ip.vpn | boolean | Currently mirrors hosting — see Limitations. |
signals.ip.risk | low / medium / high | Per-signal risk before combining. |
How the verdict works
Each signal you send contributes a risk score (low = 10, medium = 55, high = 95). The final score averages only the signals you sent — an omitted signal is excluded, not treated as safe.
Errors
400 Bad Request — no signal fields present, or a field isn't a string:
{ "error": "at least one of phone, email, or ip is required" }
401 Unauthorized — missing or invalid API key:
{ "error": "unauthorized" }
Code examples
JavaScript
const res = await fetch("https://trustscore.titung.com/v1/check", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.TRUSTSCORE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ email: "someone@example.com" }),
});
const { verdict, score } = await res.json();
if (verdict === "block") {
// reject the signup
}
Python
import os, requests
res = requests.post(
"https://trustscore.titung.com/v1/check",
headers={"Authorization": f"Bearer {os.environ['TRUSTSCORE_API_KEY']}"},
json={"email": "someone@example.com"},
)
verdict = res.json()["verdict"]
Limitations (MVP)
- Disposable-email and hosting/VPN-range lists are starter lists, not exhaustive or authoritative.
- IP checks are IPv4-only. An IPv6 address is accepted as syntactically valid but returns
hosting: false, vpn: falseregardless of origin — treat it as "unknown," not "confirmed clean." vpncurrently mirrorshosting— no distinct VPN-provider feed yet.- No self-serve key issuance or usage metering yet — one static key per account, issued manually.
- No SLA or rate limit on the authenticated endpoint yet (the public demo endpoint above is rate-limited).
Questions, a bug, or need a key? hello@titung.com