An API that checks new signups for disposable emails and repeat accounts.
Maintain this server? Claim it
Call this server's tools from your browser. Nothing to install.
Requests go through mcptry because browsers can't call most MCP servers directly. Header values are sent to the server for that request only and are never stored or logged.
Tool list from our last check. Connect live to refresh it.
Checks one email address, and optionally the signup's IP address, the way SignupBouncer checks a new signup. Returns the result as "verdict" (allow, challenge or block), a 0-100 score, the reasons with their reason codes, the normalised inbox (Gmail dots and +tags folded) and the IP's network. When the domain looks like a typo of a big mail provider (gmial.com), "suggestion" holds the corrected address. Looks up the domain's mail servers live. Every reason is listed, including ones only paid plans count. Nothing sent is stored.
The email address to check.
Optional IPv4 or IPv6 address the signup came from.
Checks one email address, and optionally the signup's IP address, the way SignupBouncer checks a new signup. Returns the result as "verdict" (allow, challenge or block), a 0-100 score, the reasons with their reason codes, the normalised inbox (Gmail dots and +tags folded) and the IP's network. When the domain looks like a typo of a big mail provider (gmial.com), "suggestion" holds the corrected address. Looks up the domain's mail servers live. Every reason is listed, including ones only paid plans count. Nothing sent is stored.
| Argument | Type | Description |
|---|---|---|
| ip | string | Optional IPv4 or IPv6 address the signup came from. |
| email* | string | The email address to check. |
{
"type": "object",
"required": [
"email"
],
"properties": {
"ip": {
"type": "string",
"maxLength": 64,
"description": "Optional IPv4 or IPv6 address the signup came from."
},
"email": {
"type": "string",
"maxLength": 320,
"description": "The email address to check."
}
},
"additionalProperties": false
}Explains a SignupBouncer reason code (for example disposable_domain, burner_mx, repeat_inbox, datacenter_ip): what it means, how many points it adds or whether it blocks alone, and the lowest plan on which it counts. Without a code, lists every code.
| Argument | Type | Description |
|---|---|---|
| code | string | A reason code such as disposable_domain. Omit to list all codes. |
{
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "A reason code such as disposable_domain. Omit to list all codes."
}
},
"additionalProperties": false
}Needs an API key. Returns the Rules settings of one of your apps: shadow or enforce mode, the challenge and block score cutoffs, what a challenge does in the Supabase and Ory hooks, the message a refused person sees, the "Why was this refused?" link, the velocity thresholds and the setting of each check. The context part lists your plan, the checks that need a higher plan, the values each check takes and the defaults. A key made for one app reads that app, so leave out "app". An older all-apps key needs "app".
| Argument | Type | Description |
|---|---|---|
| app | string | The app id (app_...). Leave it out with a key made for one app. An older all-apps key needs it. |
{
"type": "object",
"properties": {
"app": {
"type": "string",
"pattern": "^app_[0-9a-f]{24}$",
"maxLength": 28,
"description": "The app id (app_...). Leave it out with a key made for one app. An older all-apps key needs it."
}
},
"additionalProperties": false
}Returns the steps and the copy-paste code that checks new signups with SignupBouncer in one stack: supabase (a hook, no code), ory (a web hook, no code), fetch (any TypeScript server), nextjs, express, hono, python, firebase or better-auth. Use it when the user is building or changing a signup, register or auth flow. The code reads the API key from SIGNUPBOUNCER_API_KEY. The call has a 1.5-second limit, and any failure lets the signup through.
| Argument | Type | Description |
|---|---|---|
| stack* | string | The user's backend or auth stack. |
{
"type": "object",
"required": [
"stack"
],
"properties": {
"stack": {
"enum": [
"supabase",
"ory",
"fetch",
"nextjs",
"express",
"hono",
"python",
"firebase",
"better-auth"
],
"type": "string",
"description": "The user's backend or auth stack."
}
},
"additionalProperties": false
}Says whether a domain is on SignupBouncer's disposable (burner) email list, only on an old burner list (a challenge, not a block), a known relay service or a free subdomain mail service, or a known real mailbox provider, and when our harvester observed it. Uses only the loaded lists, no DNS lookup, so a brand-new burner domain on a burner mail server is caught only by check_email. Accepts a domain or an email address.
| Argument | Type | Description |
|---|---|---|
| domain* | string | A domain such as mailinator.com, or an email address. |
{
"type": "object",
"required": [
"domain"
],
"properties": {
"domain": {
"type": "string",
"maxLength": 320,
"description": "A domain such as mailinator.com, or an email address."
}
},
"additionalProperties": false
}Needs an API key. Changes the Rules settings of one of your apps, which changes live signup blocking for that app: the next real signups are scored and refused under the new settings. Send only the settings to change. The others stay as they are. Setting mode to "enforce" starts refusing signups marked block, and "shadow" stops refusing any. If one value is wrong, nothing changes and the answer says which. Returns the app's rules afterwards, with the settings that changed. Confirm the change with the user first. A key made for one app changes that app, so leave out "app". An older all-apps key needs "app".
| Argument | Type | Description |
|---|---|---|
| app | string | The app id (app_...). Leave it out with a key made for one app. An older all-apps key needs it. |
| mode | string | enforce refuses signups. shadow only logs. |
| checks | object | Per check: "default" (our points), "off", "challenge" (always challenge) or "block" (always block, for checks that do not already block). A check above your plan keeps its setting. |
| block_at | integer | The score that marks block. Higher than challenge_at. |
| velocity | object | Earlier signups in the window before a velocity rule adds points: 0 turns one off, or 2 to 10,000. app counts this app. account counts all your apps and is 0 or at least app. |
| why_link | boolean |
| Add the "Why was this refused?" link to refusals. |
| challenge_at | integer | The score that marks challenge. |
| block_message | string | The message a refused person sees, 200 characters at most after we trim spaces from both ends. Empty restores our default, which we word to fit the reason for each refusal. |
| challenge_action | string | What the Supabase and Ory hooks do with a signup marked challenge. |
{
"type": "object",
"properties": {
"app": {
"type": "string",
"pattern": "^app_[0-9a-f]{24}$",
"maxLength": 28,
"description": "The app id (app_...). Leave it out with a key made for one app. An older all-apps key needs it."
},
"mode": {
"enum": [
"shadow",
"enforce"
],
"type": "string",
"description": "enforce refuses signups. shadow only logs."
},
"checks": {
"type": "object",
"properties": {
"vpn_ip": {
"enum": [
"default",
"off",
"challenge",
"block"
],
"type": "string"
},
"ai_agent": {
"enum": [
"default",
"off",
"challenge",
"block"
],
"type": "string"
},
"tor_exit": {
"enum": [
"default",
"off",
"challenge",
"block"
],
"type": "string"
},
"burner_mx": {
"enum": [
"default",
"off",
"challenge"
],
"type": "string"
},
"repeat_inbox": {
"enum": [
"default",
"off",
"challenge",
"block"
],
"type": "string"
},
"role_account": {
"enum": [
"default",
"off",
"challenge",
"block"
],
"type": "string"
},
"datacenter_ip": {
"enum": [
"default",
"off",
"challenge",
"block"
],
"type": "string"
},
"relay_service": {
"enum": [
"default",
"off",
"challenge",
"block"
],
"type": "string"
},
"hijacked_network": {
"enum": [
"default",
"off",
"challenge",
"block"
],
"type": "string"
},
"disposable_domain": {
"enum": [
"default",
"off",
"challenge"
],
"type": "string"
},
"residential_proxy": {
"enum": [
"default",
"off",
"challenge",
"block"
],
"type": "string"
},
"stale_list_domain": {
"enum": [
"default",
"off",
"challenge",
"block"
],
"type": "string"
},
"free_subdomain_mail": {
"enum": [
"default",
"off",
"challenge",
"block"
],
"type": "string"
}
},
"description": "Per check: \"default\" (our points), \"off\", \"challenge\" (always challenge) or \"block\" (always block, for checks that do not already block). A check above your plan keeps its setting.",
"additionalProperties": false
},
"block_at": {
"type": "integer",
"maximum": 100,
"minimum": 1,
"description": "The score that marks block. Higher than challenge_at."
},
"velocity": {
"type": "object",
"properties": {
"asn": {
"type": "object",
"properties": {
"app": {
"type": "integer",
"oneOf": [
{
"const": 0
},
{
"type": "integer",
"maximum": 10000,
"minimum": 2
}
],
"description": "0 turns this count off. Otherwise 2 to 10000."
},
"account": {
"type": "integer",
"oneOf": [
{
"const": 0
},
{
"type": "integer",
"maximum": 10000,
"minimum": 2
}
],
"description": "0 turns this count off. Otherwise 2 to 10000."
}
},
"additionalProperties": false
},
"inbox": {
"type": "object",
"properties": {
"app": {
"type": "integer",
"oneOf": [
{
"const": 0
},
{
"type": "integer",
"maximum": 10000,
"minimum": 2
}
],
"description": "0 turns this count off. Otherwise 2 to 10000."
}
},
"additionalProperties": false
},
"domain": {
"type": "object",
"properties": {
"app": {
"type": "integer",
"oneOf": [
{
"const": 0
},
{
"type": "integer",
"maximum": 10000,
"minimum": 2
}
],
"description": "0 turns this count off. Otherwise 2 to 10000."
},
"account": {
"type": "integer",
"oneOf": [
{
"const": 0
},
{
"type": "integer",
"maximum": 10000,
"minimum": 2
}
],
"description": "0 turns this count off. Otherwise 2 to 10000."
}
},
"additionalProperties": false
},
"subnet": {
"type": "object",
"properties": {
"app": {
"type": "integer",
"oneOf": [
{
"const": 0
},
{
"type": "integer",
"maximum": 10000,
"minimum": 2
}
],
"description": "0 turns this count off. Otherwise 2 to 10000."
},
"account": {
"type": "integer",
"oneOf": [
{
"const": 0
},
{
"type": "integer",
"maximum": 10000,
"minimum": 2
}
],
"description": "0 turns this count off. Otherwise 2 to 10000."
}
},
"additionalProperties": false
},
"pattern": {
"type": "object",
"properties": {
"app": {
"type": "integer",
"oneOf": [
{
"const": 0
},
{
"type": "integer",
"maximum": 10000,
"minimum": 2
}
],
"description": "0 turns this count off. Otherwise 2 to 10000."
},
"account": {
"type": "integer",
"oneOf": [
{
"const": 0
},
{
"type": "integer",
"maximum": 10000,
"minimum": 2
}
],
"description": "0 turns this count off. Otherwise 2 to 10000."
}
},
"additionalProperties": false
}
},
"description": "Earlier signups in the window before a velocity rule adds points: 0 turns one off, or 2 to 10,000. app counts this app. account counts all your apps and is 0 or at least app.",
"additionalProperties": false
},
"why_link": {
"type": "boolean",
"description": "Add the \"Why was this refused?\" link to refusals."
},
"challenge_at": {
"type": "integer",
"maximum": 100,
"minimum": 1,
"description": "The score that marks challenge."
},
"block_message": {
"type": "string",
"description": "The message a refused person sees, 200 characters at most after we trim spaces from both ends. Empty restores our default, which we word to fit the reason for each refusal."
},
"challenge_action": {
"enum": [
"allow",
"block"
],
"type": "string",
"description": "What the Supabase and Ory hooks do with a signup marked challenge."
}
},
"additionalProperties": false
}