Skip to content

feat: v2 validate parity (verify_plus, activity_data, batch timeout) - #38

Merged
franciscbalint merged 14 commits into
mainfrom
feature/api-parity-p1-validate
Oct 24, 2026
Merged

franciscbalint merged 14 commits into
mainfrom
feature/api-parity-p1-validate

Conversation

@franciscbalint

@franciscbalint franciscbalint commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Adds optional activityData / verifyPlus on validateEmail (GET /v2/validate) and validateBatch (POST /v2/validatebatch).
  • Adds batch timeout (10–120s) on validateBatch.
  • Documents active_in_days and active_first_seen on validation result types.

Priority 2 — real-time AI scoring

Priority 3 — bulk finder, list evaluator, filters, S3

  • Bulk email finder: sendEmailFinderFile / sendEmailFinderFileStream, getEmailFinderFileStatus, getEmailFinderFile, deleteEmailFinderFile → bulkapi.zerobounce.net/email-finder/*
  • Bulk domain search: sendDomainSearchFile / sendDomainSearchFileStream, status/get/delete → bulkapi…/domain-search/*
  • List evaluator: sendListEvaluatorFile / sendListEvaluatorFileStream, getListEvaluatorResult, sendEvaluatedFile → bulkapi…/v2/listevaluator/ (+ /v2/send-evaluated-file/)
  • Custom filters: addFilter, listFilters (GET or method: "POST"), deleteFilter → /v2/filters/* on main API
  • S3 helpers: validateS3file, scoreS3file, exportS3file on bulkapi root (per Postman; remove_duplicates / optional column fields documented in types)

Gaps / notes

  • Domain search bulk docs page is sparse; paths match Postman + Spanish docs mirror.
  • S3 listS3files not added (out of P3 scope); validate/score field names follow Postman (remove_duplicates).

Docs

Test plan

  • npm run test:unit (94 tests) — includes Priority 3 URL/body mocks
  • Docker sdk-docs compose javascript image

Deferred

  • POST /v2/validate method parity

Made with Cursor


Validate options, Inbox Placement Tester, list evaluator base URL

Ported from the Python SDK (407f0a8, a6710e5).

Added

  • validateEmail options domainInfo, greylistProcessing, callbackUrl. Booleans go on the wire as "true"/"false" on both the GET and the form POST. callbackUrl is rejected without greylistProcessing: true, and a blank one is rejected, before any request is sent.
  • Validate response fields domain_website_exists, domain_registrant_company_name, and message (read from the API's Message key, returned while a greylist retry is still running).
  • Inbox Placement Tester on https://bulkapi.zerobounce.net/api/ipt: getInboxPlacementProviders, getInboxPlacementTestTypes, createInboxPlacementTest, getInboxPlacementTest, listInboxPlacementTests, deleteInboxPlacementTest, getInboxPlacementWebhook, setInboxPlacementWebhook. API key on the query string, camelCase JSON bodies, unset fields omitted.
  • init takes a third argument to override the list evaluator and Inbox Placement Tester base URLs.

Changed

  • List evaluator uses its own base URL (.../v2/listevaluator) instead of building the path from the bulk base; the file id is URL-encoded as one path segment. sendEvaluatedFile stays on the bulk base.

Fixed

  • An error body with a detail string (Django REST framework, used by the Inbox Placement Tester) now rejects with that text.

S3 — removed, then restored

  • An earlier push removed listS3files, validateS3file, scoreS3file, exportS3file. That was wrong: they are documented, at https://www.zerobounce.net/docs/api-integrations/amazon-s3-bulk-api. All of them, their helper, types and tests have been restored, and only the parity work above remains.
  • AGENTS.md keeps the "only documented endpoints" rule and now points at where the S3 docs live.
  • Not added: emailFinderS3file and domainSearchS3file, and the SFTP variants on the Integrations index, are documented but have never been part of this SDK.

Tests: 125 pass, plus dts-check and the webpack build. Offline only — no live API calls.


RD-66: ai_agent_mailbox sub_status

Folded in from the former feature/ai-agent-mailbox-substatus branch, which has been deleted now that the commit lives here.

Adds the ai_agent_mailbox sub_status, which the API returns alongside both valid and catch-all. Tests cover both pairings, plus an unrecognised sub_status to pin that a value added to the API after this SDK ships degrades gracefully instead of failing the whole validate call.

No sub_status_ai_agent_mailbox usage counter was added: it is not documented.

franciscbalint and others added 12 commits October 18, 2026 00:50
Expose optional verify_plus, activity_data, and batch timeout per v2 docs; extend validation result types with activity fields.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Add bulk email finder and domain search file jobs, list evaluator,
allow/block filters, and S3 validate/score/export helpers aligned with
bulkapi and v2 docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add form-encoded POST option on single validate (alongside GET) and GET
listS3files on bulkapi root, with unit tests and API parity doc updates.
HTML, empty, and account-level failures now reject with a short message, required column 0 and an explicit false header row are sent, and the default test run checks every public method without the network.

Co-authored-by: Cursor <cursoragent@cursor.com>
…st evaluator base URL

Ports the Python SDK's parity changes (407f0a8, a6710e5) to this SDK.

- validateEmail takes domainInfo, greylistProcessing and callbackUrl, sent as
  the strings "true"/"false" on both the GET and the form POST. callbackUrl
  without greylistProcessing: true, and a blank callbackUrl, are rejected
  before any request goes out.
- The validate response type carries domain_website_exists,
  domain_registrant_company_name and message, the last read from the API's
  "Message" key while a greylist retry is still running.
- Inbox Placement Tester on bulkapi.zerobounce.net/api/ipt: providers, test
  types, create/get/list/delete tests, and get/set webhook. The API key goes on
  the query string, bodies are camelCase JSON, and only fields the caller set
  are sent. createRequest gained a jsonBody option for this.
- List evaluator requests use their own base URL rather than building the path
  from the bulk base, and the file id is URL-encoded as one path segment.
  sendEvaluatedFile stays on the bulk base. init's third argument overrides
  both this and the Inbox Placement Tester base.
- An error body carrying a "detail" string (Django REST framework, used by the
  Inbox Placement Tester) rejects with that text.

Removes listS3files, validateS3file, scoreS3file and exportS3file along with
their helper, types, tests and changelog entry: https://www.zerobounce.net/docs/
does not document those endpoints, and AGENTS.md now records that the SDK
covers only documented ones.

Tests: 122 pass, plus dts-check and the webpack build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
They are documented, at
https://www.zerobounce.net/docs/api-integrations/amazon-s3-bulk-api, which
lists listS3files, validateS3file, scoreS3file, exportS3file,
emailFinderS3file and domainSearchS3file on bulkapi.zerobounce.net. The
previous commit removed them on the incorrect basis that the public docs do
not cover them; only the main API reference was considered.

Restores listS3files, validateS3file, scoreS3file and exportS3file, the
_sendBulkRootFormPost helper, the TypeScript types and declarations, the
offline and legacy tests, and the changelog entry. The parity work in the
previous commit is untouched.

AGENTS.md keeps the rule that the SDK covers only documented endpoints and
now points at where the S3 documentation lives, so the next reader does not
repeat the same assumption.

Not added here: emailFinderS3file and domainSearchS3file, and the SFTP
variants listed on the Integrations index, are documented but have never been
part of this SDK.

Tests: 125 pass, plus dts-check and the webpack build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ports b0444f8 from the Python SDK. These are documented but had never been in
this SDK:

- POST /emailFinderS3file and /domainSearchS3file, from
  https://www.zerobounce.net/docs/api-integrations/amazon-s3-bulk-api
- POST /validateSFTPfile, /scoreSFTPfile, /emailFinderSFTPfile,
  /domainSearchSFTPfile and /exportSFTPfile, from
  https://www.zerobounce.net/docs/api-integrations/sftp_bulk_api

All six file jobs share _bulkRootFileJob, which sends has_header_row and
remove_duplicates as the strings true/false and leaves remove_duplicates and
return_url out until set.

Two shapes differ from the S3 ones and are easy to get wrong:
- validateSFTPfile's file_name is optional; omitting it uses the input bucket
  from the account's settings. Every other job requires it.
- exportSFTPfile takes export_path, not export_bucket, and omitting it uses
  the export path from those settings.

Tests: 126 pass, plus dts-check and the webpack build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The API now returns ai_agent_mailbox under both the valid and catch-all
statuses. No new status was added.

Adds AI_AGENT_MAILBOX to the ZBValidateSubStatus helper in src/zero-bounce.js
and to its type in zeroBounceSDK.d.ts, following ROLE_BASED_CATCH_ALL.

sub_status is forwarded as a raw string rather than mapped onto the helper,
so an unknown value was already safe here. A test now pins that, alongside
valid + ai_agent_mailbox and catch-all + ai_agent_mailbox.

No sub_status_ai_agent_mailbox usage counter: it is not documented.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Verified against the live API on 2026-10-24 and ported from the Python SDK.

- validateBatch sends `timeout` as a JSON string ("30"). As a number the API
  rejects the whole batch with the account-level "Invalid API Key or your
  account ran out of credits" error. activity_data and verify_plus stay JSON
  booleans.
- sendEvaluatedFile takes the list evaluator's file_id as a form field, not a
  file upload as the docs describe: an upload is rejected with
  "file_id: This field is required". The signature is now
  sendEvaluatedFile(fileId); it returns a new validation job's file_id, which
  answers "File cannot be found" for a short while after it is accepted.
- The Inbox Placement Tester authenticates with the documented
  "Authorization: Bearer <key>" header instead of an api_key query parameter.

Alongside those:

- ZBApiError carries statusCode, so callers can tell a 403 (Cloudflare) from a
  400/404 (API) or a 429 (Warmup).
- Error bodies also read error_message, a "detail" validation list
  ([{loc, msg}] becomes "field: msg"), and a top-level JSON list of strings
  (["In-progress tests cannot be deleted."]).
- {"success": false} on an HTTP 200 now raises instead of being returned. Two
  existing tests encoded the old behaviour and were updated.
- A 3xx is raised rather than followed.
- Keys are rejected when they carry surrounding whitespace or control
  characters, which fetch would otherwise quote unredacted in its own error.
- Network errors are redacted for every form of the key and raised on their
  own, so the original (which can hold the key in its URL) is never the cause.
- Request URLs must be https, including every overridable base URL.
- A once-per-client warning when a regional (EU/US) base URL is set but a
  request goes to bulkapi.zerobounce.net: there is no regional bulk host.
- The email finder and domain search families get their own overridable base
  URLs, like the list evaluator and the Tester already had.

The offline fake now records the Authorization header, so it can answer
requests whose key travels there.

Tests: 129 pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A separate product from email validation, so a separate client rather than
methods on ZeroBounceSDK: its own team-scoped key, its own host
(https://warmup-api.zerobounce.net, overridable), an API-Key header, and JSON
bodies. One method per documented endpoint, 26 in all:

- sending accounts: list, get, add (self_managed needs engagement_rule_id 0-4;
  zb_managed takes email_content and managed_sending), update, delete,
  changeFromAddress (PUT /smtp-credentials, which stores no credentials), and
  listFromAddresses;
- engagement: rules catalogue, set a standard rule, and get / create / update /
  delete a custom one;
- statistics, seeds and utilities: seeds, seed count, today's progress and
  sends, emails per day, four statistics endpoints, DMARC check and the SMTP
  configuration test.

The documented ranges are checked before sending: engagement_rule_id 0-4,
status_id 0-8, reply_pct 5-45, important_pct 5-40, click_pct 0-70 and at most
important_pct x 1.75, timezone_offset -11 to 14, port 1-65535, search_from's
character set, and start_date <= end_date.

LIVE, verified 2026-10-24:
- checkDomainDmarc takes an EMAIL ADDRESS. A bare domain is rejected with
  "Please provide a valid email address", although the docs describe a domain,
  so a value without "@" is refused before sending.
- testSmtpConfiguration answers any failed SMTP login with HTTP 500. That is an
  API bug; it is documented on the method.

204 and empty 200 return null. The two downloads return CSV text. Errors carry
the HTTP status, read message / error / error_message / detail (including a
[{loc, msg}] validation list as "field: msg") and a top-level list of strings,
and mask the API key and the SMTP username and password — in network failures
too, which are raised on their own so the original is never the cause.

The webpack bundle ships only the default export, so Warmup is also hung off
ZeroBounceSDK.Warmup to stay reachable from the UMD build.

Tests: 143 pass (14 new), plus the .d.ts and TypeScript consumer checks.

Note: `npm run dts-check` fails on this machine for a reason unrelated to this
change — TypeScript 7.0.2's @typescript/typescript-darwin-arm64 binary is not
installed. Installing that package makes it pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Verified against the live API on 2026-10-24.

- The six S3 / SFTP file jobs require return_url, which the docs mark optional:
  without it the API answers "Provided return URL is not permitted: None".
  file_name is required for every job including validateSFTPfile, which the
  docs also mark optional ("file_name: This field is required"). Both are
  checked before sending, so no request goes out that the API would reject.
  remove_duplicates stays the only optional field of the four.
- Filter rule and target are closed sets, checked before sending: rule is
  allow or block, target is email, domain, mx or tld. The previous fixtures
  used "exclude"/"include", which are not documented values.

Tests: 143 pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread src/utils.js Fixed
Comment thread src/warmup.js Fixed
franciscbalint and others added 2 commits October 24, 2026 03:56
tests/live-api.mjs calls every method of both clients against the real API.
Jest does not match .mjs, so `npm test` and CI never run it; `npm run test:live`
does. It skips without ZEROBOUNCE_API_KEY / ZEROBOUNCE_WARMUP_API_KEY, runs
free calls by default and checks the credit balance did not change, and gates
the rest behind ZEROBOUNCE_LIVE_SPEND=1 (credit-spending calls and a bulk round
trip, deleted again) and ZEROBOUNCE_LIVE_CHANGES=1 (a test filter and a
self-managed Warmup sending account, both undone in cleanup even on failure).

It never creates an Inbox Placement test, sets the Inbox Placement webhook, or
starts ZB-managed Warmup onboarding: the API cannot undo those. No key is
written to a file or printed; keys come from the environment and are redacted
out of every message the suite prints.

getApiUsage now formats a Date argument as the documented YYYY-MM-DD. Passed
through unformatted a Date stringifies to "Fri Oct 24 2026 01:00:00 GMT+0400
(Gulf Standard Time)"; in a query string Cloudflare answered that with an HTML
"Access denied" page instead of the API answering at all. Strings are untouched.

Live run on 2026-10-24 with both flags: 39 passed, 0 failed, 4 skipped (the
Warmup statistics endpoints need a registered sending account, and this team
has none). It confirmed the four behaviours this branch had taken from the
Python SDK's notes rather than from the docs:

- validateBatch is accepted with `timeout` as a JSON string;
- a complete S3 job payload is accepted, and one without return_url is
  refused before it is sent;
- the Inbox Placement Tester accepts the Authorization: Bearer key;
- check-domain-dmarc rejects a bare domain and accepts an email address.

Two further live findings, both now reflected in the suite:

- activity_data, verify_plus and domain_info are billable add-ons and are
  charged even for a sandbox address, so the option-heavy validate call sits in
  the SPEND tier rather than the free one.
- A just-accepted bulk job answers "File cannot be found" for a short while, so
  the round trip retries the delete instead of leaving the job behind.

Offline tests: 143 pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two CodeQL findings on this PR.

warnOnRegionalBulkHost matched "bulkapi.zerobounce.net" as a substring of the
whole URL, which also accepts https://evil.example/bulkapi.zerobounce.net and
https://bulkapi.zerobounce.net.evil.example. The host is now parsed out and
compared on its own, the same way the PHP, C and Pascal SDKs already do it.
The regional base URL is matched the same way rather than by substring.

The Warmup client trimmed trailing slashes with /\/+$/, which backtracks
quadratically on a string of many slashes when the base URL comes from the
caller. Replaced with a linear scan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@franciscbalint
franciscbalint merged commit ad4482d into main Oct 24, 2026
6 checks passed
@franciscbalint
franciscbalint deleted the feature/api-parity-p1-validate branch October 24, 2026 19:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants