lib/zerobounce.rb: every public method, as module functions onZerobounce. The Priority 3 endpoints live inlib/zerobounce/priority3.rb.lib/zerobounce/base_request.rb: the HTTP layer._get/_postare the originals;_executeis the one that takes an explicit method and either a form or a JSON body, which the bulk jobs and the Inbox Placement Tester's PUT and DELETE need.root_requestandipt_requestare public and shared by bothRequestandMockRequest.lib/zerobounce/configuration.rb: four base URLs, see below.lib/zerobounce/warmup.rb: a separate client for the Warmup API, a different product with its own host and its own team-scoped key. It isZerobounce::Warmup, not part of theZerobouncemodule functions.spec/offline_api_spec.rb,spec/warmup_spec.rb: the offline suites, stubbed with WebMock.live/live_api.rb: the opt-in live suite. It sits outsidespec/, sorspecand CI never load it.
The base branch is develop, not main/master.
Ruby runs in Docker (the host gems are not installed):
cd ../sdk-docs && docker compose run --rm --build rubyTo run against the working tree without rebuilding:
docker run --rm -u 0 -v "$PWD":/src -w /src zerobounce-sdk-ruby:test \
sh -c "bundle _2.4.22_ config set --local path /app/vendor/bundle && bundle _2.4.22_ exec rspec"The live suite is run by hand, never by rspec:
export ZEROBOUNCE_API_KEY=... ZEROBOUNCE_WARMUP_API_KEY=...
ruby -Ilib live/live_api.rb # free calls only
ZEROBOUNCE_LIVE_SPEND=1 ruby -Ilib live/live_api.rb
ZEROBOUNCE_LIVE_CHANGES=1 ruby -Ilib live/live_api.rbKeys come from the environment only. Never write one into a file, a commit, a log, a fixture or a message.
CI runs bundle exec rspec only. rake default also runs RuboCop, which reports a large number of pre-existing offences across the repo and is not a gate — do not treat its output as a regression from your change.
- Only endpoints documented at https://www.zerobounce.net/docs/. Do not add a method for an endpoint the public docs do not describe, even if it exists on the server. Check before concluding something is undocumented: the S3 and SFTP bulk endpoints live under https://www.zerobounce.net/docs/api-integrations, not the main API reference.
- Four base URLs.
api_root_url,bulk_api_root_url(ends in/v2/),bulk_root_url(the S3/SFTP jobs, no/v2), andlist_evaluator_root_url/inbox_placement_root_url.list_evaluator_root_urlandinbox_placement_root_urlare separate values rather than paths built frombulk_api_root_url, so overriding that alone leaves a test pointing at the live API.bulk_root_urlis the exception: it defaults tonilandBaseRequest.bulk_api_root_without_v2then derives it frombulk_api_root_urlwith the/v2stripped. Set it (orZEROBOUNCE_BULK_ROOT_URL) to move only the S3/SFTP jobs. - Where the live API and the docs disagree, follow the API and say so in a comment at the call site. The ones found so far:
validatebatchtakestimeoutas a JSON string; the Inbox Placement Tester authenticates withAuthorization: Bearer, notapi_key; its listing isGET /tests, not/test, and that listing's page size parameter ispage_size, not the camelCasepageSizethe rest of that API uses (pageSizeis accepted and ignored);send-evaluated-filetakesfile_idas a form field, not a file upload; the S3 and SFTP jobs requirereturn_urlandfile_name,validate_sftp_fileincluded;/filters/*answers a success with a bare{"Message": ...}body; the Warmupcheck-domain-dmarctakes an email address, not a domain; and a failed SMTP login intest-smtp-configurationcomes back as an HTTP 500. export_sftp_filesendsexport_path, not theexport_bucketthatexport_s3_filesends. Both are optional; omitted, the export goes to the account's configured destination.- The eight S3/SFTP file jobs go through
Priority3#bulk_root_file_job. It is the one place that enforcesfile_nameandreturn_urland turns booleans into the stringstrue/false.validate_s3_fileandscore_s3_fileuse it too, so do not rebuild the parameter hash per endpoint. listS3filesis on the bulkapi root, not/v2. It is the only S3 call that is a GET, which is how it ended up onbulk_get(and a 404) once before.- A JSON content type is matched by media type, not
==. The API types some responsesapplication/json; charset=utf-8;BaseRequest.__json_content_type__is the only thing that should decide whether a body is parsed. {"success": false}on an HTTP 200 is a failure. The API answers an unknown bulk file id that way; bothbulk_getand__parse_json_response__raise on it.- A body that is nothing but
Messagemeans different things per endpoint. On the validate endpoints it is a rejection; on/filters/add,/filters/deleteand/filters/listit is the success.Request::FILTER_PATHSis the exception list. Gemfile.lockpins the gem's own version. A version bump must update bothlib/zerobounce/version.rbandGemfile.lock, orbundle install --deploymentin CI fails.- Offline tests only, in
spec/. Never call the live API from a spec. The live suite is the separate, opt-inlive/live_api.rb. - Errors carry the HTTP status. The Warmup client raises
Zerobounce::ApiResponseError, whose#status_codelets callers tell a Cloudflare 403 from an API 400/404 and a Warmup 429. The validation client still raises aRuntimeErrorwith the same short message.
Run the live suite by hand before every release. The offline suite proves the SDK sends what we think it sends; only the live suite proves the API accepts it. Run it with real keys, including ZEROBOUNCE_LIVE_SPEND=1 and ZEROBOUNCE_LIVE_CHANGES=1, and do not release on a failure.