Gates
Migrate from email & domain APIs
The Gates Decision API replaces manual logic built around the /email and /domain endpoints. Instead of pulling raw signals and writing your own logic, you define reusable rules in the dashboard (or with the Management API) and let the gate decide.
This guide shows how to migrate existing integrations in a few steps.
1. Create a gate
Start by creating a gate for the flow you want to protect. See Quickstart for setup.
2. Rebuild your checks as rules
A gate created in the dashboard already blocks disposable, spam, and no-MX domains, and challenges new domains, low-quality TLDs, and VPN traffic. See the starter rules.
Compare them with the logic you apply today after calling /email or /domain. Delete the rules you don't want and add rules for the rest. For example, if you block relay_domain today, add a rule where domain.relay_domain is true, with the action Block.
Gate fields are grouped under email and domain, and some are renamed:
/email or /domain field |
Gate field |
|---|---|
disposable |
email.disposable (for /email) or domain.disposable (for /domain) |
spam |
domain.spam |
relay_domain |
domain.relay_domain |
public_domain |
domain.public_domain |
free_subdomain |
domain.free_subdomain |
blocklisted |
domain.blocklisted |
mx |
domain.mx |
domain_age_in_days |
domain.age_days |
domain_authority |
domain.authority |
tld_trust |
domain.tld_trust |
normalized_email |
email.normalized |
role_account |
email.role_account |
alias |
email.subaddress (the text after +, or null) |
did_you_mean has no gate equivalent. If you suggest typo fixes today, keep calling /email for that.
Rules & Conditions lists every field a condition can use.
3. Update your API integration
Replace your GET /email or GET /domain calls with a POST to your gate's Decision endpoint. Send the input as JSON, from your backend:
curl -X POST "https://api.usercheck.com/v1/gates/{gate_id}/decisions" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "email": "[email protected]" }'
Swap "email" for "domain" if that's your input. The endpoint only accepts JSON: a form-encoded body returns 400 invalid_json.
The response puts the verdict at the top. Shortened here to the fields this migration cares about:
{
"action": "block",
"rule": {
"id": "01k1hxr4m6a2q9w3ztc8gn5b7k",
"name": "Block domains with no MX records",
"message": null
},
"signals": {
"email": { "disposable": false },
"domain": { "mx": false }
}
}
- action: the gate's decision (
allow,block, orchallenge) - rule: which rule decided it, including its name and optional message;
nullwhen the gate's default action applied - signals: the same intelligence as
/emailand/domain, under the field names from step 2
The full response shape is documented on the Decision Endpoint page.
Note
The gate already applies your rules. Use signals for extras such as the normalized address, not to re-implement the decision.
4. Example migration
Before: manual logic
response = requests.get(
f"https://api.usercheck.com/email/{email}",
headers={"Authorization": f"Bearer {API_KEY}"},
)
data = response.json()
if data.get("disposable"):
raise SignUpError("Disposable emails are not allowed.")
if data.get("relay_domain"):
raise SignUpError("Please use your real email address to register.")
normalized_email = data.get("normalized_email", email)
# Proceed with signup
After: gate decision
This assumes your gate has a rule that blocks domain.relay_domain, as in step 2. The starter rules don't include one.
try:
response = requests.post(
f"https://api.usercheck.com/v1/gates/{GATE_ID}/decisions",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"email": email},
timeout=3,
)
data = response.json()
except (requests.RequestException, ValueError):
data = {} # Timeout or network error: let the signup through
if data.get("error") in ("invalid_email", "invalid_domain"):
raise SignUpError("Please enter a valid email address.")
if data.get("action") == "block":
message = (data.get("rule") or {}).get("message")
raise SignUpError(message or "Please use your real email address to register.")
if data.get("action") == "challenge":
tag_account_as_high_risk()
normalized_email = (data.get("signals") or {}).get("email", {}).get("normalized", email)
# Proceed with signup
json= sends the body as JSON with the right Content-Type. Any other error response, such as a bad key or used-up credits, has no action, so this code lets the signup through. Log those responses so the problem doesn't go unnoticed. See Timeouts and failures and Errors.
5. Using rule messages in your UI
rule.message holds the rule's Response message, set in the rule editor. It is null when the rule has none, and the starter rules have none. rule itself is null when no rule matched.
Typical usage:
- Inspect
action(allow,block, orchallenge) - If
rule.messageis set, show it to your user; otherwise show your own text
How you handle that information is entirely up to your application.
6. Need help?
For complex migrations or multi-condition logic, contact [email protected].