Skip to content
v1

API Documentation

You send a phone number. We tell you if it has a WhatsApp account, plus what we know about the number. You can check an email too — alongside the number, or on its own.

Start here

Three steps to your first result:

  1. Create a free account.
  2. Make an API key in your dashboard.
  3. Send your first request.

Every request needs an API key. There is no way to use the API without one.

Base URL

Send all requests to this address:

https://api.waverify.app

Add the path to the end. For example, the check endpoint is https://api.waverify.app/api/v1/check.

Always use https. Plain http does not work.

Your API key

Send your key in a header called X-API-Key:

X-API-Key: wav_live_xxxxxxxxxxxxxxxxxxxxxxxx

You see the full key only once — when you create it. We do not keep a copy, so we cannot show it to you again. Save it somewhere safe straight away.

Keep the key in an environment variable. Do not write it directly in your code, and do not upload it to GitHub. If someone else gets your key, delete it in the dashboard and make a new one.

If you are signed in to the dashboard, your session uses a short-lived Bearer token instead of a key. Both work on the same endpoint. We record which one you used for each request.

Check a number

POST/api/v1/check

Send one phone number. Write it in international format: a + sign, then the country code, then the number.

Spaces, dashes and brackets are fine — we remove them for you. A number without a country code is rejected. We never guess the country.

What you send

FieldTypeDescription
phonestringRequired. International format, for example +8801712345678.
emailstringOptional. If you know an email for this person, send it and we check whether it is a real, working address — and look it up on Gravatar. Leave it out and nothing changes.

Example request

curl -X POST https://api.waverify.app/api/v1/check \
  -H "Content-Type: application/json" \
  -H "X-API-Key: wav_live_your_api_key" \
  -d '{"phone": "+8801712345678"}'

With an email

The email is extra. It never changes the WhatsApp part of the answer. A bad email does not fail the request either: you still get status 200, with the problem described in email_info. See Email lookup below.

curl -X POST https://api.waverify.app/api/v1/check \
  -H "Content-Type: application/json" \
  -H "X-API-Key: wav_live_your_api_key" \
  -d '{"phone": "+8801712345678", "email": "someone@example.com"}'

What you get back

The number is on WhatsApp

200 OK
{
  "success": true,
  "phone": "+8801712345678",
  "exists": true,
  "display_name": "Acme Store",
  "name_source": "business_verified",
  "about": "Open 9-6, Sat closed",
  "business": true,
  "profile_photo": "https://cdn.waverify.app/p/9f2c.jpg",
  "profile_photo_id": "1721728451",
  "device_count": 2,
  "number_info": {
    "country_code": "+880",
    "region": "BD",
    "location": "Bangladesh",
    "carrier": "Grameenphone",
    "line_type": "mobile",
    "timezones": ["Asia/Dhaka"],
    "international_format": "+880 1712-345678",
    "national_format": "01712-345678"
  },
  "email_info": null,
  "gravatar": null,
  "response_time_ms": 214,
  "cached": false,
  "checked_at": "2026-07-23T10:04:11Z"
}

The number is not on WhatsApp

This is still a successful request. You get status 200 with exists: false, and it uses one request from your monthly total.

200 OK
{
  "success": true,
  "phone": "+14155552671",
  "exists": false,
  "display_name": null,
  "name_source": null,
  "about": null,
  "business": false,
  "profile_photo": null,
  "profile_photo_id": null,
  "device_count": null,
  "number_info": {
    "country_code": "+1",
    "region": "US",
    "location": "San Francisco, CA",
    "carrier": null,
    "line_type": "fixed_line_or_mobile",
    "timezones": ["America/Los_Angeles"],
    "international_format": "+1 415-555-2671",
    "national_format": "(415) 555-2671"
  },
  "email_info": null,
  "gravatar": null,
  "response_time_ms": 187,
  "cached": false,
  "checked_at": "2026-07-23T10:04:12Z"
}

Fields in the answer

FieldTypeDescription
successbooleanAlways true when the request worked.
phonestringThe number you sent, rewritten in the standard international format.
existsbooleantrue if this number has a WhatsApp account.
display_namestring | nullThe name on the account. Business accounts have one. Personal accounts are usually null — WhatsApp does not give a stranger the name. This is normal, not an error.
name_sourcestring | nullWhere the name came from: business_verified, business_name or contact_name. null when there is no name.
aboutstring | nullThe short “about” text on the account. null if the account hides it.
businessbooleantrue if this is a WhatsApp Business account.
profile_photostring | nullLink to the profile picture. null if the account hides it. The link stops working after a few hours, so download the picture if you need to keep it.
profile_photo_idstring | nullAn id for the current picture. It changes when the account changes its picture, so you can save it and see when the picture is new.
device_countinteger | nullHow many devices the account uses. 1 means the phone only. More means WhatsApp Web or Desktop is also linked. null if we could not tell.
number_infoobjectFacts about the number itself. Always there, even when the number has no WhatsApp account. See “Number details”.
email_infoobject | nullOur verdict on the email you sent: whether it is a real address, and whether its domain can receive mail. null if you sent no email. See “Email lookup”.
gravatarobject | nullThe public Gravatar profile for the email you sent. null if you sent no email, the address was malformed, or it has no profile. See “Email lookup”.
response_time_msintegerHow long the check took on our server, in milliseconds.
cachedbooleantrue if we answered from a saved recent result instead of checking again.
checked_atstringDate and time of the check, in UTC.

Names, and why they are missing

Business accounts give you a real name. Personal accounts almost never do: WhatsApp does not show a personal name to someone who is not a contact. So display_name is usually null for a normal person.

This is not an error. Do not show it to your users as one. Use name_source to explain what you did get.

about and profile_photo do come back for personal accounts, as long as the person kept the normal privacy setting. They are null if the person hid them.

Profile pictures expire

profile_photo is a WhatsApp link that stops working after a few hours. Do not save the link. If you need the picture, download it and keep your own copy.

To spot a new picture without downloading anything, save profile_photo_id and compare it next time.

Number details

Every answer has a number_info object. It comes from the number itself, not from WhatsApp, so it is filled in even when exists is false.

FieldTypeDescription
country_codestring | nullThe country calling code, for example +880.
regionstring | nullTwo-letter country code, for example BD.
locationstring | nullThe country, or the city when we know it.
carrierstring | nullThe mobile network. null for landlines and for numbers that moved to another network.
line_typestringThe kind of line. Always has a value — unknown when we cannot tell.
timezonesstring[]Time zones for this number, for example ["Asia/Dhaka"]. Can be empty.
international_formatstring | nullThe number written for use from any country.
national_formatstring | nullThe number written the way people write it at home.

Values for line_type

mobilefixed_linefixed_line_or_mobiletoll_freepremium_rateshared_costpersonal_numberpageruanvoicemailunknown

Email lookup

Send an email and you get two things back: email_info, our verdict on the address itself, and gravatar, the public profile behind it.

Is the email real?

We check two things without ever sending a message: the address is written correctly, and its domain publishes somewhere to deliver mail.

That is as far as anyone can honestly go. The only way to prove one exact mailbox exists is to try to send to it, which gets the sender blocked, so we do not do it. Read deliverable as “this domain accepts mail”, not “this person exists”.

{
  "email": "jane@acme.com",
  "syntax_valid": true,
  "domain": "acme.com",
  "deliverable": true,
  "mx_hosts": ["mx.acme.com"],
  "disposable": false,
  "role_account": false,
  "free_provider": false,
  "status": "valid",
  "reason": null
}

A bad email is not an error

You still get status 200, and the WhatsApp part of the answer is unaffected. The problem is described in email_info instead.

200 OK
{
  "email": "jane@@acme",
  "syntax_valid": false,
  "domain": "acme",
  "deliverable": null,
  "mx_hosts": [],
  "disposable": false,
  "role_account": false,
  "free_provider": false,
  "status": "invalid_syntax",
  "reason": "The part after the @-sign contains invalid characters: '@'."
}

deliverable has three values

true and false mean what you expect. null means we could not find out — usually the domain lookup timed out.

null is not false. If your code does if (!deliverable) it will call good addresses bad whenever a lookup is slow. Check the three cases separately.

FieldTypeDescription
emailstringThe address you sent, lowercased and tidied up.
syntax_validbooleantrue if the address is written correctly and could exist.
domainstring | nullThe part after the @. Treat it as a guess when syntax_valid is false.
deliverableboolean | nulltrue if the domain has a mail server, false if it does not. null means we could not find out — that is not the same as false, so do not treat it as a bad address.
mx_hostsstring[]The domain's mail servers, best first. Empty when there are none.
disposablebooleantrue for a known throwaway-inbox service. These do receive mail, which is exactly why it is worth knowing.
role_accountbooleantrue for a shared mailbox like info@ or support@. Real, but no one person behind it.
free_providerbooleantrue for a consumer provider such as Gmail. Normal for a personal address.
statusstringOne word for the whole verdict: valid, invalid_syntax, domain_not_found, no_mail_server, disposable, or unknown.
reasonstring | nullA sentence explaining status. null when the address is fine.

Values for status

validinvalid_syntaxdomain_not_foundno_mail_serverdisposableunknown

disposable wins over valid when both apply. Those addresses do receive mail — that is exactly why they are worth flagging.

Checking an email on its own

If the email is all you have, post it to /check/email. You get the same email_info, plus any Gravatar profile — with no phone number involved.

Two things make this route different. It never contacts WhatsApp, so it keeps working even when a number check cannot. And it does not use up any of your monthly requests — only the per-minute rate limit applies.

curl -X POST https://api.waverify.app/api/v1/check/email \
  -H "Content-Type: application/json" \
  -H "X-API-Key: wav_live_your_api_key" \
  -d '{"email": "someone@example.com"}'
200 OK
{
  "success": true,
  "email": "jane@acme.com",
  "email_info": {
    "email": "jane@acme.com",
    "syntax_valid": true,
    "domain": "acme.com",
    "deliverable": true,
    "mx_hosts": ["mx.acme.com"],
    "disposable": false,
    "role_account": false,
    "free_provider": false,
    "status": "valid",
    "reason": null
  },
  "gravatar": null,
  "response_time_ms": 96,
  "cached": false,
  "checked_at": "2026-07-29T10:04:11Z"
}

Gravatar profile

We also look the address up on Gravatar and put any public profile in gravatar.

Most emails have no Gravatar profile, so expect null often. It is also null when you sent no email, or when the address was too malformed to look up.

{
  "display_name": "Jane Roe",
  "about": "Product designer.",
  "location": "Dhaka, Bangladesh",
  "job_title": "Designer",
  "company": "Acme",
  "pronouns": null,
  "avatar_url": "https://gravatar.com/avatar/…",
  "profile_url": "https://gravatar.com/janeroe",
  "verified_accounts": [
    { "service": "Twitter", "url": "https://twitter.com/janeroe" },
    { "service": "LinkedIn", "url": "https://linkedin.com/in/janeroe" }
  ]
}

Every field inside can be null — people fill in only what they want to.

FieldTypeDescription
display_namestring | nullThe name on the Gravatar profile.
aboutstring | nullThe short text the person wrote about themselves.
locationstring | nullWhere the person says they are.
job_titlestring | nullTheir job title.
companystring | nullWhere they work.
pronounsstring | nullThe pronouns they list.
avatar_urlstring | nullLink to their picture.
profile_urlstring | nullLink to the full Gravatar profile.
verified_accountsarrayOther accounts they have proven are theirs. Each item has service and url. Can be empty.

Errors

Every error looks the same. In your code, check error.code. Do not check the message text — the wording can change, but the code will not.

402 Payment Required
{
  "success": false,
  "error": {
    "code": "quota_exceeded",
    "message": "You have used all 100 requests included in the Free plan this period.",
    "details": {}
  }
}

List of error codes

StatusCodeWhat it means
401authentication_failedYour API key is missing, wrong, or was deleted.
402quota_exceededYou have used all the requests in your plan this month.
403email_not_verifiedYou have not confirmed your email address yet.
422validation_errorThe phone number is missing, or it is not a real number.
429rate_limit_exceededYou sent too many requests in one minute. Wait, then retry.
502provider_errorOur WhatsApp connection is down right now. Please try again soon.

Limits

There are two separate limits. Please plan for both.

1. Requests per minute

Free: 10. Starter: 60. Pro: 300. Enterprise: your own limit.

If you go over, you get status 429 with the code rate_limit_exceeded. Wait a short time, then send the request again.

2. Requests per month

Each plan includes a number of requests per month. When you use them all, you get status 402 with the code quota_exceeded. You can upgrade your plan at any time.

If you check the same number twice in a short time, the second answer comes from our saved results and is marked cached: true. It is faster, but it still counts towards your monthly total.

Need help?

This page is the full reference. There is one endpoint, and everything it can return is listed above.

If something does not work the way this page describes, email support@waverify.app. Please include the error.code you received and the time of the request — that is enough for us to find it in our logs.