Every request carries an API key in a header. Send it as X-API-Key, which is the preferred form, or as a bearer token in Authorization. Both reach the same check and there is no second step: no OAuth flow, no signed request, no per-call nonce.
You create keys in the dashboard under API keys. The plaintext is shown once, at creation. We store only a hash, so a key you lose cannot be recovered and has to be replaced. A key can be revoked at any time and stops working at once.
Keys carry a prefix so you can tell which environment one belongs to. Live keys start zb_live_ and test keys start zb_test_. The key is read from headers only. A key in a query string would end up in access logs, in browser history and in the Referer header of any page you redirect to, so we never look there.
curl -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"]}'
# Also accepted
# Authorization: Bearer zb_live_...{
"statusCode": 401,
"code": "UNAUTHORIZED",
"message": "Invalid API key",
"requestId": "req-8f2c1d"
}Fields
| Field | Type | What it carries |
|---|---|---|
| X-API-Key | header | The key. Preferred over the bearer form. |
| Authorization | header | Bearer plus the key. Accepted as an alternative. |
| Idempotency-Key | header | Optional, on POST /jobs only. See the idempotency page. |
| X-Request-Id | response header | Sent on every response. Quote it when you contact support. |
Reference
| Scope | What it allows | Routes |
|---|---|---|
| verification.run | Run a synchronous check. | POST /verify |
| jobs.manage | Create and cancel bulk jobs. | POST /jobs, POST /jobs/{id}/cancel |
| jobs.view | List jobs and read their results. | GET /jobs, GET /jobs/{id}, /emails, /export |
| credits.view | Read the credit balance and ledger. | GET /credits/balance, GET /credits/ledger |
A key can only call what its scopes allow. Keys cannot manage other keys or change account settings, so a leaked key cannot be used to mint a new one.
Three jobs, three keys, one Friday leak
Say you run a signup form, a nightly list-cleaning job and a CI suite. Give each one its own key. The form needs only the verification.run scope, the nightly job needs jobs.manage and jobs.view, and CI gets a key of its own with a label you'll recognize later.
Now picture the nightly job's key showing up in a pasted log on a Friday afternoon. You create a replacement, deploy it to that one job, confirm the job is using it, and revoke the old key. Your signup form never noticed, because it never shared that key. With a single shared key, the same leak means a rushed deploy across every service you own at the worst hour of the week.
Separate keys also make scopes useful. A leaked key that can only run single checks cannot read your jobs or your ledger, and a key that can only view jobs cannot spend credits.
Where keys leak in practice, and a ten-minute search
Most leaked keys don't come from break-ins. They come from habits. Someone pastes a failing curl command into a chat channel with the header still attached. A debug logger prints full request headers into a tool that forty people can search. Or a .env file gets committed because the ignore rule landed one commit too late.
You can check for all of these before lunch. Search your log tool for the string zb_live_ and read what comes back. Run the same search across your repository history and not only the current tree, because a deleted file still lives in old commits. Then look at your error tracker, since many of them capture outgoing headers unless you scrub them.
Any hit means the key is spent, even if the channel was private. Rotate it. The prefix exists to make this search possible, so put the search on a calendar and don't wait for a scare. When a 401 with the code UNAUTHORIZED appears right after a deploy, check the prefix against the environment first.
Questions developers ask
Can I use a key from the browser?
No. A key in front-end code is public the moment the page loads. Put an endpoint of your own in front of ours and keep the key on your server.
How do I rotate a key with no downtime?
Create the second key first, deploy it, confirm your traffic has moved, then revoke the old one. Both keys work during the overlap.
Which header should I send?
X-API-Key. The bearer form works too, which helps when an HTTP client or gateway already adds an Authorization header for you.
What happens if I send no key?
You get a 401 with the code UNAUTHORIZED. The request never reaches the verification queue, so no credit moves.