Signals API documentation
Check an email address and/or IP against the Sender Registry network in a single request, and decide who to let through at signup, checkout or first login. Start free to get a key.
Overview
One GET request returns up to ten independent signals about an email, an IP and a domain. Each is honest about how strong it is, so you decide the policy. Our own recommendation, and the policy we run on our own signup forms:
- Hard-block on
disposableandknown_malicious. These are rarely a false positive. - Flag or review on
risky_tld,bulk_sender,sentinel,ip_reputation,tor_exit,spoofableandrole_account. Adomain_reputationhit is strong enough to block on: it means somebody has reported that domain as a scam site. A risky TLD in particular is a deliberately weak signal (real businesses use.xyz,.topand the like), so blocking on it alone turns away legitimate users. Asentinelhit withconfirmed: trueis strong enough to block on, as is anip_reputationhit where a good number of sources agree.
Privacy: we do not store the email addresses or IPs you check. Only usage metadata (which key, when, the endpoint, the response status) and non-personal aggregate counts are kept. You can safely send real user data.
Base URL & authentication
All requests go to https://www.senderregistry.com/api/v1 over HTTPS. Authenticate with your secret key as a Bearer token. Every key carries the check:read scope, which is what the check endpoints require. Keep keys server-side; never expose them in client code.
curl "https://www.senderregistry.com/api/v1/check?email=foo@mailinator.com&ip=185.220.101.1" \
-H "Authorization: Bearer sr_live_your_key_here"
- Managing keys: create, rotate and revoke keys in your console. A secret is shown once at creation and never again; rotate if you suspect a leak.
- Expiry: a key can be given an optional expiry date. An expired key returns
401. - Environments: keys are marked live or test at creation.
Endpoints
The three check endpoints are GET requests and take query parameters. The payee-check endpoint is a POST with a JSON body, documented in its own section below.
GET /v1/check | Pass email, ip and/or domain. Returns every signal your plan includes for whichever you supplied. |
GET /v1/check/email | Pass email. Email-based signals only. |
GET /v1/check/ip | Pass ip. The Tor exit signal only. |
POST /v1/payee-check | Pass a JSON body { value, type? }. Is this money target a network-confirmed fraud payee? Growth and above. See below. |
Parameters
email | A single email address. Must be a valid address or the request returns 400. |
ip | A single IPv4 or IPv6 address. Must be valid or the request returns 400. |
domain | A single domain name, for the spoofability signal. Must be a valid domain or the request returns 400. When you pass email, the domain is taken from it automatically, so domain is only needed to check a bare domain on its own. |
On /v1/check you must supply at least one of email, ip or domain. The response only contains the signals relevant to what you sent.
Response
A 200 returns one key per applicable signal. Commodity signals are a boolean; reputation signals are an object. A signal your plan does not include is replaced by { "upgrade_required": true }, so you can see what a higher tier would add.
{
"disposable": true,
"tor_exit": true,
"risky_tld": false,
"bulk_sender": { "flagged": false },
"known_malicious": { "flagged": true, "first_seen": "2026-07-14", "network_confirmation": "CORROBORATED" },
"sentinel": { "flagged": true, "catch_class": "definitive", "breadth": 2, "confirmed": false },
"ip_reputation": { "flagged": true, "sources_agreeing": 4 },
"domain_reputation": { "flagged": true, "first_seen": "2026-06-02" },
"spoofable": { "spoofable": true, "grade": "D", "spf": "soft", "dmarc": "none" },
"mx": { "valid": false, "reason": "no_dns" },
"role_account": { "flagged": true, "role": "info", "category": "shared" }
}
disposable | Boolean. The email is on a throwaway or temporary domain, from a continuously maintained list. Genuine privacy relays (iCloud Hide My Email, DuckDuckGo and similar) are allowlisted, so a real forwarding address is never flagged. Strong signal: rarely a legitimate signup. |
tor_exit | Boolean. The IP is a current Tor network exit node (published by the Tor Project). A signal for traffic deliberately anonymised to hide its origin. |
risky_tld | Boolean. The email is on a small, curated set of top-level domains disproportionately used for scams (.zip, .top and the like). A weak signal on its own; weigh it alongside the others. |
bulk_sender | Object. flagged is true when the sender or its domain is a network-confirmed bulk source; when flagged it also carries confirmations (how many independent organisations corroborated it) so you can set your own threshold. |
known_malicious | Object. flagged is true when the sender or domain has been confirmed malicious through real reported phishing and fraud; when flagged it also carries first_seen and, for a domain, network_confirmation. This is intelligence a free public list cannot give you. |
sentinel | Object. flagged is true when the IP was caught behaving maliciously across the Sentinel Network, our own network of monitored web properties seeded with decoy traps a real visitor never sees. First-sighting intelligence: it can flag an attacker before the IP is on any public list. When flagged it also carries catch_class (definitive for a hard trap, ambiguous for a softer signal), breadth (how many of our sites caught it), and confirmed (true once an admin has verified it as attacker infrastructure, the strongest form). IP-based, so pass the ip. Growth and above. |
ip_reputation | Object. flagged is true when the IP appears on a pool of addresses seen behaving badly, aggregated from many independent abuse blocklists and rebuilt daily. When flagged it carries sources_agreeing, the number of those blocklists that flagged it: one source is an opinion, seven independent sources agreeing is close to a fact, so use the number to decide whether to block or merely review. Distinct from sentinel, which is our own traps catching an attacker directly, so an address can appear on one and not the other. IP-based, so pass the ip. Starter and above. |
domain_reputation | Object. flagged is true when the domain appears on a published list of websites reported as scams, refreshed daily. There is no sources_agreeing here and that is deliberate rather than missing: the list carries no score, so a domain is on it or it is not, and a number would imply a corroboration nobody measured. When flagged it carries first_seen, the date we first saw it listed, because a domain listed for months reads very differently from one listed yesterday. Domain-based, so pass the domain or an email we can take it from. Starter and above. |
spoofable | Object. spoofable is true when email can be forged from the domain, that is, when it does not publish and enforce SPF and DMARC. Also carries the domain's A to F security grade and the spf and dmarc states (for example reject, quarantine, none, missing). From a live DNS check cached for 24 hours; the domain comes from the domain parameter or the email's domain. |
mx | Object. valid is false only when the domain genuinely cannot receive email, with reason giving no_dns (nothing published) or null_mx (the domain explicitly declares it accepts no mail, per RFC 7505). valid is true for mx_found, and also for a_record_fallback, where there is no MX but an A record: mail servers do fall back to it, so rejecting a signup on that alone would turn away deliverable addresses. This is a domain check, not a mailbox check. Proving a specific mailbox exists needs an SMTP probe, which we deliberately do not do. |
role_account | Object. flagged is true for a shared team mailbox or a send-only address, with role giving the matched local part and category either shared (info@, sales@, support@) or no_reply (noreply@, donotreply@). A quality signal, not a risk one: legitimate small businesses sign up as info@ all the time. The categories differ in practice, because a send-only address cannot receive a confirmation email at all. A plus-tag is ignored, so info+signup@ matches info. |
Fraud payee check (POST)
Answers whether a money target (a bank account, crypto wallet, phone or callback number, or payment handle) is a network-confirmed fraud payee, so you can verify a payee inside your own payment or onboarding flow before money moves. Requires the signal_payee_check entitlement (Growth and above); without it the request returns 403. Uses the same check:read scope, rate limit and monthly quota as the check endpoints.
No-store: the value is turned into a one-way fingerprint and matched against the registry, then discarded. It is never stored or logged. The sighting count in the response is banded into a range, so a raw number can never fingerprint one source.
curl -X POST "https://www.senderregistry.com/api/v1/payee-check" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"value": "GB29 NWBK 6016 1331 9268 19", "type": "bank_account"}'
{
"flagged": true,
"status": "confirmed",
"entity_type": "bank_account",
"fraud_sightings": "2-5",
"first_seen": "2026-08-21"
}
value | Required. The payee to check: an IBAN or UK sort code and account, a Bitcoin or Ethereum wallet, a phone number, or a payment or messaging handle. |
type | Optional. One of bank_account, crypto_wallet, phone, payment_handle, messaging_handle. Omit to auto-detect. A value that cannot be recognised as a payee returns 400. |
flagged | Boolean. True when the payee is on the registry as confirmed or reported. |
status | confirmed (network-confirmed fraud, do not pay), reported (seen in suspicious emails, not yet confirmed), or clean (not on the registry, never a guarantee of safety). |
fraud_sightings | A banded range (1, 2-5, 6-20, 20+) when flagged, else null. |
first_seen | The date the payee was first seen on the network, or null. |
Rate limits & quota
Two separate limits apply:
- Per-key rate limit: an optional requests-per-hour ceiling you set on each key. Exceeding it returns
429for that key. - Monthly quota: your plan includes a monthly check allowance, shared across all of your keys and counting only successful
200checks. When you reach it, checks return429until it resets on the 1st.
Track both in your console usage page.
Using the data
Short version: use it to protect your own product, as much as you like, on as many of your own sites as you like. Do not rebuild the data somewhere else.
- Use it freely inside your own products. Screening signups, checking a payee before money moves, scoring a contact form, flagging a support ticket. Several sites and several environments under one account are all fine, and you never need to credit us for any of it.
- Cache a result if it helps. Caching for a day or so is sensible, and we would rather you did that than burn quota re-asking the same question. What we ask is that a cache stays a cache rather than growing into a copy of the underlying lists.
- Please do not bulk-extract or redistribute. Enumerating the API to reconstruct our data, republishing responses as a feed or a blocklist, or reselling access as your own service. Rate limits already make the first impractical; this says it plainly rather than relying on them.
- Please do not pass a verdict off as your own intelligence. Using it inside your product is the whole point. Handing a result to a third party as though your own network produced it is a different thing.
- Some of it is not ours to relicense. Several signals draw on public lists whose own terms travel with them, so a response can carry facts we are not free to grant you redistribution rights over. That is the real reason behind the two points above rather than a preference.
Credit is welcome and never required. If you would like to show it, the console has a self-contained website badge: a small mark telling your own users that you screen every signup. It loads no scripts and tracks nobody.
One thing worth saying plainly: a verdict is evidence for your decision rather than the decision itself. Every signal tells you what we know and how strongly, so where the answer is weak, treat it as a reason to look closer rather than grounds to turn a real customer away. Best practices below says which are which.
Errors
Errors return the matching HTTP status and a JSON body with an error message.
400 | No valid email or ip supplied, or one of them is malformed. |
401 | Missing, invalid, expired or disabled key, or a key without the check:read scope. |
429 | The key's hourly rate limit or your plan's monthly quota was reached. A quota 429 includes quota and used. |
Code examples
curl
curl "https://www.senderregistry.com/api/v1/check?email=foo@mailinator.com&ip=185.220.101.1" \
-H "Authorization: Bearer sr_live_your_key_here"
PHP
$ch = curl_init("https://www.senderregistry.com/api/v1/check?email=" . urlencode($email) . "&ip=" . $ip);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer " . $KEY]);
$data = json_decode(curl_exec($ch), true);
if (!empty($data["disposable"]) || !empty($data["known_malicious"]["flagged"])) {
// reject the signup
}
Python
import requests
r = requests.get(
"https://www.senderregistry.com/api/v1/check",
params={"email": email, "ip": ip},
headers={"Authorization": "Bearer " + KEY},
timeout=5,
)
data = r.json()
if data.get("disposable") or data.get("known_malicious", {}).get("flagged"):
reject_signup()
Node
const res = await fetch(
`https://www.senderregistry.com/api/v1/check?email=${encodeURIComponent(email)}&ip=${ip}`,
{ headers: { Authorization: `Bearer ${KEY}` } }
);
const data = await res.json();
if (data.disposable || data.known_malicious?.flagged) {
rejectSignup();
}
Best practices
- Fail open. If the API times out or errors, let the user through rather than blocking your own signup flow. Availability should never gate your funnel. Set a short timeout (a few seconds).
- Cache short-term. A given email or IP rarely changes verdict within a session, so caching for a short window saves quota.
- Block the strong signals, review the weak ones. Hard-block
disposable,known_malicious,domain_reputationand anmxofno_dnsornull_mx, since that address cannot receive mail at all; sendrisky_tld,bulk_sender,sentinel,tor_exit,spoofableandrole_accountto review or step-up verification rather than an outright block. Treatip_reputationby itssources_agreeingcount rather than as a plain true or false: a single source is worth a review, several independent sources agreeing is worth a block. - Send real data. We discard the values you check, so you do not need to hash or redact the email or IP first.
Changelog
- 27 Aug 2026: added the
domain_reputationsignal (a domain on a published list of reported scam sites, with how long it has been listed). Domain-based; available from Starter upward. - 26 Aug 2026: added the
ip_reputationsignal (an IP on multiple independent abuse blocklists, with how many sources agree). IP-based; available from Starter upward. - 21 Aug 2026: added the
sentinelsignal (Sentinel Network: an IP caught behaving maliciously across our own monitored properties). IP-based; available from Growth upward. - 19 Aug 2026: added the
mxandrole_accountsignals. Both available from Starter upward. - 9 Aug 2026: added the
spoofablesignal (domain spoofability, with the domain's A to F grade) and thedomainparameter. Free on every tier. - v1 launch: the
/v1/check,/v1/check/emailand/v1/check/ipendpoints and the five signals (disposable, Tor exit, risky TLD, bulk-sender reputation, known-malicious reputation).