T
trueuser
API Reference  /  POST /api/v1/verify
← HomeDashboard
Endpoint

Verify an email

POSThttps://api.trueuser.dev/api/v1/verify

Submits a single email for verification. Returns a verdict computed from your custom rules over the underlying detection signals, plus batch-cluster scoring and reasoning, in under 100 ms.

Request body

email
stringrequired
The email address to verify. Must be a valid RFC 5322 string under 254 characters.
username
string
The username the customer chose at signup. Helps the model spot collisions across distinct emails (a strong batch-attack signal).
ip
string
The client IP that submitted the email. Used for IP reputation and batch-cluster fingerprinting. Strongly recommended.
user_agent
string
The client User-Agent. Improves batch detection accuracy when many signups share an identical UA.

Response

verdict
enum
One of allow, review, or block — derived from your account's verdict rule applied to the signals below. Edit the rule at /dashboard/rules.
signals
object
Detection booleans and scores: format_valid, disposable, public_domain, relay_domain, alias, role_account, spam, suspicious_pattern, batch_cluster.
reasoning
string
One short sentence summarizing the strongest signals.
latency_ms
number
Server-side latency for this verification.

Errors

400invalid_emailThe email failed RFC parsing.
401invalid_api_keyThe Bearer token is missing or malformed.
429rate_limitedYou exceeded your plan's per-second cap. Retry after the Retry-After header.
500internal_errorSomething broke on our side. Always safe to retry.

Best practices

Verify once, at signup

Call /api/v1/verify when the user creates the account, not on every login. Persist the verdict and the signals you care about on your users row and reuse them. Re-verify only on a meaningful change — email update, suspected takeover, refund dispute, etc. Re-checking a known-good user on every request burns latency and budget.

Soft-block, not hard-block

Recommended

This is the integration we recommend for most apps. A block verdict can be a false positive — a real customer with a disposable-shaped local part, a privacy relay user, an alias on top of a real mailbox. Hard-rejecting them at the signup screen costs you real revenue.

Treat the verdict as a dial, not a switch. Let everyone in, but scale what they get: full trial credits for allow, a smaller trial for review, zero credits + a manual review queue for block. The bots can’t monetise zero credits; the false positives still get to use your product.

signup handler · soft block
// In your signup handler — give every user an account, scale the trial.
const { verdict } = await verifyAtSignup(form.email, req.ip);
const trialCredits =
  verdict === "block"  ? 0   :   // suspected bot — no trial, manual review
  verdict === "review" ? 50  :   // borderline — small trial, watch closely
                         500;    // looks human — full trial

await db.users.create({
  ...form,
  trialCredits,
  verifiedVerdict: verdict,   // store it; you've already paid for the call
});

Always wrap in try/catch with a timeout

Verification is a network call to a third party. Treat it the way you’d treat any other dependency: cap the budget with AbortController (3 seconds is a sane synchronous ceiling) and make sure a thrown error or timeout falls through to a safe default — never crash the signup.

verifyAtSignup · with timeout
// Synchronous: 3s budget, soft-degrade on any failure.
async function verifyAtSignup(email: string, ip: string) {
  const ctl = new AbortController();
  const timer = setTimeout(() => ctl.abort(), 3000);
  try {
    const res = await fetch("https://api.trueuser.dev/api/v1/verify", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.TU_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ email, ip }),
      signal: ctl.signal,
    });
    if (!res.ok) return { verdict: "allow" as const, degraded: true };
    return await res.json();
  } catch {
    // Network error or timeout — never crash signup on verification.
    return { verdict: "allow" as const, degraded: true };
  } finally {
    clearTimeout(timer);
  }
}

Or run it asynchronously

If your signup flow can’t spare even 3 seconds, don’t put the verification on the critical path at all. Create the user immediately with zero trial credits, then call verification from a fire-and-forget async function. Customers see an instant response; the verdict lands a moment later, before they can do anything expensive. No queue infrastructure needed for a small flow.

signup handler · fire-and-forget
// Tight latency budget? Verify in the background. The async function runs
// after you've already responded to the user — no queue infrastructure
// needed for a small flow.
async function verifyInBackground(userId: string, email: string, ip: string) {
  const { verdict } = await verifyAtSignup(email, ip);
  await db.users.update(userId, {
    verifiedVerdict: verdict,
    verifiedAt:      new Date(),
    trialCredits:    verdict === "block" ? 0
                   : verdict === "review" ? 50
                   : 500,
  });
}

async function signup(form, req) {
  const user = await db.users.create({
    ...form,
    trialCredits: 0,           // start at zero, raise once verified
    verifiedAt:   null,
  });

  // Fire-and-forget — don't await. The user gets an instant response;
  // the verdict lands a moment later.
  void verifyInBackground(user.id, form.email, req.ip);

  return user;
}
Request · cURLcopy
curl https://api.trueuser.dev/api/v1/verify \
  -H "Authorization: Bearer $TU_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email":    "[email protected]",
    "username": "alex_42",
    "ip":       "203.0.113.42"
  }'
Response · 200 OK68 ms
{
  "id": "ver_01HK8X...",
  "verdict": "block",
  "signals": {
    "format_valid":  true,
    "disposable":    true,
    "public_domain": false,
    "relay_domain":  false,
    "alias":         false,
    "role_account":  false,
    "spam":          false,
    "suspicious_pattern": 0.84,
    "batch_cluster": 0.91
  },
  "reasoning":  "Disposable provider with batch-shaped local part.",
  "latency_ms": 68
}
Try it
Uses your sandbox key · 100 free calls / day