Quickstart
A working ShieldSignup integration in under ten minutes — curl, Node, and Python.
This page gets you from zero to a working assessment in three steps. Examples
use process.env.SHIELDSIGNUP_API_KEY, set a 3000ms timeout, and handle all
three verdicts. Only email is required — add the client IP for full
network and velocity checks in production.
1. Prerequisites
- A ShieldSignup account (sign up free).
- An API key, created at Dashboard → API Keys.
- That's it — there is no SDK to install at MVP.
API keys begin with sk_live_ (production) or sk_test_ (sandbox). Use a
sk_test_… key for the rest of this page; sandbox keys never deduct from
your monthly quota.
2. Make your first request
The base URL for production is https://api.shieldsignup.com/v1.
Fastest path — email only (no IP extraction needed yet):
curl https://api.shieldsignup.com/v1/assess \
-H "Authorization: Bearer sk_test_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"block@example.com"}'Production path — email plus the end user's public IP:
curl https://api.shieldsignup.com/v1/assess \
-H "Authorization: Bearer sk_test_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"block@example.com","ip":"203.0.113.42"}'See Getting the client IP for framework
examples. ShieldSignup ignores 127.0.0.1, private LAN addresses, and
localhost automatically — you still get 200 OK, but with
ip_provided: false.
In sandbox mode (sk_test_…), the API matches on the email local-part
prefix to give you a deterministic verdict. block@… always returns this
shape:
{
"request_id": "req_01hw5k3mfvx8t2b4ncqw7ydpej",
"verdict": "block",
"score": 92,
"reasons": [
{
"code": "email_disposable",
"signal": "email"
}
],
"signals": {
"email": {
"disposable": false,
"domain": "example.com",
"domain_age_days": 3650,
"mx_valid": true,
"public_domain": false,
"role_account": false
},
"ip": {
"address": "203.0.113.42",
"tor": false,
"vpn": false,
"proxy": false,
"datacenter": false,
"abuse_score": 3,
"country_code": "US",
"asn": "AS15169"
},
"velocity": {
"ip_signups_1h": 1,
"ip_signups_24h": 1,
"email_domain_1h": 1,
"email_domain_24h": 1
}
},
"ip_provided": true,
"ip_status": "ok",
"processed_ms": 12,
"assessed_at": "2026-05-10T10:14:33Z"
}When you omit ip, the response includes ip_provided: false and omits
signals.ip. Sandbox fixtures still match on the email local-part.
Swap block@ for allow@ or challenge@ to exercise the other branches.
See Testing for the full fixture matrix.
3. Add it to your signup handler
Node.js (Next.js Server Action / API route)
export async function signup(email: string, ip?: string) {
const payload: Record<string, string> = { email };
if (ip) {
payload.ip = ip;
}
const res = await fetch("https://api.shieldsignup.com/v1/assess", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SHIELDSIGNUP_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(3000),
});
if (!res.ok) {
// ShieldSignup returned an error envelope. Decide your fallback policy.
// See: /docs/api-reference/errors
return { error: "assessment_unavailable" as const };
}
const { verdict, request_id } = (await res.json()) as {
verdict: "allow" | "challenge" | "block";
request_id: string;
};
if (verdict === "block") {
return { error: "signup_rejected" as const };
}
if (verdict === "challenge") {
return { challenge: true as const, request_id };
}
await createUser(email);
return { success: true as const, request_id };
}Python
import os
import requests
API_URL = "https://api.shieldsignup.com/v1/assess"
def assess(email: str, ip: str | None = None) -> dict:
payload = {"email": email}
if ip:
payload["ip"] = ip
response = requests.post(
API_URL,
headers={
"Authorization": f"Bearer {os.environ['SHIELDSIGNUP_API_KEY']}",
"Content-Type": "application/json",
},
json=payload,
timeout=3,
)
response.raise_for_status()
return response.json()
def signup(email: str, ip: str | None = None) -> dict:
result = assess(email, ip)
verdict = result["verdict"]
request_id = result["request_id"]
if verdict == "block":
return {"error": "signup_rejected"}
if verdict == "challenge":
return {"challenge": True, "request_id": request_id}
create_user(email)
return {"success": True, "request_id": request_id}curl (full handler shape)
RESPONSE=$(curl --silent --max-time 3 \
-H "Authorization: Bearer ${SHIELDSIGNUP_API_KEY}" \
-H "Content-Type: application/json" \
-d "{\"email\":\"${EMAIL}\",\"ip\":\"${IP}\"}" \
https://api.shieldsignup.com/v1/assess)
VERDICT=$(echo "$RESPONSE" | jq -r '.verdict')
case "$VERDICT" in
allow) echo "ok: create the account" ;;
challenge) echo "challenge: send verification email" ;;
block) echo "block: refuse signup" ;;
*) echo "error: $RESPONSE" ;;
esacWhat's next
- How to read the client IP in your stack → Getting the client IP.
- Full request and response reference → API reference: assess.
- What to do with each verdict → Handling verdicts.
- How to test without burning credits → Testing.
- All error codes and the response envelope → Errors.