Signup protection Pricing Documentation Sign up Log in

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, or challenge)
  • rule: which rule decided it, including its name and optional message; null when the gate's default action applied
  • signals: the same intelligence as /email and /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:

  1. Inspect action (allow, block, or challenge)
  2. If rule.message is 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].

Previous
Errors