Identity router for signups and outbound

One API that decides who gets in. Verify the address, enrich the person, score the fit, and apply the rule you wrote in plain English.

Sortance routes each email across the email verification and enrichment providers you are already comparing, caches hard, and returns one decision with its reasons, its receipts, and a calibrated bounce probability underneath.

# Active policy, compiled once from two sentences:
#   Don't allow temp emails or role accounts.
#   Review companies outside fintech or insurance, or under 50 people.
curl -X POST https://api.sortance.com/v1/decide \
  -H "Authorization: Bearer $SORTANCE_KEY" \
  -d '{"email":"mia@acme.com","context":{"purpose":"signup"}}'

Three lanes, one key.

Verification and enrichment are what the router buys; each card names the providers behind it, and says so where a contract is not yet confirmed. The decision is what it returns.

Verify

POST /v1/verify

Will mail to this bounce, and who is behind it?

  • Email verification across a provider pool, fanned out only where one check cannot settle it.
  • A calibrated bounce probability with its interval, plus actor_class: human, agent, relay or role.
Checks
  • Sortance (syntax · MX · disposable blocklist)
  • DeBounce
  • MyEmailVerifier
  • ZeroBounce

Enrich

POST /v1/enrich

Who is this person, and what company are they from?

  • Company first, by domain; a person lookup only when a rule needs one. Consumer mailboxes are not enriched at all.
  • One profile shape whoever answered, with coverage saying how much came back and a receipt per upstream.
Adapters built · contracts not yet confirmed
  • People Data Labs
  • Apollo (fallback)

Using your own upstream key is not available yet. Enrichment pricing is not yet published; the pricing below covers verification.

Decide

POST /v1/decide

Given all that and your rules, does this one get in?

  • Your policy, first matching rule wins: allow, allow with a flow, review, challenge or deny.
  • Enrichment is bought only when evaluation reaches a rule that reads it. A disposable address denied by rule two never pays for a profile.
Runs over
  • Both lanes
  • Your lists
  • Your fit rubric

Write the policy in plain English. Read the rules it compiles to.

A model turns your sentences into typed rules once, when you write them, and asks instead of guessing when a sentence is ambiguous. From then on every request is decided by plain code: the first matching rule wins, and no model sits on the request path. Before you enforce a policy, Sortance replays it over your recent decisions. Then you enforce it, or run it in shadow first.

You write
Don’t allow temp emails or role accounts. Anyone on our partners list is always allowed. Let AI agents in through the API-key flow. Review companies outside fintech or insurance, or under 50 employees.

Static example. This page calls no model; on your account the compiler runs on your text, once, when you click.

It compiles to ordered, first match wins
  1. r_partnersallow
    {
      "field": "email.domain",
      "op": "in_list",
      "list": "partners"
    }

    If the domain is on your ‘partners’ list, allow. Checked first, so it overrides everything below.

    on_unknown: skip
  2. r_no_disposabledeny
    {
      "field": "verification.email_type",
      "op": "eq",
      "value": "disposable"
    }

    Deny when the address is on a disposable-email domain.

    on_unknown: skip
  3. r_no_roledeny
    {
      "field": "verification.email_type",
      "op": "eq",
      "value": "role"
    }

    Deny role accounts like info@ or sales@.

    on_unknown: skip
  4. r_agentsallow_with_flow → api-key
    {
      "field": "verification.actor_class",
      "op": "eq",
      "value": "agent_mailbox"
    }

    Send AI agent mailboxes to the ‘api-key’ flow instead of the normal signup.

    on_unknown: skip
  5. r_industry_fitreview
    { "any": [
      {
        "field": "enrichment.company.industry",
        "op": "not_in",
        "value": ["fintech", "insurance"]
      },
      {
        "field": "enrichment.company.employees",
        "op": "lt",
        "value": 50
      }
    ] }

    Review, don’t deny, when the company is outside fintech or insurance or under 50 employees. When company data is missing the rule is skipped, so a gap in enrichment never turns into a lost signup.

    on_unknown: skip
  6. default_actionallow

    Nothing matched, or every rule that could have was missing its data: the request is let in.

Replay over the last 500 decisions

Illustrative example, not real traffic
318
allow
4
allow_with_flow
121
review
0
challenge
57
deny
Matched per rule
r_partners 6 · r_no_disposable 23 · r_no_role 34 · r_agents 4 · r_industry_fit 121
Changed against the active version
allow → deny 41 · deny → allow 2 · allow → review 97
Enrichment cost change
$31.40 more per 1,000 decisions, shown before you activate, every time.
Warning
38 of 500 traces had no enrichment stored; those were evaluated with enrichment: null.

Read the response before you read the pitch.

One POST, one payload. The decision says which of your rules fired and why; the verification inside it is the same object the other three tabs show, never reshaped.

{
"decision":"review",
"flow":null,
"rule_id":"r_industry_fit",
"tags":[],
"policy":{ "slug": "default", "version": 14 },
"reasons":[
"company.industry is 'logistics', not one of 'fintech', 'insurance'"
],
"verification":{ /* the clean human mailbox tab, verbatim */ },
"enrichment":{
"person":null,
"company":{
"name":"Acme Corp",
"domain":"acme.com",
"industry":"logistics",
"industry_raw":"logistics & supply chain",
"employees":240,
"employees_range":"201-500",
/* revenue_range, founded, country, type, tech, linkedin_url, funding */
},
"coverage":{ "person": 0, "company": 1 },
"freshness":{ "person": null, "company": "2026-09-30" },
"sources":{
"pdl":{ "status": "match", "ms": 412, "cost": 0.1, "fields": 11 }
},
"disagreement":{},
"cache":{ "hit": false, "ttl_s": 2592000 },
"visitor":null
},
"icp":null,
"lists":{ "matched": [] },
"cost_usd":0.10045,
"latency_ms":468,
"trace_id":"dc_01J…"
}
decision + rule_id + policy + reasons

The decision is your policy's, never Sortance's. rule_id names the rule that matched (null when the default action applied), policy names the exact version, and reasons states each fact that fired. If every upstream fails, decision is still set: the policy's default, with a degraded reason.

enrichment.coverage + sources

Bought only because a rule reads company.industry and company.employees, and only after the earlier rules had passed. Company first, by domain, so no person lookup. coverage says how much of what was asked for came back; sources is the receipt for each upstream that was called.

One call at the signup form, one answer, and the receipts for every upstream it took to reach it.

Three layers between a domain and your list.

Rules that cost nothing, Sortance’s own second-opinion check, then a model that reads the domain the way a person would. A paypa1.com signup passes the first two: it has mail servers and is on no list. Only the third sees a domain dressed up as PayPal. On our test set, the third layer flagged 0 of 674 real domains.

Local gate

free · every request

Can this domain receive mail at all?

  • Syntax, MX over DNS-over-HTTPS, and agent-inbox infrastructure, before any provider is called.
  • The block tier of Sortance's disposable lists, rebuilt every six hours with popular real domains held back for review, plus your own blocklist and allowlist. A bad download never replaces a good list.

Sortance check

free · first in the pool

A second opinion on what the gate let through.

  • Runs inside the API in a few milliseconds and reads the signal tier the gate does not act on: alias services and domains still under review. Reported as providers.sortance on every verification.
  • It never answers “valid”. It does not open an SMTP conversation, so it only reports what is wrong: invalid, disposable or unknown.

Jev domain check

/v1/screen · after MX resolves

Does the name itself look wrong?

  • TypeSafe’s Jev model sorts the domain into look-alike, provider typo, placeholder, throwaway or real, from a fixed set of labels.
  • It sees the domain name only, never the address or the name, and each answer is cached for 30 days. Anything suspicious means review, never a block.

How each layer works, and how it was measured

Catch the typo before it becomes a lost signup.

Once the domain’s MX resolves, screening asks the next question: did this person type their own address, and is the name they gave a real one? It catches gmial.com, a jhon.smtih@ typed by someone who called themselves John Smith, and placeholders like test@, names like Mickey Mouse or asdf asdf, and domains like paypa1.com. One call, POST /v1/screen. It answers allow or review, never a block.

Illustrative example, not customer data

Request and response

curl -X POST https://api.sortance.com/v1/screen \
  -H "Authorization: Bearer $SORTANCE_KEY" \
  -d '{"email":"jhon.smtih@gmail.com","name":"John Smith"}'
{
"email":"jhon.smtih@gmail.com",
"domain":{ "syntax": "pass", "mx": "pass", "disposable": false },
"screening":{
"typo_suggestion":"john.smith@gmail.com",
"typo_source":"name_match",
"name_match":"typo",
"fake_name":"unlikely",
"domain_check":"ok",
"placeholder":false,
"recommendation":"review",
"reasons":["local_part_typo_of_name"],
"degraded":false
},
"cost_usd":0.0000076,
"latency_ms":212,
"trace_id":"sc_…"
}

The suggestion is a corrected address you can offer back on the form. The response never carries a score or a probability, only the labels.

What sends a signup to review

ReasonFromReview?
syntax_invalidGatereview
address_blocklistedGatereview
domain_blocklistedGatereview
domain_no_mxGatereview
disposable_domainGatereview
domain_allowlistedGatenoted
address_allowlistedGatenoted
domain_typoCode checkreview
local_part_typo_of_nameCode checkreview
placeholder_addressCode checkreview
name_mismatchCode checknoted
domain_suspiciousModelreview
fake_name_likelyModelreview
name_email_inconsistentModelnoted

The gate and the code checks are free and always run, including when the domain has no MX, which is exactly when a typo suggestion matters. The model is asked only after the MX resolves: about the name only when you sent one, and about the domain in a separate request that carries the domain name alone, never the address or the name. Big consumer providers like gmail.com skip it.

Metered on its own allowance, 10,000 screenings a month by default, separate from verifications. A mismatch between a name and an address is noted, not penalised: plenty of real people use an address unrelated to their name. How screening works.

The check became a commodity

Per-check pricing went from about $25 per 10K in 2019 to $0.00045–$0.01 today. Anyone can buy a verdict on an address.

The address stopped meaning a human

A deliverable mailbox can belong to an AI agent, a relay alias or a shared inbox. Valid is no longer evidence that a person, let alone a customer, is on the other end.

So the question moved to “should this one get in”

That answer depends on your rules, not ours. Sortance applies the policy you wrote and tells you which rule decided.

Measure a rule against revenue, not against itself.

Run a policy change as an experiment, in shadow or enforced, and score it on events you report or Stripe reports. Sortance’s own decision counts are never the metric: denying more is not an outcome.

Illustrative example figures, not customer data

Experiment report

{
"status":"running",
"started_at":"2026-09-14T00:00:00.000Z",
"exposures":{ "control": 1631, "treatment": 402 },
"decisions":{
"control":{ "allow": 1402, "deny": 229 },
"treatment":{ "allow": 301, "deny": 101 }
},
"metric":{
"event":"subscribed",
"control":{ "events": 67, "rate": 0.0416, "ci": [0.0338, 0.0501] },
"treatment":{ "events": 18, "rate": 0.047, "ci": [0.0311, 0.0655] },
"p_treatment_better":0.6649,
"expected_loss":{ "choose_control": 0.0076, "choose_treatment": 0.0023 },
"verdict":"inconclusive",
"says":"Treatment is more likely better (66%), but the interval is wide. 402 of 400 minimum exposures reached; at the current rate, a 90% answer needs roughly 14,000 more."
},
"guardrails":[{ "event": "support_ticket", "delta": -0.0089, "ok": true }],
"shadow":null
}

Beta posteriors, not p-values. Below the minimum sample the report shows no probability at all, and each address always lands in the same variant.

Paid conversion by segment, from Stripe

SegmentSignupsPaidRate (90% interval)
mailbox_type: consumer1,204191.7% 1.1%–2.3%
mailbox_type: corporate1,4241319.3% 8.0%–10.5%
corporate + company.industry: fintech2124119.6% 15.3%–24.2%
actor_class: agent_mailbox3802.5% 0.1%–7.4%

Joins are exact when your Stripe customer carries metadata.sortance_trace_id; otherwise they fall back to the email address, which is approximate, and every report counts joins by method (here 2,210 exact, 418 by email, 97 unmatched).

Segments under 30 signups are listed as too few to say. A segment that clearly underperforms becomes a suggested rule, proposed as review, never deny, and offered as an experiment first.

Where the remaining error lives

Same protocol, same providers, two very different outcomes depending on who hosts the mailbox.

Google Workspace90.6%
verified valid9.4% not confirmed
Microsoft 36551.4%
verified valid48.6% not confirmed

The gap isn’t a provider being bad at its job. Microsoft-hosted domains largely stopped answering SMTP probes truthfully, and a catch-all domain accepts every address by design — no verifier can resolve it, ours included.

That’s where fan-out pays: on the addresses one provider can’t settle, the spread between four of them is itself information. On Google Workspace mail, a single cheap check is usually enough, and Sortance will tell you so instead of billing you four times for it.

Figures: Microsoft 365 51.4% valid · Google Workspace 90.6% valid.

Priced per 1,000 sends protected. Not per check.

Deliberate: per-check pricing pays us to check more, which is the opposite of what you want. Per send protected pays us to check less — cache harder, gate more locally, fan out only where it changes the answer.

Sends protected / month

250,000 sends
$850.00/ month
$3.40 per 1,000 sends protected

What’s in it

01Every check in the pool — our own Sortance check, then DeBounce, MyEmailVerifier and ZeroBounce — on our contracts. No separate accounts, no separate keys.
02Local gate and cache included. Traffic they resolve costs you nothing extra.
03Calibrated probability, confidence interval, disagreement and actor class on every response.
04Router policy you can pin: cost-first, accuracy-first, or a latency budget in milliseconds.
05Outcome webhook, replay of any decision, and per-decision provider receipts.

For reference, raw per-check pricing across the pool runs $0.00045–$0.01 per check. We buy at that; you pay for the decision, however many checks it took.

Get an API key

Not a lead scorer, not a CRM, not a bouncer

Three things Sortance deliberately does not do.

No model emits a score

Fit is a rubric you write: weighted criteria, added up by plain code, with the share of criteria that could actually be checked printed beside the number. A model helps you write rules once, when you write them. It never runs on a request.

No Sortance-authored deny

A deny only ever comes from a rule you confirmed. Our suggestions propose review, the default action is allow, and every response names the rule and policy version that decided it.

No visitor tracking of our own

No pixel, no private graph, no data we collected ourselves. Every enrichment fact is bought from a named upstream and shows up in that request's receipt, attributed to it.