Screening

Catch typo’d addresses and fake names at signup

Verification asks whether mail to an address will bounce. Screening asks the next question, once the domain’s MX resolves: did this person type their own address, and is the name they gave a real one? One call, and the answer is allow or review, never a block.

The endpoint

curl -X POST https://api.sortance.com/v1/screen \
  -H "Authorization: Bearer $SORTANCE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"jhon.smtih@gmail.com","name":"John Smith"}'
  • Auth: the same Authorization: Bearer sk_live_... header as /v1/verify. A key comes from the quickstart.
  • Limits: the same body limit and the same rate limit as /v1/verify, 600 requests a minute per key, shared between the two.
  • Meter: its own monthly allowance, described below.

Request

FieldTypeNotes
emailstringRequired. Up to 320 characters, with the same rules as /v1/verify.
namestringOptional. The name the person typed into your form, up to 200 characters after trimming. Empty or whitespace is treated as absent; anything other than a string is a 400 invalid_request. Without a name, the name checks are skipped and no name reaches the model; the address checks and the domain check still run.

Response

{
  "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_..."
}

An illustrative example: the person called themselves John Smith and typed jhon.smtih@. The domain passed the gate, so the model was asked and found the name plausible; the typo alone is enough for review, and the suggestion is what you would show them. gmail.com is a major consumer mailbox provider, so domain_check is ok without a model call and adds nothing to the cost.

FieldTypeMeaning
emailstringThe address you sent, echoed back. Sortance never stores it; the screening record keeps only a keyed hash.
domain.syntaxpass | failWhether the address is syntactically valid.
domain.mxpass | fail | unknownWhether the domain's MX resolves. unknown means the lookup did not settle.
domain.disposablebooleanWhether the domain is a known disposable-mail service.
screening.typo_suggestionstring | nullA corrected full address you can offer back on the form (“Did you mean john.smith@gmail.com?”), or null. It never echoes the name on its own.
screening.typo_sourcename_match | domain | nullWhat the suggestion corrects: the part before the @ against the name, or the domain against a common one.
screening.name_matchmatch | typo | partial | mismatch | not_checkedHow the part before the @ relates to the name. not_checked when no name was sent.
screening.fake_namelikely | unlikely | uncertain | not_checkedWhether the name reads as fictional, a placeholder or a keyboard mash. not_checked when the model was not asked or did not answer.
screening.domain_checkok | typo_like | lookalike | placeholder | throwaway_like | uncertain | not_checkedWhat the domain looks like, judged from its name alone. See the domain check below. It never produces a typo_suggestion.
screening.placeholderbooleanThe address looks like something typed to get past a form rather than a real mailbox.
screening.recommendationallow | reviewNever deny. See below for what triggers review.
screening.reasonsstring[]Every reason that applied, from the closed vocabulary below. Nothing you typed can appear here.
screening.degradedbooleanTrue when a model request was made and failed. The gate and code checks still answered; whichever label that request feeds (fake_name or domain_check) is not_checked.
cost_usdnumberWhat the model requests cost, summed when both were made. 0 when the model was not called, and a cached domain answer adds 0.
latency_msnumberWall-clock for the request.
trace_idstringsc_-prefixed. Quote it when asking about a screening.

No probability or score appears in the response, and none is stored. Each model answer becomes one of the labels above and the number behind it is dropped.

Reasons

reasons only ever contains values from this list. The recommendation is review when any reason marked review is present, and allow otherwise. name_mismatch and name_email_inconsistent are informational: plenty of real people sign up with an address that has nothing to do with their name, so they never cause a review on their own.

ReasonFromTriggers reviewMeans
syntax_invalidGateYesThe address is not a valid address.
address_blocklistedGateYesThe address itself is on your account's own blocklist (/v1/addresses/blocklist). Sortance keeps a keyed hash and a masked form, never the address.
domain_blocklistedGateYesThe domain, or a parent of it, is on your account's own blocklist (/v1/domains/blocklist).
domain_no_mxGateYesThe domain has no mail server, so mail to it cannot be delivered.
disposable_domainGateYesThe domain is a known disposable-mail service.
domain_allowlistedGateNoThe domain is on your account's own allowlist, so Sortance's disposable lists were not applied to it. Informational.
address_allowlistedGateNoThe address itself is on your account's own allowlist (/v1/addresses/allowlist), so Sortance's disposable lists and your domain blocks were not applied to it. Syntax and MX still were. Informational.
domain_typoCode checkYesThe domain looks like a misspelling of a common one, such as gmial.com for gmail.com.
local_part_typo_of_nameCode checkYesThe part before the @ is a near-miss of the name they typed, such as jhon.smtih for John Smith.
placeholder_addressCode checkYesThe address looks like something typed to get past a form rather than a real mailbox.
name_mismatchCode checkNoThe part before the @ has nothing in common with the name. Informational.
domain_suspiciousModelYesJudged from the domain name alone, it looks like a misspelt mail provider, a brand lookalike such as paypa1.com, a placeholder such as asdfgh.com, or a throwaway inbox. No correction is suggested.
fake_name_likelyModelYesThe name reads as fictional, a placeholder or a keyboard mash, such as Mickey Mouse or asdf asdf.
name_email_inconsistentModelNoThe address clearly names a different person. Informational.

Order of operations

  1. The gate. The same local gate /v1/verify runs first: syntax, MX and disposable domains, plus your account’s own domain and address rules. Its signals are the domain object. A failure here adds syntax_invalid, address_blocklisted, domain_blocklisted, domain_no_mx or disposable_domain. A domain or address on your allowlist adds the informational domain_allowlisted or address_allowlisted instead of disposable_domain. The model’s domain check still runs on it, as on any other domain.
  2. Code checks, always. Plain code compares the address against common domains and against the name, and looks for placeholders. These are free and run even when the gate failed: a misspelt domain such as gmial.com is often the reason there is no MX, and that is when the suggestion matters most.
  3. The model, only after the MX resolves. Up to two separate requests to TypeSafe’s Jev model, described in the next two sections: a name check and a domain check. When both apply they run in parallel under one shared deadline.
    The name check. Jev is asked whether the name is a real person’s and whether the address plausibly belongs to them, but only when all of these hold: the gate passed (valid syntax, an MX that actually resolved, not disposable), you sent a name with at least one letter in it, and screening by model is on for your account. A DNS lookup that failed is not a resolved MX (domain.mx reads unknown), so the model is not asked then either. Otherwise fake_name is not_checked and that request costs nothing.
    The domain check. The same conditions, except that no name is needed: the gate passed with an MX that resolved, and screening by model is on for your account. It is skipped when the domain is a major consumer mailbox provider (Gmail, Outlook, Yahoo and the like), which answers ok without a call, and when the free code check already found a domain typo, in which case domain_check stays not_checked because domain_typo and its suggestion already cover it.

The domain check

The free code check catches misspellings of the mail providers on its list, such as gmial.com. The domain check covers what a list cannot: a misspelling of a provider that is not on it, a brand lookalike such as paypa1.com, a placeholder such as asdfgh.com, or a throwaway-style domain that is not on the disposable list. Jev cannot look anything up. It judges the string, choosing one of five descriptions, and that choice becomes screening.domain_check:

ValueMeans
okThe domain reads as a plausible real organisation's or mail provider's domain, including uncommon, new and non-English names. Also the answer, without a model call, for a major consumer mailbox provider such as gmail.com.
typo_likeLooks like a misspelling of a well-known email provider that the free code check does not have on its list.
lookalikeImitates a well-known brand with swapped, added or look-alike characters, such as paypa1.com.
placeholderA placeholder, test, fake or keyboard-mash domain, such as asdfgh.com.
throwaway_likeLooks like a temporary or throwaway inbox service that is not on the disposable list.
uncertainThe model answered, but not confidently enough to label the domain either way. Adds no reason.
not_checkedThe domain was not judged: the conditions above did not all hold, the code typo check already fired, or the call failed.
  • typo_like, lookalike, placeholder and throwaway_like add the reason domain_suspicious, which sends the signup to review. Never a block.
  • A suspicious label needs a confident answer. A less confident one is reported as uncertain, which adds no reason, and so is a less confident legitimate.
  • It never suggests a correction. Jev only picks a description, so this check never sets typo_suggestion; a corrected domain only ever comes from the free code check.
  • Only the domain is sent. The request carries the domain name and the mail-provider label Sortance already derived for it, and nothing else: not the part before the @, not the full address, not the name. It is a separate request from the name check for exactly that reason.
  • Cached per domain for 30 days. An answer is kept against the domain, as the label alone, and reused for every account. A cached answer means no model call and adds nothing to cost_usd. A failed call is never cached.

The model deadline and degraded

The model calls have a total budget of 600 ms, including at most one retry when the model is overloaded and enough of the budget is left. When both the name check and the domain check apply, they run in parallel under that same 600 ms, not one budget each, and each succeeds or fails on its own. If a request times out, is overloaded, is rejected or answers in a shape Sortance does not accept, the request still returns 200: the label that request feeds (fake_name or domain_check) is not_checked, degraded is true, and everything else, including the other model request if it answered, is still there. A screening step that fails your signup form is a worse outcome than a missing label.

degraded means a model request was made and did not answer. When none was made, because the gate failed, the MX did not resolve, the account has it turned off, the domain was answered from the cache or the consumer-provider list, there was no name for the name check, or the model is not yet enabled on this deployment, degraded is false.

Its own meter

  • A separate monthly allowance of 10,000 screenings by default, metered on the user who owns the key, across every key they hold. One request is one screening, whether the model was asked once, twice or not at all.
  • cost_usd is what the model requests cost Sortance: each is priced on its input tokens, and the two are added when both run. A domain answered from the cache costs 0.
  • Screenings do not count against your verification allowance, and verifications do not count against screenings.
  • When it is spent, /v1/screen returns 402 with code screening_quota_exceeded. /v1/verify carries on.
  • GET /v1/usage reports screenings used this month and the screening quota alongside the verification figures. See pricing.

Privacy

Screening can make two kinds of request to TypeSafe’s Jev model, and they carry different data:

  • The name check. When you send a name and the address passes the gate, the address, the name and the results of the free code checks (including any suggested corrected address) are sent so Jev can judge whether the name is real. Nothing is sent for it when you omit the name.
  • The domain check. When the address passes the gate and the domain is not a major consumer mailbox provider or already in the cache, the domain name and its mail-provider label are sent, and nothing else: no address, no part before the @, no name. The answer is cached against the domain for 30 days.

That is the only data that leaves Sortance for screening. TypeSafe is listed as a sub-processor on the trust page.

Sortance stores neither the name nor the address. Each screening keeps a keyed hash of the address, whether a name was supplied, the labels and reasons, the model version, the cost and the time. No column holds the name, and nothing that could recover it (no hash, length or script). Only the labels describing how it compared to the address and whether it looked real are kept. Neither the name nor the address is written to a log.

The model step is on by default. If your contracts require it, it can be turned off for your account on request. That turns off both requests, the name check and the domain check (domain_check reads not_checked); the gate and the code checks keep working without them.

From an agent

The MCP server exposes the same call as the screen_contact tool, with email required and name optional. It returns the same response, counts against the same screening allowance, and its trace_id starts with sc_.