Guidance for AI coding agents working in this repository.
zero-bounce-python-sdk-setup is the ZeroBounce Python SDK, published to PyPI as zerobouncesdk. Callers construct a ZeroBounce with an API key and call methods that return response objects or raise ZBApiException / ZBClientException. HTTP goes through the requests library inside src/zerobouncesdk/zerobouncesdk.py.
src/zerobouncesdk/zerobouncesdk.py: the client._get,_post,_post_form, and_request_jsonare the HTTP entry points.src/zerobouncesdk/zb_warmup.py:ZeroBounceWarmup, the Warmup API client. It has its own host and key, authenticates with anAPI-Keyheader, and reuses the error helpers inzerobouncesdk.py. Keep Warmup methods there, not onZeroBounce.src/zerobouncesdk/zb_*.py: response models, enums, and options.tests/zero_bounce_test_case.py: scripted unit tests. They patchzerobouncesdk.zerobouncesdk.requests.tests/test_offline_edges.py,tests/test_warmup_offline.py,tests/test_security_offline.pyandtests/test_streaming_and_regions_offline.py: more offline suites on the same principle (error bodies and every optional field; the Warmup API; where keys travel and that they never reach an exception; streamed downloads and the regional-host warning).tests/test_offline_api.py: offline suite. It patchesrequests.sessions.Session.send, so the realrequestsclient builds the URL and body and the fake answers without the network.tests/live_api_test.py: opt-in live suite that calls every method of both clients (ZEROBOUNCE_API_KEY,ZEROBOUNCE_WARMUP_API_KEY). Free calls by default;ZEROBOUNCE_LIVE_SPEND=1adds credit-spending calls and bulk round trips,ZEROBOUNCE_LIVE_CHANGES=1reversible account changes.pytestand CI never run it:python -m unittest tests.live_api_test -v.tests/zero_bounce_integration_test.py: the original live tests.pytestdoes not collect this file. Run it withpython -m unittest tests.zero_bounce_integration_testwhenZEROBOUNCE_API_KEYis set.pyproject.toml: the only version field,version = "x.y.z".setup.pycallssetup()and reads that file.
pip install -e ".[test]"
pip install ruff==0.17.2 # the version CI pins
ruff check src tests # undefined names and syntax errors only
python -m compileall -q src tests
python -m pytest # every offline suite under tests/ruff check plus compileall is the lint step. There is no typechecker. The Ruff selection in pyproject.toml is E9, F63, F7, and F82. Do not widen it to a style ruleset that fails the existing tree.
CI (.github/workflows/sdk_ci.yml) runs ruff check, compileall, then python -m pytest with coverage.
-
Only endpoints documented at https://www.zerobounce.net/docs/. Do not add a method for an endpoint that the public docs do not describe, even if it exists on the server. Integration pages count: the S3 bulk API is documented at https://www.zerobounce.net/docs/api-integrations/amazon-s3-bulk-api.
-
One
ZeroBounceper API key. The class is not a singleton. Do not reuse one instance across users or requests. The offline concurrency test constructs a separate client per key. -
Column indexes are 1-based, and the SDK does not add 1. Pass the API's index.
0is a real value and is sent.Noneon an optional column (such asfirst_name_column) omits the field.send_file's email column is required and is sent as given, including0.domain_columnand the list-evaluator email column are required:Noneis rejected,0is sent. The bulk email finder also needsfirst_name_columnorfull_name_column. -
Optional parameters are omitted when unset.
timeout,activity_data,verify_plus,domain_info,greylist_processing,callback_url, andallow_phase_2are not sent until the caller sets them. ExplicitFalseis sent asfalse.ip_address=Noneis omitted;""is sent. -
Booleans on form and multipart bodies must be the strings
trueandfalse. A Pythonboolpassed torequestsbecomesTrueorFalse, and the API will not treat that as a boolean._form_dataconverts them. JSON bodies (validate_batch, the Inbox Placement Tester, the Warmup API) keep JSON booleans. -
allow_phase_2is validationsend_fileonly. Scoringsend_fileand the S3 / SFTP jobs do not send it; those jobs send onlyfile_name,has_header_row,remove_duplicates, andreturn_url, and requirefile_name,has_header_rowandreturn_urlbecause the live API rejects jobs without them.activity_dataon get-file is validationget_fileonly. -
validate_batchusesapi.zerobounce.net, notbulkapi.zerobounce.net. It followsbase_url. -
Failures raise.
ZBClientExceptionmeans the arguments were rejected and no request was sent.ZBApiExceptionmeans the API failed. The message must be short: no raw HTML, no megabyte body, noJSONDecodeError/Expecting valuetext, and noError: Error:prefix. An account-levelvalidate_batcherror (email_addressall) raises. A per-address error stays on the result'serrorslist.get_creditswith an invalid key returnsCredits-1; that endpoint does not send anerrorfield. -
Downloads are buffered unless
stream=True. The streamed path sniffs the first 64 KB for an error body, writes to a.parttemporary file next todownload_path, and moves it into place only when complete. Keep both paths' error checks identical (_raise_if_download_error). -
A file download is not JSON.
get_filesaves a non-empty CSV body, includingtext/csvandtext/plain. An empty body, an HTML page (status 200 or 403), and a JSON error areZBApiException. Do not treat everytext/plainbody as an error. -
Where the key travels is fixed by each API's docs. The validation API:
api_keyin the query (GET) or body (POST, uploads). The Inbox Placement Tester:Authorization: Bearer(_request_json(..., bearer=True)). The Warmup API:API-Keyheader, inZeroBounceWarmuponly, never mixed withZeroBounce. -
Every request URL must be https.
_checked_url(main client) refuses anything else before sending, including the reassignable*_BASE_URLattributes; it also warns once when an EU or USbase_urlmeetsbulkapi.zerobounce.net, because no regional bulk host is documented. -
Keys never reach an exception.
_check_api_keyrejects surrounding whitespace and control characters (the HTTP library would quote such a header value unredacted). ARequestExceptionis redacted with_redactand raised after theexceptblock, so the original is not its__context__; keep that pattern for any new request path. Caller IDs placed in a URL path go through_path_segment. -
Do not follow redirects. Every
requestscall passesallow_redirects=False. A 3xx response is aZBApiException. A redirect must not be fetched, because theLocationcould be another host and the next request can carry the API key. -
Do not commit
.env. It is gitignored and may hold a real key. Tests must not call the live API. -
Match the surrounding style. Do not reformat unrelated code.
-
The live API wins over the docs. Where they disagree, follow what the API accepts and say so in the docstring. Known cases:
validate_batchtimeoutmust be a JSON string;send_evaluated_filetakes the evaluator'sfile_id, not an upload; S3 / SFTP jobs needreturn_url(andvalidateSFTPfileneedsfile_name); Warmupcheck-domain-dmarcneeds an email address, not a domain. Add every new method totests/live_api_test.py(free calls by default, behindZEROBOUNCE_LIVE_SPEND/ZEROBOUNCE_LIVE_CHANGESwhen it spends credits or changes the account, never anything the API cannot undo) and run it before a release.
CHANGELOG.md follows Keep a Changelog 1.1.0, and the project adheres to Semantic Versioning 2.0.0.
- Add every user-visible change to
## [Unreleased]in the same commit as the change. - Group entries under these headings, in this order, omitting empty ones:
Added,Changed,Deprecated,Removed,Fixed,Security. - Write for SDK users: name the method, option, or behaviour, and say what changed. Leave out CI-only changes, internal refactors, and fixes to code that was never released.
- At 2.y.z, a breaking change bumps the major version, a new method or option bumps the minor version, and fixes alone bump the patch version.
Publishing and pushing tags need a maintainer's explicit approval. Do not bump the version, tag, or publish unless they asked.
Every release needs a manual live API test run first. The offline suites cannot catch what only the live API rejects (a timeout type, a required field the docs call optional). The run needs real keys, which only a maintainer has, so it is a manual step:
- Tell the maintainer as early as possible, at the start of any release work and as soon as a change adds or alters an API call, that a live run is required, which keys it needs (
ZEROBOUNCE_API_KEY, plusZEROBOUNCE_WARMUP_API_KEYwhen Warmup changed), and which flags (ZEROBOUNCE_LIVE_SPEND=1when bulk or credit-spending methods changed,ZEROBOUNCE_LIVE_CHANGES=1when filters or Warmup sending accounts changed). Do not leave it for the moment before tagging. - Do not tag or publish until the run has passed with every flag the change calls for. Record the result (date, flags, pass or failures) in the release pull request or the release notes.
- Never write a key into a file, a commit, a log, or a message. Pass keys only as environment variables for the run.
- Move
[Unreleased]entries under## [x.y.z] - YYYY-MM-DD, leave an empty[Unreleased]section, and update the compare links at the bottom ofCHANGELOG.md. - Set the same version in
pyproject.toml(version = "x.y.z"). The publish workflow fails if it differs from the tag. That is the only version field. There is no runtime__version__and no user-agent constant.setup.pydoes not contain a version. ruff check src tests,python -m compileall -q src tests, andpython -m pytest; then the manual live run (see above):python -m unittest tests.live_api_test -vwith the keys and flags the release needs. A failure blocks the release.- Tag
vx.y.zon that commit and run the Publish workflow (.github/workflows/publish.yml) with that tag. It publisheszerobouncesdkto PyPI through PyPI Trusted Publishing: the PyPI project trusts this repository,publish.yml, environmentrelease. There is no PyPI token, and none should be added. Keep the workflow file name and environment name, or update the trusted publisher on PyPI first.