Send them to a paid plan
Strong evidence of a repeat. Offer the paid plan. The visitor never sees why.
One API call per signup. Allow, challenge or deny, before the account exists.
500 free decisions a month. No card.

Real screens from a live workspace, fed by the built-in sandbox.

A repeat device is a household. A shared subnet is an office. What moves a signup is how many independent kinds agree.

Card fingerprints from your own Stripe.
A hint that survives cleared cookies.
Plus tags stripped, dots folded, throwaways known.
The subnet, the network type, where it resolves.
The pace of the form, a cold arrival, the hour.
Abuse is rarely a bot. It is one person with a fresh mailbox and a cleared browser. Blocklists miss them; hard blocks lose real customers.
Strong evidence of a repeat. Offer the paid plan. The visitor never sees why.
One proof: a card, an email link or a captcha. Real customers clear it and carry on.
Nothing looks like a repeat. Most of your traffic, and it never notices.

New workspaces start in shadow mode: every signup is scored, your app is always told allow. Read a week of what would have happened, then turn enforcement on.
json{ "decision": "allow", "shadow": true, "wouldBe": "deny", "score": 81 }
An exception in plain English, editable before it saves.

It mints a token and shows nothing. Nothing is decided in the browser.
html<script src="https://onetrial.fly.dev/v1.js" data-key="YOUR_PUBLIC_KEY" async></script>
One call from your server. Under 300ms at the median.
typescriptconst res = await fetch('https://onetrial.fly.dev/api/v1/decisions', {method: 'POST',headers: { Authorization: `Bearer ${process.env.ONETRIAL_API_KEY}`, 'Content-Type': 'application/json' },body: JSON.stringify({ visitorToken, email, ip, userId }),});const d = await res.json();if (d.decision === 'deny') return showPaidPlanOrSupport();if (d.decision === 'challenge') return redirect(d.challengeUrl); // trial unlocks on the challenge.completed webhookcreateTrial(userId);
Allow, challenge or deny. The reasons are yours, never the visitor’s.
json{"decision": "deny","score": 81,"reasons": ["D_DEVICE_REPEAT","E_EMAIL_REPEAT","N_IP24_REPEAT"],"challengeOptions": []}
Detects your framework, writes the env, injects the tag, self-tests the install.
bashnpx onetrial init
