Log in Sign up
Docs/Validation/Email Validator

Email Validator v1.0 1 credit / call

GET|POST /api/v1/email-validator

Validate email addresses — syntax, MX/deliverability, disposable and role-based detection.

50 free/month Test keys supported OpenAPI spec Try it in the console
At a glance
Base path
/api/v1/email-validator
Methods
GET or POST — parameters are read identically from the query string or a JSON body
Auth
X-Api-Key header (never Authorization: Bearer)
Price
1 credit per call · about $0.0010 at 1,000 credits/USD
Free tier
50 credits, refilled every month — drawn down before your paid balance
Test mode
Supported — ksty_test_ keys call this API free of charge
Rate limit
60 calls/minute per key by default, raisable per key

Email Validator API

Validates an email address across four independent layers — syntax, domain deliverability (MX), disposable-domain detection and role-address detection — and returns each result separately so you decide how strict to be. Syntax and normalization always run locally; the MX check is a best-effort DNS lookup you can switch off per call.

What "valid" actually means

There is no single test that proves an address will receive mail — the only certain check is to send a message and watch for a bounce, which you cannot do at signup time. Validation is therefore a stack of cheaper signals, each ruling out a different class of bad address, and valid is a conservative summary of them. Treat the individual checks as the real output and valid as a sensible default.

POST /api/v1/email-validator
X-Api-Key: ksty_test_...
Content-Type: application/json

{"email": "jane.doe@example.com"}
{
  "email": "jane.doe@example.com",
  "normalized": "jane.doe@example.com",
  "valid": true,
  "checks": { "syntax": true, "mx": true, "disposable": false, "role": false },
  "domain": "example.com"
}

The four checks

Syntax

The address is parsed against the standard email grammar. This catches typos like a missing @, illegal characters or a malformed domain. It is deterministic and offline, so it always runs first — if syntax fails, nothing downstream can succeed.

MX / deliverability

The gateway looks up the domain's DNS records and asks a simple question: can this domain receive mail at all? A domain with an MX record (or a fallback A record) can accept mail; one with neither cannot, no matter how well-formed the address is. This proves the domain is deliverable — not that the specific mailbox exists, which no lookup can tell you.

example.com          -> DNS query for MX
                        -> mail.example.com (has MX)  => deliverable
typo-domain.invalid  -> no MX, no A                  => not deliverable

Disposable

The domain is matched against a curated list of throwaway / temporary-mailbox providers (Mailinator, Guerrilla Mail, 10 Minute Mail and similar). These addresses are syntactically perfect and often have valid MX, but exist only to slip past a signup — so a disposable hit forces valid to false.

Role-based

The local-part (the text before the @) is matched against common function addresses like info, support, sales or admin. These reach a team rather than a person. It is reported as a flag, not a failure — you might warn on it for a personal-account signup while happily accepting it on a contact form.

Accuracy & limitations

  • Mailbox existence is out of scope. A pass means the address is well-formed and its domain can receive mail — not that the specific inbox exists or is monitored.
  • MX is best-effort. If the DNS lookup is slow or inconclusive it returns null (unknown) rather than failing the call, and an unknown MX result does not on its own make an address invalid.
  • Lists are heuristic. The disposable and role lists are curated and self-contained; they catch the common cases, not every possible domain or alias.

The MX lookup adds network latency and can occasionally be inconclusive. For high-volume or offline validation — cleaning a large import, for example — send check_mx: false to run the fast, fully-local syntax and heuristic checks only.

When to use it

  • At signup, to reject typos and disposable addresses before they reach your database.
  • Cleaning an existing list, with check_mx: false for speed on large batches.
  • Gating sensitive flows where you want to flag (not necessarily block) role addresses.

Endpoints

GETPOST /api/v1/email-validator 1 credit

Validate a single email address

Parameters
ParameterTypeDescription
email required string The email address to validate
check_mx optional boolean Perform an MX/DNS lookup on the domain. Turn off for speed on large batches, or when running without outbound DNS. Defaults to true
Request
curl -X POST "https://ksty.ch/api/v1/email-validator" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"jane@example.com","check_mx":true}'
<?php
$ch = curl_init('https://ksty.ch/api/v1/email-validator');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'X-Api-Key: YOUR_API_KEY',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{    "email": "jane@example.com",    "check_mx": true}',
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
const response = await fetch('https://ksty.ch/api/v1/email-validator', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    "email": "jane@example.com",
    "check_mx": true
})
});
const data = await response.json();
import requests

response = requests.post(
    'https://ksty.ch/api/v1/email-validator',
    headers={'X-Api-Key': 'YOUR_API_KEY'},
    json={
    "email": "jane@example.com",
    "check_mx": true
},
)
data = response.json()
Response 200 OK
{
    "email": "Jane@Example.com",
    "normalized": "jane@example.com",
    "valid": true,
    "checks": {
        "syntax": true,
        "mx": true,
        "disposable": false,
        "role": false
    },
    "domain": "example.com"
}
Response fields
FieldTypeDescription
email string The address exactly as you sent it, trimmed.
normalized string Lower-cased form — store this if you deduplicate addresses.
valid boolean The overall verdict: correct syntax, not a known disposable domain, and the MX check did not come back negative. An inconclusive MX lookup does not make an address invalid.
checks.syntax boolean Whether the address parses as a valid address.
checks.mx boolean|null true when the domain accepts mail, false when it demonstrably does not, and null when the lookup was skipped or inconclusive. Treat null as "unknown", never as a failure.
checks.disposable boolean The domain is a known throwaway-mail provider.
checks.role boolean The local part is a role address (info, support, admin…). Reported but never counted against "valid" — plenty of real users sign up with one.
domain string Domain part of the normalized address, empty when the input had no "@".

Errors

Errors are JSON with a stable error.code — branch on the code, never on the message. Everything below carries the X-Request-Id of the failed call. Requests rejected by this API are not charged, and a 502 after a charge is refunded automatically.

CodeHTTPCause & what to do
invalid_request this API 400 No "email" parameter was sent, or it was an empty string. Send the address you want checked. Note that a malformed address is not an error — it comes back 200 with "valid": false, so you can tell "you called this wrong" apart from "this address is bad".
invalid_key platform 401 The X-Api-Key header is missing, malformed, revoked or expired. Send an active key. Repeated bad keys from one address are throttled, so fix the header rather than retrying.
forbidden_scope platform 403 The key is valid but not scoped to this API, or it is a test key on an API without sandbox support. Add this API to the key's scopes on the Keys page, or use a key scoped to all APIs.
insufficient_credits platform 402 The balance cannot cover the call. Top up. Nothing is charged and the request never reaches the API, so no work is half-done.
rate_limited platform 429 The key exceeded its per-minute limit. Back off and retry. The limit is per key, so a second key does not share the budget.
upstream_error platform 502 The API failed unexpectedly after being charged. Safe to retry — credits are refunded automatically whenever this is returned.

Response headers

Every successful call reports its own cost, so you never have to guess what a request spent or reconcile it later.

HeaderMeaning
X-Credits-Charged Credits taken for this call. Zero on test-mode calls and on errors raised before the work started.
X-Credits-Remaining Balance left afterwards, across per-API grants, promotional and paid credits.
X-Request-Id Identifier for this call. It appears in your usage log and in every error body — quote it in support requests.