ShieldSignup
API reference

Assess a signup

POST /v1/assess — the core ShieldSignup endpoint. Full request and response reference.

POST /v1/assess is the only endpoint you need to integrate ShieldSignup into a signup flow. It accepts an email address and, optionally, the end user's IP. It returns a verdict, a numeric score, machine-readable reason codes, and underlying signals.

Send the client IP in production

email is required. ip is optional but strongly recommended. Without a usable public IP, only email and domain signals are scored. Loopback, private, and localhost values are accepted but ignored — see Getting the client IP.

Endpoint

POST https://api.shieldsignup.com/v1/assess

Authentication

Bearer token in the Authorization header. See Authentication for details.

Request

Headers

HeaderRequiredValue
AuthorizationYesBearer sk_live_… or Bearer sk_test_…
Content-TypeYesapplication/json

Body

FieldTypeRequiredDescription
emailstringYesEmail address to assess. Must pass basic format validation (local@domain.tld). Trimmed and lower-cased server-side.
ipstringNoIPv4 or IPv6 address of the end user submitting the signup, collected server-side. Strongly recommended for production. Omitted, empty, loopback, private, or localhost values run an email-only assessment. Malformed strings return 400 invalid_ip. See Getting the client IP.
session_idstringNoYour internal session or request ID. Echoed back on the response and stored on the assessment record. Use it to correlate ShieldSignup assessments with your own request logs. If omitted, the response does not include a session_id field at all.

There is no metadata field at MVP. If you need to attach extra context, put it in your own database keyed on request_id.

Full example

{
  "email": "user@example.com",
  "ip": "203.0.113.42",
  "session_id": "sess_abc123"
}

Minimal example (email only)

Works without IP extraction — email and domain signals only:

{
  "email": "user@example.com"
}
{
  "email": "user@example.com",
  "ip": "203.0.113.42"
}

Response

Status

HTTP/1.1 200 OK
Content-Type: application/json

Body

FieldTypeAlways presentDescription
request_idstringYesUnique ID for this assessment, prefix req_. Use it for support lookups and to refetch the assessment with GET /v1/assess/:request_id.
session_idstringOnly if sentEchoed back from the request.
verdictstringYesOne of "allow", "challenge", "block".
scoreintegerYesRisk score 0100. Higher is riskier.
reasonsarray of objectsYesThe reason codes that contributed to the score. Empty array on a clean signal.
reasons[].codestringYesMachine-readable reason code. See Reason codes.
reasons[].signalstringYesSource group. One of "email", "ip", "velocity".
ip_providedbooleanYestrue when a usable public IP was used for scoring.
ip_statusstringYesOne of "ok", "missing", "ignored_loopback", "ignored_private", "ignored_localhost".
signalsobjectYesUnderlying signal values (see below).
signals.emailobjectYesEmail-derived signals.
signals.email.disposablebooleanYesDomain is on the disposable-domain blocklist.
signals.email.domainstringYesThe domain part of the email, lower-cased.
signals.email.domain_age_daysintegerYesAge of the domain in days. 3650 is the placeholder when age is unknown.
signals.email.mx_validbooleanYesDomain has valid MX records.
signals.email.public_domainbooleanYesEmail is from a free consumer provider (Gmail, Yahoo, Outlook, Hotmail).
signals.email.role_accountbooleanYesLocal-part is a known role account (admin, info, support, sales).
signals.ipobjectOnly when ip_provided is trueIP-derived signals. Omitted from the response when no usable IP was available.
signals.ip.addressstringWhen signals.ip is presentThe usable public IP used for scoring.
signals.ip.torbooleanWhen signals.ip is presentIP is a known Tor exit node.
signals.ip.vpnbooleanWhen signals.ip is presentIP is a known VPN endpoint.
signals.ip.proxybooleanWhen signals.ip is presentIP is a known anonymous proxy.
signals.ip.datacenterbooleanWhen signals.ip is presentIP belongs to a datacenter / hosting ASN.
signals.ip.abuse_scoreintegerWhen signals.ip is presentAbuse score 0100 from upstream IP intelligence sources.
signals.ip.country_codestringWhen signals.ip is presentISO 3166-1 alpha-2 country code.
signals.ip.asnstringWhen signals.ip is presentASN string, e.g. "AS15169".
signals.velocityobjectYesSliding-window counters seen across all of your traffic.
signals.velocity.ip_signups_1hintegerYesSignup attempts from this IP in the last 60 minutes. 0 when ip_provided is false.
signals.velocity.ip_signups_24hintegerYesSignup attempts from this IP in the last 24 hours. 0 when ip_provided is false.
signals.velocity.email_domain_1hintegerYesDistinct signups for this email domain in the last 60 minutes.
signals.velocity.email_domain_24hintegerYesDistinct signups for this email domain in the last 24 hours.
processed_msintegerYesServer-side processing time for this request, in milliseconds.
assessed_atstringYesRFC 3339 timestamp of when the assessment was produced (UTC).

Response headers

Every successful response also includes the rate-limit headers documented in Rate limits:

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1717200000
X-Quota-Used: 4312
X-Quota-Limit: 10000

Full response example

{
  "request_id": "req_01hw5k3mfvx8t2b4ncqw7ydpej",
  "session_id": "sess_abc123",
  "verdict": "block",
  "score": 91,
  "reasons": [
    {
      "code": "email_disposable",
      "signal": "email"
    },
    {
      "code": "ip_anonymizer",
      "signal": "ip"
    },
    {
      "code": "ip_reputation",
      "signal": "ip"
    },
    {
      "code": "velocity_ip",
      "signal": "velocity"
    }
  ],
  "signals": {
    "email": {
      "disposable": true,
      "domain": "mailinator.com",
      "domain_age_days": 9201,
      "mx_valid": true,
      "public_domain": false,
      "role_account": false
    },
    "ip": {
      "address": "185.220.101.45",
      "tor": true,
      "vpn": false,
      "proxy": false,
      "datacenter": false,
      "abuse_score": 97,
      "country_code": "DE",
      "asn": "AS200651"
    },
    "velocity": {
      "ip_signups_1h": 8,
      "ip_signups_24h": 22,
      "email_domain_1h": 14,
      "email_domain_24h": 31
    }
  },
  "ip_provided": true,
  "ip_status": "ok",
  "processed_ms": 18,
  "assessed_at": "2026-05-10T10:14:33Z"
}

Email-only response example

When ip is omitted or not usable (e.g. 127.0.0.1 during local dev):

{
  "request_id": "req_01hw5k3mfvx8t2b4ncqw7ydpej",
  "verdict": "allow",
  "score": 5,
  "reasons": [],
  "ip_provided": false,
  "ip_status": "ignored_loopback",
  "signals": {
    "email": {
      "disposable": false,
      "domain": "example.com",
      "domain_age_days": 3650,
      "mx_valid": true,
      "public_domain": false,
      "role_account": false
    },
    "velocity": {
      "ip_signups_1h": 0,
      "ip_signups_24h": 0,
      "email_domain_1h": 1,
      "email_domain_24h": 1
    }
  },
  "processed_ms": 12,
  "assessed_at": "2026-05-10T10:14:33Z"
}

Verdict logic

The verdict is derived from score against the thresholds configured for your account or for the specific API key. Defaults:

ScoreVerdict
0–29allow
30–59challenge
60–100block

Thresholds are configurable in Dashboard → Settings → Thresholds, both account-wide and per API key. Your code must handle all three values; see Handling verdicts.

Reason codes

These are the public reason codes returned by the API. Switch on code for stable UX and logging. For finer-grained policies (e.g. block Tor but allow VPN), read signals — see Reasons vs signals.

Email signals

CodeMeaning
email_disposableDomain is in the disposable-domain blocklist.
email_deliverabilityDomain has deliverability issues (e.g. no valid MX records).
email_role_accountLocal-part is admin, info, support, or sales.
email_aliasLocal-part contains + (plus addressing).
email_consumer_providerDomain is a free consumer provider (Gmail, Yahoo, Outlook, Hotmail).

IP signals

Only emitted when ip_provided is true.

CodeMeaning
ip_anonymizerIP is associated with anonymized traffic (Tor, VPN, proxy).
ip_reputationIP has elevated abuse reputation or blocklist matches.
ip_hostingIP belongs to a datacenter or hosting ASN.

Velocity signals

CodeMeaning
velocity_ipUnusual signup rate from this IP once 5+ attempts occur in the last hour. Requires a usable IP.
velocity_domainUnusual signup rate for this email domain once 6+ attempts occur in the last hour. Always available.

Reasons vs signals

Two layers of detail

  • reasons — stable risk categories for messaging, logging, and default branching. Codes like ip_anonymizer intentionally hide specific detection rules.
  • signals — underlying booleans and counters for advanced custom policies. Example: branch on reasons[].code === "ip_anonymizer" for generic friction, then use signals.ip.tor vs signals.ip.vpn for different actions.

Future detectors

New internal detectors may map to these same public codes without a breaking API change. Do not depend on removed codes such as ip_tor_exit_node or velocity_ip_signups in the public response.

Latency

Typical processing time is well under 50ms server-side, with end-to-end p95 near 80–150ms. Always set a client-side timeout. Recommended: 3000ms. ShieldSignup is a synchronous risk check, but a slow signal source must never be allowed to break your signup.

Error responses

Common errors:

HTTP 400 — missing_field, invalid_email, invalid_ip
HTTP 401 — unauthorized, token_revoked
HTTP 429 — quota_exceeded (monthly cap), rate_limit (per-second burst)
HTTP 500 — internal_error

The error envelope is documented in full at API reference: errors.

On this page