Home / Developers / Verify a list with the API

Verify a list with the API

Submit up to 100,000 addresses in one call, wait for the list to finish, and download the results as JSON or CSV.

Updated 2026-10-18

TL;DR

  • Four calls cover a whole list: POST /v1/lists, poll GET /v1/lists/{id}, read GET /v1/lists/{id}/results, then DELETE /v1/lists/{id}.
  • One list holds up to 100,000 unique addresses. You are charged one credit per unique address when you submit, and unknown results are refunded when the list finishes.
  • Fetch results as JSON with offset and limit, or as CSV with format=csv. Or pass a callback_url and skip polling.

When to use a list instead of /verify

POST /verify is for one address at a time, such as a signup form. Anything from a few dozen addresses up should go through the list endpoints. A list is cheaper in round trips, it is checked in the background, and you do not have to hold a connection open. If you need the same thing in a spreadsheet instead, the bulk upload does it without code. You need an API key, which is issued on request through the contact page with free test credits. The reference for every endpoint is the email verification API page.

1. Submit the list

Post a JSON array of addresses. name is optional and helps you recognise the list later. Addresses are trimmed and lower-cased, blanks are ignored and duplicates are removed (and not charged).

curl -X POST https://mailexchange.com/api/v1/lists \
  -H "Authorization: Bearer $MAILEXCHANGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"emails": ["[email protected]", "[email protected]", "[email protected]"], "name": "october-leads"}'

The answer is 201 and a summary of the list. The numbers below are an example:

{
  "id": 4821,
  "name": "october-leads",
  "status": "processing",
  "total": 2,
  "duplicates_removed": 1,
  "checked": 0,
  "counts": {"deliverable": 0, "risky": 0, "undeliverable": 0, "unknown": 0},
  "created_at": 1792310400,
  "finished_at": null
}

Credits are taken now, one per unique address. If you do not have enough you get 402 and nothing is created. A list over 100,000 addresses gets 400. Timestamps are Unix seconds.

2. Poll for status

GET /v1/lists/4821 returns the same summary, updated. The status moves through processing and checking to done. It is failed if the list could not be completed. checked against total gives you a progress bar, and counts fills in as results arrive. Polling every 5 to 10 seconds is plenty; there is no reason to poll faster, and polls count towards your rate limit.

Prefer not to poll? Add "callback_url": "https://you.example/hook" (https only) to the submit call and we POST a signed list.done event when the list finishes. See webhooks and callbacks.

3. Fetch the results

Results come back in the order the unique addresses were added. Each row has the same fields as a single /verify response, plus a provider field.

curl -H "Authorization: Bearer $MAILEXCHANGE_API_KEY" \
  "https://mailexchange.com/api/v1/lists/4821/results?offset=0&limit=1000"

The response is {"results": [...]}. The default limit is 1,000 and a request returns at most 10,000 rows. To read a bigger list, keep adding the page size to offset until a page comes back with fewer rows than you asked for. Rows not yet checked have the state pending.

As CSV

Add format=csv to get a file. The columns are email, state, reason, score, accept_all, role, free, disposable, mx, provider, with booleans written as True and False. The same offset and limit apply, so for a long list ask for the pages you need. Each page has its own header row.

curl -H "Authorization: Bearer $MAILEXCHANGE_API_KEY" \
  "https://mailexchange.com/api/v1/lists/4821/results?format=csv&offset=0&limit=10000" \
  -o results-0.csv

4. Delete the list

When you have your results, remove the list and its data:

curl -X DELETE -H "Authorization: Bearer $MAILEXCHANGE_API_KEY" \
  https://mailexchange.com/api/v1/lists/4821
# {"deleted": true}

The whole flow in a shell script

This uses curl and jq. It reads emails.txt, submits it, waits, saves the first 1,000 results as CSV and deletes the list.

#!/usr/bin/env bash
set -euo pipefail
API=https://mailexchange.com/api/v1
AUTH="Authorization: Bearer $MAILEXCHANGE_API_KEY"

ID=$(jq -R -s '{emails: (split("\n") | map(select(length > 0))), name: "shell-example"}' emails.txt \
  | curl -sf -X POST "$API/lists" -H "$AUTH" -H "Content-Type: application/json" -d @- \
  | jq -r .id)

until [ "$(curl -sf -H "$AUTH" "$API/lists/$ID" | jq -r .status)" = "done" ]; do
  echo "waiting for list $ID..."; sleep 10
done

curl -sf -H "$AUTH" "$API/lists/$ID" | jq .counts
curl -sf -H "$AUTH" "$API/lists/$ID/results?format=csv&limit=1000" -o results.csv
curl -sf -X DELETE -H "$AUTH" "$API/lists/$ID"

The script does not stop on a failed status, so add a check if you run it unattended. Complete versions with pagination, a decision function and 429 retries are in Python, Node.js, PHP, Ruby, Go, Java and C#.

What it costs

One credit per unique address, taken at submission. Duplicates cost nothing. Unknown results are refunded automatically when the list is done, so you only pay for answers. Credits never expire and prices start at $29 for 5,000, see pricing. Read errors, credits and rate limits for the 402 and 429 cases.

Questions

How many addresses can I send in one list?

Up to 100,000 unique addresses per list. Larger lists need to be split into several POST /lists calls.

Can I read results before the list is finished?

Yes. Rows that are not checked yet come back with the state pending, so you can show progress, but only trust final states once the list status is done.

How do I download a list as CSV?

Call GET /lists/{id}/results?format=csv. Offset and limit still apply, so for lists over 1,000 rows pass limit and page through with offset.

Related