Signup protection Pricing Documentation Sign up Log in

Gates

Decision endpoint

Use the Decision endpoint to evaluate a gate in real time. It is the only Gates endpoint that consumes credits.

Endpoint

POST /v1/gates/{gate_id}/decisions

gate_id is the id shown in the dashboard, and returned by the Management API. The key must belong to the gate's environment; a gate in another one returns 404 gate_not_found.

Authentication

Include your API key in the Authorization header:

Authorization: Bearer YOUR_API_KEY

Call the endpoint from your backend, never from the browser. The key is secret, and the endpoint does not send CORS headers, so browsers block the call anyway.

Request body

Send a JSON object with Content-Type: application/json. A form-encoded or multipart body returns 400 invalid_json.

curl -X POST "https://api.usercheck.com/v1/gates/01k2wjgq8rnv6t0aefy3s7xdhb/decisions" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]"}'

Send either email or domain, not both. A request carrying neither returns 422 missing_input, and one carrying both returns 422 both_email_and_domain_provided. Leave out the field you don't use: sending email or domain as null or an empty string returns 422 validation_failed.

{
  "email": "[email protected]",
  "ip": "1.1.1.1" // optional
}

or

{
  "domain": "github.com",
  "ip": "1.1.1.1" // optional
}

ip is optional. It adds IP intelligence to the signals and costs 2 extra credits. Send a single IPv4 or IPv6 address, without a port, CIDR prefix, or forwarded-address list. A malformed value returns 422 invalid_ip before a decision is made or credits are charged. Omitting ip or sending null, an empty string, or whitespace treats it as absent: no signals.ip and no extra credits.

Example response

The decision comes back as a flat object: the verdict at the top, the evidence under it.

{
  "id": "01k1skm0zdceees5qa9n3m5epx",
  "gate_id": "01k2wjgq8rnv6t0aefy3s7xdhb",
  "action": "allow",
  "rule": {
    "id": "01k1hxqrn2f7xfreq20y0p012d",
    "name": "Allow professional email providers",
    "message": null
  },
  "input": {
    "email": "[email protected]",
    "ip": "1.1.1.1"
  },
  "signals": {
    "email": {
      "address": "[email protected]",
      "normalized": "[email protected]",
      "domain": "github.com",
      "local_part": "octocat",
      "subaddress": null,
      "disposable": false,
      "role_account": false
    },
    "domain": {
      "name": "github.com",
      "authority": 96,
      "tld_trust": 1,
      "tld": "com",
      "sld": "github",
      "subdomain": null,
      "age_days": 6871,
      "mx": true,
      "mx_records": [
        { "hostname": "github-com.mail.protection.outlook.com", "priority": 0 }
      ],
      "mx_providers": [
        { "slug": "microsoft", "type": "mailbox", "grade": "professional" }
      ],
      "spf": "relaxed",
      "dmarc": "quarantine",
      "disposable": false,
      "public_domain": false,
      "relay_domain": false,
      "free_subdomain": false,
      "spam": false,
      "blocklisted": false
    },
    "ip": {
      "address": "1.1.1.1",
      "abuse": {
        "detected": false
      },
      "anonymity": {
        "proxy": {
          "detected": false
        },
        "relay": {
          "detected": false
        },
        "tor": {
          "detected": false
        },
        "vpn": {
          "detected": false
        }
      },
      "geo": {
        "city": "Hong Kong",
        "country": "Hong Kong",
        "country_code": "HK",
        "continent": "Asia",
        "continent_code": "AS"
      },
      "hosting": {
        "detected": true
      },
      "network": {
        "asn": 13335,
        "aso": "Cloudflare, Inc.",
        "domain": "cloudflare.com",
        "type": "hosting"
      }
    }
  },
  "duration_ms": 52.62,
  "created_at": "2026-08-18T03:57:18+00:00"
}
  • action is the verdict: allow, block, or challenge.
  • rule is the rule that decided it, including its optional user-facing message. It is null when no rule matched and the gate's default_action applied.
  • signals holds the email, domain, and IP intelligence the rules were evaluated against. signals.email is only present when the request sent an email, and signals.ip only when it sent a valid ip. Some fields only appear when they apply, such as domain.disposable_provider, and some are null when the data is unknown, such as domain.age_days. Rules & Conditions lists every field.
  • id identifies the decision. It is also its handle in the decision log, so you can read the decision back later with the Management API.

Which rule produced action, and what happens when several match, is covered in Rules & Conditions.

Error responses

Errors return the matching HTTP status code and a JSON body with a stable error code and a human-readable message:

{
  "error": "gate_not_found",
  "message": "Gate not found."
}

Codes specific to this endpoint:

  • 404 gate_not_found: Unknown gate_id, or a gate outside the key's environment
  • 422 missing_input: Neither email nor domain provided
  • 422 both_email_and_domain_provided: Both email and domain were provided
  • 422 invalid_email: The email format or its domain is invalid
  • 422 invalid_domain: The domain format or suffix is invalid
  • 422 invalid_ip: The IP address format is invalid

If an address or domain is well formed but its suffix is not a public top-level domain (such as .test, .internal, or .corp), the code is still invalid_email or invalid_domain, and the message reads "Domain has an unknown or reserved top-level domain".

It can also return the codes every Gates endpoint shares, including:

  • 400 invalid_json: The body is not JSON, or is sent without Content-Type: application/json
  • 401 unauthorized: The API key is missing or not valid. After 5 such requests in an hour from one IP, the API answers 429 rate_limit_exceeded instead
  • 403 pro_only: The account is not on a plan that includes Gates
  • 422 validation_failed: email or domain is empty, null, not a string, or longer than 255 characters
  • 429 rate_limit_exceeded: Too many requests, see Rate limits
  • 429 quota_exceeded: The monthly credits are used up

An error response uses no credits. Errors lists every code and how to handle each one.

Rate limits

Decisions share your account's per-second rate limit with the data API (/domain and /email) and with the keys of every environment. Management API requests have a separate limit of their own.

Timeouts and failures

Decisions usually return in well under a second. A domain whose DNS does not respond can take several seconds.

  • Set a client timeout of 3 to 5 seconds.
  • Decide ahead of time what your signup does when the call fails or times out. Most apps allow the signup and log the failure, so a problem on our side never blocks real users.
  • Don't retry inside the user's request. Apply your fallback and move on.

Errors covers which responses are safe to retry.

The /v0 endpoint

Integrations built during the beta call POST /v0/gates/{gate_id}/decisions. It keeps working, with its original response and error format. New integrations should use /v1. Migrate from v0 to v1 lists every difference.

Previous
Rules & conditions