Email verification in cURL
curl is the fastest way to find out whether a problem is your code or our API. Every command below runs as written once ZAPBOUNCE_KEY is exported, and each one is the exact request the libraries on the other pages build.
Two flags are worth adding by habit. --fail-with-body makes curl exit non-zero on a 4xx while still printing the error, which is what a shell script needs. -sS keeps the progress bar out of your pipe and leaves errors visible.
curl 7.76 or later for --fail-with-body. Pipe through jq if you want the output readable.
Verify one address
You branch on four results: valid, invalid, catch_all and unknown. Role and disposable are boolean flags beside the result, so test them separately. Unknown is the branch most code forgets, and it is never billed.
export ZAPBOUNCE_KEY="zb_live_..."
# One address.
curl -sS --fail-with-body https://api.zapbounce.com/v1/verify \
-H "Authorization: Bearer $ZAPBOUNCE_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"ada@example.com"}' | jq
# Just the verdict, for a shell condition.
verdict=$(curl -sS https://api.zapbounce.com/v1/verify \
-H "Authorization: Bearer $ZAPBOUNCE_KEY" \
-H "Content-Type: application/json" \
-d "{\"email\":\"$1\"}" | jq -r '.result')
case "$verdict" in
valid) echo "send" ;;
catch_all) echo "segment separately: the domain accepts everything" ;;
unknown) echo "no verdict, nothing billed, try again tomorrow" ;;
*) echo "suppress" ;;
esacVerify a list
Submit a batch rather than looping the single endpoint. We pace probes per receiving mail host, which protects the sending reputation your results depend on.
# Submit a batch from a file of addresses, one per line.
batch_id=$(jq -R -s -c 'split("\n") | map(select(length > 0))' contacts.txt \
| jq -c '{name: "cli-run", emails: .}' \
| curl -sS --fail-with-body https://api.zapbounce.com/v1/batches \
-H "Authorization: Bearer $ZAPBOUNCE_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d @- | jq -r '.batch_id')
echo "batch $batch_id"
# Poll until it finishes. A webhook is better; this is for a one-off.
until [ "$(curl -sS https://api.zapbounce.com/v1/batches/$batch_id \
-H "Authorization: Bearer $ZAPBOUNCE_KEY" | jq -r '.status')" = "complete" ]; do
sleep 10
done
# Page the results through the cursor into one JSONL file.
cursor=""
while :; do
page=$(curl -sS "https://api.zapbounce.com/v1/batches/$batch_id/results?limit=1000${cursor:+&cursor=$cursor}" \
-H "Authorization: Bearer $ZAPBOUNCE_KEY")
echo "$page" | jq -c '.data[]' >> results.jsonl
[ "$(echo "$page" | jq -r '.has_more')" = "true" ] || break
cursor=$(echo "$page" | jq -r '.next_cursor')
done
# What came back unresolved, and therefore free.
jq -r 'select(.billed == false) | .email' results.jsonl | wc -l
# A large file goes up as multipart instead.
curl -sS --fail-with-body https://api.zapbounce.com/v1/files \
-H "Authorization: Bearer $ZAPBOUNCE_KEY" \
-F "file=@contacts.csv" \
-F "email_column=work_email" | jqErrors and retries
A 429 and a 5xx are worth retrying. A 402 for credits and a 400 for a malformed address will fail the same way on every attempt, so stop.
# --fail-with-body: non-zero exit AND the error body, which -f alone swallows.
if ! response=$(curl -sS --fail-with-body https://api.zapbounce.com/v1/verify \
-H "Authorization: Bearer $ZAPBOUNCE_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"ada@example.com"}'); then
code=$(echo "$response" | jq -r '.error.code')
request_id=$(echo "$response" | jq -r '.error.request_id')
echo "failed: $code (request_id=$request_id)" >&2
[ "$code" = "insufficient_credits" ] && exit 2 # retrying will not help
exit 1
fi
# Retry with backoff, honoring Retry-After on a 429.
attempt=0
while [ $attempt -lt 5 ]; do
http_code=$(curl -sS -o /tmp/zb.json -w '%{http_code}' \
https://api.zapbounce.com/v1/verify \
-H "Authorization: Bearer $ZAPBOUNCE_KEY" \
-H "Content-Type: application/json" \
-d "{\"email\":\"$1\"}")
[ "$http_code" = "200" ] && break
[ "$http_code" = "402" ] && { echo "out of credits" >&2; exit 2; }
sleep $(( 2 ** attempt ))
attempt=$(( attempt + 1 ))
done
cat /tmp/zb.json | jqcURL: common questions
Can I put the key in the URL?
We do not accept it there. Query strings land in access logs, in shell history and in the Referer header, and a header costs nothing.
How do I check my balance?
curl -sS https://api.zapbounce.com/v1/credits -H "Authorization: Bearer $ZAPBOUNCE_KEY" | jq. The ledger comes with it.
Is there a CLI?
Not yet. These commands plus jq cover everything a CLI would wrap, and they work on any machine with curl already installed.
Run this cURL code today
100 free checks a month, no card, credits that never expire, and unknown results that cost nothing.