> ## Documentation Index
> Fetch the complete documentation index at: https://docs.finowl.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Validate an email

> Check whether an email address can receive mail, and catch typos, disposable and role addresses.

### Good to know

* **Branch on `verdict`.** `valid` means send, `invalid` means the address cannot receive mail, and `risky` means it probably can but deserves a second look. Use `reason` to explain why.
* **`valid` is about the domain, not the mailbox.** Finowl never connects to the mail server, so it cannot tell whether `jane@` exists at a valid domain. Confirm ownership with a verification email when it matters.
* **`risky` has two causes.** `disposable` addresses work but are usually thrown away within hours. `a_record_fallback` means the domain has no MX record, which often means mail was never set up.
* **Offer `did_you_mean`, don't apply it.** For `jane@gmail.co`, show "Did you mean [jane@gmail.com](mailto:jane@gmail.com)?" and let the user accept it. The address they typed might be correct.
* **Bad syntax is a `200`, not an error.** Send raw form input and read `reason: invalid_syntax`. Only an empty or over-long string returns `invalid_input`.
* **Send internationalized domains as punycode.** Convert `jane@münchen.de` to `jane@xn--mnchen-3ya.de` first. Unicode domains return `reason: invalid_syntax`.
* **`role` doesn't change the verdict.** Addresses like `support@` are real, but they reach a team, not a person.
* **`dns_unavailable` is free.** If DNS doesn't answer, the request returns `503` and is not charged. Try again after a short delay.

<Card title="Guide: sign-up fraud checks" icon="user-shield" href="/guides/sign-up-fraud-checks">
  Validate the email, phone and IP address together when someone registers.
</Card>


## OpenAPI

````yaml api-reference/openapi.yaml POST /v1/email/validate
openapi: 3.1.0
info:
  title: Finowl API
  version: '1.0'
  description: >
    Check emails and passwords against known data breaches, predict gender from
    names

    and emails, check cold-email compliance, validate email addresses and phone
    numbers,

    look up companies by domain and geolocate IP addresses. Every endpoint is a
    JSON

    `POST` authenticated with a bearer API key.
servers:
  - url: https://api.finowl.co
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Breaches
    description: Check whether an email or password appears in known data breaches.
  - name: Gender
    description: Predict gender from a name or email, one at a time or in batches.
  - name: Compliance
    description: >-
      Place an email address in a jurisdiction and return that jurisdiction's
      cold-email rule.
  - name: Email validation
    description: >-
      Check whether an email address can receive mail, and catch typos,
      disposable and role addresses.
  - name: Company
    description: Look up public company details from a domain or work email.
  - name: IP
    description: Geolocate an IP address and flag hosting, proxy and Tor traffic.
  - name: Phone
    description: Validate, format and classify a phone number.
paths:
  /v1/email/validate:
    post:
      tags:
        - Email validation
      summary: Validate an email
      description: >
        Checks whether an email address can receive mail: syntax, whether the
        domain exists,

        its mail servers, disposable and role addresses, free mailbox providers
        and likely typos.

        The check runs on Finowl's own infrastructure using public DNS. Finowl
        never connects

        to the mailbox, so a `valid` verdict means the domain accepts mail, not
        that this

        particular mailbox exists.


        Malformed addresses are not rejected. They return `200` with `verdict:
        invalid` and

        `reason: invalid_syntax`.


        Costs **1 credit**. A `503` where the lookup could not complete is not
        charged.
      operationId: validateEmail
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - email
              properties:
                email:
                  type: string
                  minLength: 1
                  maxLength: 254
                  description: >-
                    The address to validate. Leading and trailing spaces are
                    removed. Any string from 1 to 254 characters is accepted, so
                    you can send raw form input and read the verdict. Send
                    internationalized domains in punycode (`xn--`) form. Unicode
                    domains return `invalid_syntax`.
                  example: jane@gmail.co
            examples:
              valid:
                summary: Valid address
                value:
                  email: jane@gmail.com
              typo:
                summary: Typo in the domain
                value:
                  email: jane@gmail.co
              disposable:
                summary: Disposable address
                value:
                  email: a@mailinator.com
              role:
                summary: Role address
                value:
                  email: info@stripe.com
              not_found:
                summary: Domain does not exist
                value:
                  email: a@nonexistent-domain-xyz123.com
              bad_syntax:
                summary: Invalid syntax
                value:
                  email: jane.doe@
      responses:
        '200':
          description: >-
            The check completed. `invalid` and `risky` verdicts are still
            successful responses.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmailValidationResult'
              examples:
                valid:
                  summary: Valid address
                  value:
                    email: jane@gmail.com
                    verdict: valid
                    reason: accepted
                    syntax_valid: true
                    local_part: jane
                    domain: gmail.com
                    domain_exists: true
                    mx_found: true
                    mx_records:
                      - gmail-smtp-in.l.google.com
                      - alt1.gmail-smtp-in.l.google.com
                      - alt2.gmail-smtp-in.l.google.com
                      - alt3.gmail-smtp-in.l.google.com
                      - alt4.gmail-smtp-in.l.google.com
                    mail_provider: google
                    disposable: false
                    role: false
                    free_provider: true
                    did_you_mean: null
                typo:
                  summary: Typo in the domain
                  value:
                    email: jane@gmail.co
                    verdict: invalid
                    reason: null_mx
                    syntax_valid: true
                    local_part: jane
                    domain: gmail.co
                    domain_exists: true
                    mx_found: false
                    mx_records: []
                    mail_provider: null
                    disposable: false
                    role: false
                    free_provider: false
                    did_you_mean: jane@gmail.com
                disposable:
                  summary: Disposable address
                  value:
                    email: a@mailinator.com
                    verdict: risky
                    reason: disposable
                    syntax_valid: true
                    local_part: a
                    domain: mailinator.com
                    domain_exists: true
                    mx_found: true
                    mx_records:
                      - mail.mailinator.com
                      - mail2.mailinator.com
                    mail_provider: other
                    disposable: true
                    role: false
                    free_provider: false
                    did_you_mean: null
                role:
                  summary: Role address
                  value:
                    email: info@stripe.com
                    verdict: valid
                    reason: accepted
                    syntax_valid: true
                    local_part: info
                    domain: stripe.com
                    domain_exists: true
                    mx_found: true
                    mx_records:
                      - aspmx.l.google.com
                      - alt1.aspmx.l.google.com
                      - alt2.aspmx.l.google.com
                      - aspmx2.googlemail.com
                      - aspmx3.googlemail.com
                    mail_provider: google
                    disposable: false
                    role: true
                    free_provider: false
                    did_you_mean: null
                not_found:
                  summary: Domain does not exist
                  value:
                    email: a@nonexistent-domain-xyz123.com
                    verdict: invalid
                    reason: domain_not_found
                    syntax_valid: true
                    local_part: a
                    domain: nonexistent-domain-xyz123.com
                    domain_exists: false
                    mx_found: false
                    mx_records: []
                    mail_provider: null
                    disposable: false
                    role: false
                    free_provider: false
                    did_you_mean: null
                bad_syntax:
                  summary: Invalid syntax
                  value:
                    email: jane.doe@
                    verdict: invalid
                    reason: invalid_syntax
                    syntax_valid: false
                    local_part: ''
                    domain: ''
                    domain_exists: false
                    mx_found: false
                    mx_records: []
                    mail_provider: null
                    disposable: false
                    role: false
                    free_provider: false
                    did_you_mean: null
        '400':
          $ref: '#/components/responses/InvalidInput'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          description: >-
            `dns_unavailable`: DNS did not answer, so the domain could not be
            checked. Not charged. Retry after a short delay.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: dns_unavailable
        '504':
          $ref: '#/components/responses/Unconfirmed'
components:
  schemas:
    EmailValidationResult:
      type: object
      required:
        - email
        - verdict
        - reason
        - syntax_valid
        - local_part
        - domain
        - domain_exists
        - mx_found
        - mx_records
        - mail_provider
        - disposable
        - role
        - free_provider
        - did_you_mean
      properties:
        email:
          type: string
          description: The address that was checked, after trimming.
        verdict:
          type: string
          enum:
            - valid
            - risky
            - invalid
          description: >
            The overall answer. Branch on this.

            - `valid`: the syntax is correct and the domain accepts mail. The
            mailbox itself is not probed.

            - `risky`: the address can probably receive mail, but it is
            disposable or the domain has no MX record and relies on its A or
            AAAA record.

            - `invalid`: the address cannot receive mail.
        reason:
          type: string
          enum:
            - accepted
            - invalid_syntax
            - domain_not_found
            - null_mx
            - no_mail_server
            - disposable
            - a_record_fallback
          description: >
            The most important reason for the verdict.

            - `accepted` (`valid`): the domain has mail servers.

            - `invalid_syntax` (`invalid`): the address is not a well-formed
            email address.

            - `domain_not_found` (`invalid`): the domain does not exist in DNS.

            - `null_mx` (`invalid`): the domain publishes a null MX record (RFC
            7505), which means it accepts no mail.

            - `no_mail_server` (`invalid`): the domain has no MX record and no A
            or AAAA record.

            - `disposable` (`risky`): the domain is a known disposable email
            service.

            - `a_record_fallback` (`risky`): the domain has no MX record, so
            mail would go to its A or AAAA record (RFC 5321). This often means
            mail is not set up.
        syntax_valid:
          type: boolean
          description: '`true` when the address is well formed.'
        local_part:
          type: string
          description: >-
            The part before the `@`. Empty when the syntax is invalid and it
            cannot be found.
        domain:
          type: string
          description: >-
            The part after the `@`, lowercased. Empty when the syntax is invalid
            and it cannot be found.
        domain_exists:
          type: boolean
          description: >-
            `true` when the domain exists in DNS. `false` when the syntax is
            invalid.
        mx_found:
          type: boolean
          description: '`true` when the domain publishes at least one usable MX record.'
        mx_records:
          type: array
          maxItems: 10
          items:
            type: string
          description: >-
            The domain's mail server hostnames, most preferred first, lowercase
            and without a trailing dot. At most 10. Empty when none were found.
        mail_provider:
          type:
            - string
            - 'null'
          enum:
            - google
            - microsoft
            - yahoo
            - apple
            - zoho
            - proton
            - yandex
            - mimecast
            - proofpoint
            - fastmail
            - gmx
            - ovh
            - amazon_ses
            - other
            - null
          description: >-
            Who runs the domain's mail servers, detected from the MX hostnames.
            `other` for any other host. `null` when there is no MX record. Treat
            values you don't recognize like `other`.
        disposable:
          type: boolean
          description: >-
            `true` when the domain, or a parent domain, is on the open-source
            list of disposable email services.
        role:
          type: boolean
          description: >-
            `true` when the local part, ignoring any `+tag`, is a role mailbox
            such as `admin`, `info`, `sales`, `support` or `noreply`. Role
            addresses are usually shared by a team. This does not change the
            verdict.
        free_provider:
          type: boolean
          description: >-
            `true` when the domain is a free personal mailbox provider such as
            `gmail.com`, `outlook.com` or `web.de`.
        did_you_mean:
          type:
            - string
            - 'null'
          description: >-
            A corrected address when the domain looks like a typo of a popular
            mail domain, such as `gmail.co` for `gmail.com`. `null` otherwise.
            Suggest it to the user. Never replace the address without asking.
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
          properties:
            code:
              type: string
              description: >-
                A stable, machine-readable error code. See the Errors page for
                the full list.
              example: invalid_input
  responses:
    InvalidInput:
      description: The request body is not valid. Fix the request before trying again.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: invalid_input
    Unauthorized:
      description: The API key is missing, malformed or revoked.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: unauthorized
    TooManyRequests:
      description: >
        `rate_limited` (too many requests this minute), `quota_exceeded` (not
        enough credits) or

        `provider_rate_limited` (Finowl intelligence is busy). Check the
        `Retry-After` header.
      headers:
        Retry-After:
          description: Seconds to wait before trying again.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: rate_limited
    Unconfirmed:
      description: >-
        The lookup did not finish in time. It may or may not have run, and the
        request was charged.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: provider_response_unconfirmed
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        Your Finowl API key, for example `finowl_3f9a...`. Create keys in the
        dashboard under **API Keys**.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.