{
    "openapi": "3.0.3",
    "info": {
        "title": "Email Validator \u2014 Ksty.ch",
        "version": "1.0",
        "description": "Validate email addresses \u2014 syntax, MX/deliverability, disposable and role-based detection.\n\n# Email Validator API\n\nValidates an email address across four independent layers \u2014 syntax, domain deliverability (MX), disposable-domain detection and role-address detection \u2014 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.\n\n## What \"valid\" actually means\n\nThere is no single test that proves an address will receive mail \u2014 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.\n\n```\nPOST /api/v1/email-validator\nX-Api-Key: ksty_test_...\nContent-Type: application/json\n\n{\"email\": \"jane.doe@example.com\"}\n```\n\n```json\n{\n  \"email\": \"jane.doe@example.com\",\n  \"normalized\": \"jane.doe@example.com\",\n  \"valid\": true,\n  \"checks\": { \"syntax\": true, \"mx\": true, \"disposable\": false, \"role\": false },\n  \"domain\": \"example.com\"\n}\n```\n\n## The four checks\n\n### Syntax\n\nThe 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 \u2014 if syntax fails, nothing downstream can succeed.\n\n### MX / deliverability\n\nThe 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 \u2014 not that the specific mailbox exists, which no lookup can tell you.\n\n```\nexample.com          -> DNS query for MX\n                        -> mail.example.com (has MX)  => deliverable\ntypo-domain.invalid  -> no MX, no A                  => not deliverable\n```\n\n### Disposable\n\nThe 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 \u2014 so a disposable hit forces `valid` to false.\n\n### Role-based\n\nThe 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 \u2014 you might warn on it for a personal-account signup while happily accepting it on a contact form.\n\n## Accuracy & limitations\n\n- **Mailbox existence is out of scope.** A pass means the address is well-formed and its domain can receive mail \u2014 not that the specific inbox exists or is monitored.\n- **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.\n- **Lists are heuristic.** The disposable and role lists are curated and self-contained; they catch the common cases, not every possible domain or alias.\n\n> The MX lookup adds network latency and can occasionally be inconclusive. For high-volume or offline validation \u2014 cleaning a large import, for example \u2014 send `check_mx: false` to run the fast, fully-local syntax and heuristic checks only.\n\n## When to use it\n\n- At signup, to reject typos and disposable addresses before they reach your database.\n- Cleaning an existing list, with `check_mx: false` for speed on large batches.\n- Gating sensitive flows where you want to flag (not necessarily block) role addresses.",
        "x-credit-cost": 1,
        "x-free-grant": {
            "type": "monthly",
            "amount": 50
        },
        "x-sandbox": true
    },
    "servers": [
        {
            "url": "https://ksty.ch/api/v1"
        }
    ],
    "security": [
        {
            "ApiKeyAuth": []
        }
    ],
    "paths": {
        "/email-validator": {
            "get": {
                "operationId": "email-validator_get",
                "summary": "Validate a single email address",
                "x-credit-cost": 1,
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "email": "Jane@Example.com",
                                    "normalized": "jane@example.com",
                                    "valid": true,
                                    "checks": {
                                        "syntax": true,
                                        "mx": true,
                                        "disposable": false,
                                        "role": false
                                    },
                                    "domain": "example.com"
                                },
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "email": {
                                            "type": "string",
                                            "description": "The address exactly as you sent it, trimmed."
                                        },
                                        "normalized": {
                                            "type": "string",
                                            "description": "Lower-cased form \u2014 store this if you deduplicate addresses."
                                        },
                                        "valid": {
                                            "type": "boolean",
                                            "description": "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."
                                        },
                                        "domain": {
                                            "type": "string",
                                            "description": "Domain part of the normalized address, empty when the input had no \"@\"."
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "invalid_request \u2014 No \"email\" parameter was sent, or it was an empty string.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing or invalid API key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "402": {
                        "description": "Insufficient credits \u2014 nothing is charged and the API is never reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "The key is not scoped to this API, or a test key was used on an API without sandbox support",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limit exceeded for this key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "502": {
                        "description": "The API failed after being charged; credits are refunded automatically",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "email",
                        "in": "query",
                        "required": true,
                        "description": "The email address to validate",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "check_mx",
                        "in": "query",
                        "required": false,
                        "description": "Perform an MX/DNS lookup on the domain. Turn off for speed on large batches, or when running without outbound DNS.",
                        "schema": {
                            "type": "boolean"
                        }
                    }
                ]
            },
            "post": {
                "operationId": "email-validator_post",
                "summary": "Validate a single email address",
                "x-credit-cost": 1,
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "email": "Jane@Example.com",
                                    "normalized": "jane@example.com",
                                    "valid": true,
                                    "checks": {
                                        "syntax": true,
                                        "mx": true,
                                        "disposable": false,
                                        "role": false
                                    },
                                    "domain": "example.com"
                                },
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "email": {
                                            "type": "string",
                                            "description": "The address exactly as you sent it, trimmed."
                                        },
                                        "normalized": {
                                            "type": "string",
                                            "description": "Lower-cased form \u2014 store this if you deduplicate addresses."
                                        },
                                        "valid": {
                                            "type": "boolean",
                                            "description": "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."
                                        },
                                        "domain": {
                                            "type": "string",
                                            "description": "Domain part of the normalized address, empty when the input had no \"@\"."
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "invalid_request \u2014 No \"email\" parameter was sent, or it was an empty string.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing or invalid API key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "402": {
                        "description": "Insufficient credits \u2014 nothing is charged and the API is never reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "The key is not scoped to this API, or a test key was used on an API without sandbox support",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limit exceeded for this key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "502": {
                        "description": "The API failed after being charged; credits are refunded automatically",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "email"
                                ],
                                "properties": {
                                    "email": {
                                        "type": "string",
                                        "description": "The email address to validate",
                                        "example": "jane@example.com"
                                    },
                                    "check_mx": {
                                        "type": "boolean",
                                        "description": "Perform an MX/DNS lookup on the domain. Turn off for speed on large batches, or when running without outbound DNS.",
                                        "default": true
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "ApiKeyAuth": {
                "type": "apiKey",
                "in": "header",
                "name": "X-Api-Key"
            }
        },
        "schemas": {
            "Error": {
                "type": "object",
                "properties": {
                    "error": {
                        "type": "object",
                        "properties": {
                            "code": {
                                "type": "string",
                                "description": "Stable machine-readable code \u2014 branch on this, never on the message."
                            },
                            "message": {
                                "type": "string"
                            },
                            "request_id": {
                                "type": "string"
                            }
                        }
                    }
                }
            }
        }
    }
}