use case · signups

Catch fake signups at the door.

Updated May 12, 2026

Bot signups are noisy in your dashboards, expensive on your activation funnel, and occasionally a genuine compromise vector. A Siftfy call inside your signup handler scores the free-text fields a real applicant fills out — display name, bio, company, "how did you hear about us" — and routes obvious junk to review without adding friction for legitimate users.

Score the free-text, not the email

Siftfy is trained on natural language, not addresses. Don't pass the email address through the classifier — it'll either score zero or behave unpredictably. The good signal is in the optional copy fields: a "company" field that says "Need to clear my credit card debt FAST!" or a "bio" with three URLs is what the model is built to catch.

If your signup is email-only with no free-text fields, signups isn't the right place to put Siftfy — score the user's first piece of text content instead (their first comment, message, listing, etc.).

Drop-in handler (Next.js)

The example assembles every optional free-text field into a single classifier input. Two thresholds: 0.70 to hold for human review (genuine signups occasionally score here, especially if they have unusual marketing copy in their bio), and 0.95 to silently shadow-ban (genuine signups effectively never score this high — almost certainly a bot).

typescript
// Next.js App Router signup handler. The interesting work is
// constructing the classifier input from whichever free-text fields the
// user filled in — display name, bio, company, "how did you hear about us".
// Skip the email address and other structured fields.
import { NextResponse } from "next/server";

const SIFTFY_KEY = process.env.SIFTFY_KEY!;
const HOLD_THRESHOLD = 0.70;   // hold for review above this
const REJECT_THRESHOLD = 0.95; // hard reject — genuine signups rarely score this high

type SignupInput = {
  email: string;
  displayName?: string;
  bio?: string;
  company?: string;
  referrer?: string;
};

export async function POST(req: Request) {
  const body: SignupInput = await req.json();
  const { email } = body;

  // Concatenate every free-text field into a single classifier input.
  // Empty fields are fine — the model is robust to short inputs.
  const text = [body.displayName, body.bio, body.company, body.referrer]
    .filter(Boolean)
    .join("\n")
    .trim();

  let probability = 0;
  if (text) {
    try {
      const resp = await fetch("https://api.siftfy.io/v1/predict", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-API-Key": SIFTFY_KEY,
        },
        body: JSON.stringify({ text }),
        signal: AbortSignal.timeout(2000),
      });
      if (resp.ok) {
        ({ spam_probability: probability } = await resp.json());
      }
    } catch (err) {
      // Fall open: never block a paying customer because Siftfy was slow.
      console.error("siftfy failed", err);
    }
  }

  if (probability >= REJECT_THRESHOLD) {
    // Don't return an error — that just informs the bot. Pretend success
    // and create the account in a "shadow" state.
    await createShadowAccount({ email, probability });
    return NextResponse.json({ ok: true });
  }

  const status = probability >= HOLD_THRESHOLD ? "needs_review" : "active";
  await createAccount({ ...body, status, spamProbability: probability });
  return NextResponse.json({ ok: true });
}

Stack with non-text signals

Bots don't only spam your text fields — they signal in other ways. Combine Siftfy's score with:

  • Disposable-email lookups. A 0.4 spam score on content + a known disposable address is much more actionable than either alone.
  • IP reputation / ASN. Datacenter signups scoring above 0.5 are a far stronger signal than the same score from a residential IP.
  • Velocity. Five signups in a minute from one /24, all near 0.6, are obviously a coordinated burst.
  • Honey fields. A non-zero hidden field is a 0.99 signal on its own; treat it as a hard reject regardless of Siftfy's score.

The calibrated 0–1 output from Siftfy makes it easy to combine into a single risk score weighted alongside these.

Edge cases

  • Don't 4xx the user. Returning "looks like spam" tells a bot to retry with different wording. Always 200, and make rejected accounts behave normally up to a hidden limit.
  • Allow appeals. A real customer can have an unusual bio — comedy, sales copy, a link in their tagline. Surface the holding queue in your admin so a human can promote within a day.
  • Re-classify on first content. A signup with empty free-text fields produces no Siftfy signal. Re-score on their first user-generated content — comment, message, listing — and update the account flag.
  • Don't double-block. If your billing layer (Stripe, etc.) has already verified a card, you've effectively paid for a stronger signal than text classification. Down-weight Siftfy's score for paying customers.

What do teams ask before adding signup filtering?

Which signup fields should Siftfy classify?

Classify free-text fields such as display name, bio, company, referral notes, or onboarding answers. Skip email addresses, IDs, and structured fields because the model is tuned for natural-language text.

Should suspicious signups be rejected immediately?

Use a review state for borderline scores and reserve silent rejection for very high probabilities. That keeps real users from being blocked by unusual but legitimate profile text.

What if the signup form has no free-text field?

Do not force a signup check. Classify the user's first meaningful content instead, such as a comment, listing, message, or profile update.

Get an API key

See /v1/predict for the full reference, or related patterns: fake registrations (webhook pattern), contact forms, comments, SMS.