The verification API

One POST for a handful of addresses. Another POST for a list, which you poll and download. Everything else is billing and pagination.

Shell
curl -s -X POST "https://app.zapbounce.com/api/v1/verify" \
  -H "X-API-Key: $ZB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"emails":["ada@example.com"]}'

{
  "node": "verify-1",
  "results": [
    {
      "id": "5821",
      "email": "ada@example.com",
      "status": "catch_all",
      "reasonCode": "CATCH_ALL",
      "provider": "other",
      "flags": [],
      "smtpCode": 250,
      "checkedAt": "2026-10-26T10:02:41.000Z"
    }
  ]
}

Verification is one POST to /v1/verify, with up to 20 addresses in a JSON body. The body keeps your contacts' addresses out of access logs, proxy logs and browser history, which is where a URL query string gets written down.

What comes back

Branch on status. The reason code underneath it is a closed list that can grow, so treat one you don't recognize as extra detail.

FieldWhat it holds
node, resultsThe node that did the work, and one object per address you sent.
emailThe address as you sent it.
statusOne of valid, invalid, catch_all, unknown. It answers one question: does this mailbox exist?
reasonCodeThe narrower cause, from a closed list: MAILBOX_NOT_FOUND, CATCH_ALL, TEMPORARY_FAILURE and so on.
providerThe mailbox provider we detected from the domain's mail servers.
flagsAn array of role, disposable and free_provider. They are flags, never statuses, and they are set even when the status is unknown.
smtpCodeThe literal reply code to RCPT TO, so you can audit our reading. null if the handshake never got that far.
checkedAtWhen we ran the check, ISO 8601 in UTC.

A list, and a job you poll

Shell
curl -s -X POST "https://app.zapbounce.com/api/v1/jobs" \
  -H "X-API-Key: $ZB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"emails":["ada@example.com","grace@example.org"],"source":"api"}'

{ "id": "0b6f2d3e-6a52-4c58-9d0e-3f6a1c9a7e10", "status": "pending", "submittedEmails": 2, "totalEmails": 2 }

# Poll until status is completed, then download
curl -s "https://app.zapbounce.com/api/v1/jobs/0b6f2d3e-6a52-4c58-9d0e-3f6a1c9a7e10/export" \
  -H "X-API-Key: $ZB_API_KEY" -o results.csv

Rate limits and job sizes

SurfaceDefaultWhat to do about it
All requests120 requests per 60-second windowRead X-RateLimit-Limit and X-RateLimit-Window-Seconds. A 429 has no Retry-After, so wait out the window.
Synchronous check1 to 20 addresses a callMore than that is rejected with VERIFY_TOO_MANY. Use a job.
Job creationUp to 50,000 addresses eachSend one large job instead of many small ones.
Result pagespageSize 1 to 100, default 20Loop on page up to meta.totalPages, or download the CSV.
Idempotency window24 hoursReuse one Idempotency-Key per unit of work, so a lost reply can't create a second job.

Two errors need different handling from the rest. A 429 means wait out the window and carry on, with jitter if you run more than one worker. A 422 CREDITS_INSUFFICIENT means the balance is too low, and retrying won't help until credits are added. Neither costs a credit. No error does, because an error means the request never produced a verdict.

At a signup form, budget three seconds for the common case and set your client timeout at ten. A greylisting server can hold the connection that long, and when the timeout fires your code should let the signup through.

Before you build against this

Questions

Am I charged for an unknown result?

No. Unknown means the receiving server would not give a straight answer, and billing for it would make the honest verdict the expensive one. Its credit is refunded.

Is a catch-all result billed?

Yes. One credit buys one definitive result, and valid, invalid and catch_all all count. A catch-all took a full handshake plus a second probe, and it tells you something true about the domain. Unknown results and failed requests cost nothing.

How do I know a job finished?

Poll GET /v1/jobs/{id} until status is completed, partial, failed or cancelled. There is no webhook for customers today.

What happens on a timeout?

Fail open in your code. A verification service should never be the reason a signup fails.

Can I re-check an address?

Yes, and it costs a credit, because the answer genuinely can change between checks.

Get a key

100 free checks a month, no card.