Repository navigation
feat: validate options, AI scoring, filters, list evaluator, Inbox Placement Tester and the S3/SFTP bulk jobs - #81
Open
franciscbalint wants to merge 7 commits into
Open
franciscbalint wants to merge 7 commits into
franciscbalint wants to merge 7 commits into
Conversation
… bulk jobs Ports 407f0a8 and b0444f8 from the Python SDK. Everything below is documented at https://www.zerobounce.net/docs/; nothing undocumented was added. - A validate overload taking ZBValidateOptions: timeout, activity_data, verify_plus, domain_info, greylist_processing, callback_url, and usePost for the form-encoded POST /v2/validate. An unset option is not sent; booleans go on the wire as the strings true/false. callback_url is where the greylist retry posts its result, so it is rejected without greylist_processing, and when blank, before any request is sent. timeout must be 3-60 seconds. - ZBValidateResponse gains activeInDays, activeFirstSeen, domainWebsiteExists, domainRegistrantCompanyName and message. message is the API's "Message" key, returned instead of a result while a greylist retry is still running; it is part of equals/hashCode/toString like the rest. - Real-time AI scoring: scoreEmail (GET /v2/scoring) and its POST variant. - Account filters: addFilter, listFilters, deleteFilter. - getListEvaluatorResult, on the list evaluator's own base URL rather than a path built from the bulk one, with the file id URL-encoded as one segment. - The S3 bulk jobs (listS3Files, validateS3File, scoreS3File, exportS3File, emailFinderS3File, domainSearchS3File) and the SFTP/FTP ones (validateSFTPFile, scoreSFTPFile, emailFinderSFTPFile, domainSearchSFTPFile, exportSFTPFile). These are on the bulkapi host WITHOUT /v2, so they use a separate bulkApiRootUrl. - The Inbox Placement Tester: the API key goes on the query string, bodies are camelCase JSON, and unset fields are left out. Two bulk shapes differ from the rest and are easy to get wrong: - validateSFTPFile's file_name is optional; leaving it unset uses the input bucket from the account's settings. Every other job requires it. - exportSFTPFile sends export_path, not the export_bucket exportS3File sends. sendRequest could only do a GET or a JSON POST, so sendRequestWithMethod is new: it takes an explicit method and either form fields or a JSON body, which the bulk jobs, the filters and the Tester's PUT and DELETE all need. initialize gains a four-argument overload for the list evaluator and Tester base URLs. They are separate hosts, not paths built from the bulk base, so overriding apiBaseUrl alone would leave a test pointing at the live API. ErrorResponse.parseError already collects every value of an error body, so the Tester's Django-style {"detail": ...} surfaces without a special case; a test now pins that. AGENTS.md is new, and records the four base URLs, the two bulk shapes above, and how to run the tests (there is no mvn on the host and mvnw has no wrapper jar committed, so Maven runs in Docker). Tests: 58 pass, up from 46. Offline only — a mocked HttpURLConnection answers every call and records the url, method and body. 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 ZBValidateSubStatus with its @SerializedName, following ROLE_BASED_CATCH_ALL. Unknown values were already safe: Gson maps an unrecognised enum constant to null rather than throwing, so a sub_status added after this SDK shipped degrades to null instead of failing the validate call. That had no coverage, so a test now pins it alongside the two new ones. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Verified against the live API; where the API and the docs disagree, the
API wins and the call site says so.
- validateBatch gains a ZBValidateOptions overload, and sends timeout as
a JSON string. Sent as a number the API rejects the whole batch with
the account-level "Invalid API Key or your account ran out of
credits". Until now the batch had no options at all.
- The Inbox Placement Tester authenticates with an Authorization: Bearer
header, not an api_key parameter, so the key no longer reaches a query
string that gets logged. Its listing is GET /tests; /test is the
single-test path and answers Method "GET" not allowed.
- sendEvaluatedFile posts the list evaluator's file_id as a form field.
The endpoint was missing from this SDK.
- The S3 and SFTP jobs require file_name and return_url, validateSFTPFile
included, although the docs list file_name as optional for that one.
{"success": false} on an HTTP 200 is a failure as well: the API answers
an unknown file id that way. ErrorResponse now carries the HTTP status
through getStatusCode(), on every path including the multipart uploads
and the bulk download.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Warmup API is a separate product: its own host, its own team-scoped key, and an API-Key header rather than an api_key parameter. ZeroBounceSDK stays the validation client; ZeroBounceWarmup covers all 26 documented endpoints, including the two CSV downloads. Two live findings are recorded at the call site: check-domain-dmarc takes an email address, not the domain the docs describe, and a failed SMTP login comes back as an HTTP 500. The key is masked out of every message, and the SMTP username and password out of errors from that method. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
live/LiveApi.java calls every SDK method against the real API. It lives outside the Maven module's source root, so `mvn test` and CI never compile or run it; it is compiled and run by hand with keys in the environment. Free calls run by default: sandbox addresses, reads, and requests expected to fail that still prove the API accepts the payload. The free tier asserts the credit balance did not move. ZEROBOUNCE_LIVE_SPEND=1 adds the billable calls and a bulk round trip that deletes its own job; ZEROBOUNCE_LIVE_CHANGES=1 adds account changes, each undone in cleanup even when the test fails. Nothing the API cannot undo is ever called. Two things only the live run could catch: - listFilters threw. GET /filters/list answers with a JSON array, not an object, so it now hands back a JsonElement. - The Warmup client threw on /seed-count, which answers with a bare number. Any payload that is not an object now comes back wrapped under "data", and a body that is not JSON at all is rejected before Gson's lenient parser can read it as a string. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
README gains a Warmup API section and the opt-in live suite with its two flags, and its S3/SFTP examples now pass the file_name and return_url the API requires. AGENTS.md records where the live API and the docs disagree, the rule that the API wins and the call site says so, and the release rule that the live suite is run by hand before every release. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found by auditing every SDK's api-usage model against the keys the live API actually returns, after the same class of bug turned up in Rust and iOS. sub_status_leading_period_removed and sub_status_alias_address are returned by getapiusage and were not read at all, so both were silently discarded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ports
407f0a8andb0444f8from the Python SDK. Everything below is documented at https://www.zerobounce.net/docs/. Nothing undocumented was added.Validate options
validategains an overload takingZBValidateOptions:timeout,activity_data,verify_plus,domain_info,greylist_processing,callback_url, andusePostfor the form-encodedPOST /v2/validate. An unset option is not sent, and booleans go on the wire as the strings"true"/"false".callback_urlis where the greylist retry posts its result, so it is rejected withoutgreylist_processing, and when blank — before any request is sent.timeoutmust be 3–60 seconds.ZBValidateResponsegainsactiveInDays,activeFirstSeen,domainWebsiteExists,domainRegistrantCompanyNameandmessage.messageis the API'sMessagekey, returned instead of a result while a greylist retry is still running; it is part ofequals/hashCode/toStringlike the rest.Real-time AI scoring, filters, list evaluator
scoreEmail(GET /v2/scoring) and its POST variant.addFilter,listFilters,deleteFilter.getListEvaluatorResult, on the list evaluator's own base URL rather than a path built from the bulk one, with the file id URL-encoded as a single path segment.S3 and SFTP/FTP bulk
listS3Files,validateS3File,scoreS3File,exportS3File,emailFinderS3File,domainSearchS3File— https://www.zerobounce.net/docs/api-integrations/amazon-s3-bulk-apivalidateSFTPFile,scoreSFTPFile,emailFinderSFTPFile,domainSearchSFTPFile,exportSFTPFile— https://www.zerobounce.net/docs/api-integrations/sftp_bulk_apiThese are on the bulkapi host without
/v2, so they use a separatebulkApiRootUrl. All six file jobs shareZBBulkFileJobOptionsand onebulkRootFileJobhelper.Two shapes differ from the rest and are easy to get wrong:
validateSFTPFile'sfile_nameis optional — leaving it unset uses the input bucket from the account's settings. Every other job requires it.exportSFTPFilesendsexport_path, not theexport_bucketthatexportS3Filesends.The SFTP server, path and protocol are never sent by the SDK; they come from the account's ZeroBounce settings.
Inbox Placement Tester
The API key goes on the query string, bodies are camelCase JSON, and unset fields are left out. Spends the account's inbox-test credits.
Notes
sendRequestcould only do a GET or a JSON POST.sendRequestWithMethodis new: an explicit method plus either form fields or a JSON body, which the bulk jobs, the filters and the Tester's PUT and DELETE all need.initializegains a four-argument overload for the list evaluator and Tester base URLs. They are separate hosts, not paths built from the bulk base, so overridingapiBaseUrlalone would leave a test pointing at the live API — which is how the equivalent Rust test first failed.ErrorResponse.parseErroralready collects every value of an error body, so the Tester's Django-style{"detail": ...}surfaces without a special case. A test now pins that.AGENTS.mdis new, and records the four base URLs, the two bulk shapes above, and how to run the tests — there is nomvnon the host andmvnwhas no wrapper jar committed, so Maven runs in Docker.Test plan
mvn -B testin the Java 21 + Maven image — 58 pass, up from 46 (12 new inOfflineApiTest)HttpURLConnectionanswers every call and records the url, method and body, so the assertions are on what actually went on the wire🤖 Generated with Claude Code
RD-66:
ai_agent_mailboxsub_statusFolded in from the former
feature/ai-agent-mailbox-substatusbranch, which has been deleted now that the commit lives here.Adds the
ai_agent_mailboxsub_status, which the API returns alongside bothvalidandcatch-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_mailboxusage counter was added: it is not documented.Python-SDK parity, verified against the live API
Brings this SDK level with the Python SDK's
master, then checks every call against the real API. Where the live API and the docs disagree, the API wins and the call site says so.What the live API taught us. Each of these was a real defect, found by calling the endpoint:
validatebatchtakestimeoutas a JSON string. Sent as a number, the API rejects the whole batch with the account-level "Invalid API Key or your account ran out of credits" — which looks like a billing problem, not a payload one.validateBatchhad no options overload at all until now, so the batch could not sendtimeout,activity_dataorverify_plus.Authorization: Bearer, not anapi_keyparameter — the key no longer reaches a query string that gets logged — and its listing isGET /tests.GET /testanswersMethod "GET" not allowed, solistInboxPlacementTestshad never worked.send-evaluated-filetakes the list evaluator'sfile_idas a form field, not the file upload the docs describe. The endpoint was missing from this SDK;sendEvaluatedFile(String fileId, …)adds it.file_nameandreturn_url—validateSFTPFileincluded, although the docs listfile_nameas optional for that one.Three bugs the live run exposed in this SDK.
listFiltersthrew.GET /filters/listanswers with a JSON array, and the call deserialized into aJsonObject:Expected a JsonObject but was JsonArray. It now hands back aJsonElement./seed-count, which answers with a bare number. Any payload that is not an object now comes back wrapped underdata. While fixing it: Gson parses leniently, so a junk body was being read as a string rather than raising — the shape is now checked before parsing.{"success": false}on an HTTP 200 was returned as data. The API answers an unknown bulk file id that way. It now raises, andErrorResponse.getStatusCode()carries the HTTP status on every path, including the multipart uploads and the bulk download, so callers can tell a Cloudflare 403 from an API 400/404.New: the Warmup API client.
ZeroBounceWarmupcovers all 26 documented endpoints of what is a separate product — its own host, its own team-scoped key in anAPI-Keyheader, two CSV downloads. Ranges are checked before sending (engagement rules 0–4, reply 5–45 %, important 5–40 %, click ≤ 70 % and ≤ important × 1.75), 204 and empty 200 come back asnull,checkDomainDmarcrequires an email address (the live API rejects a bare domain), and the SMTP test masks the credentials out of every error it raises. A key with surrounding whitespace or control characters is refused before it can be quoted unredacted by the transport.Testing. 95 offline tests pass (
mvn test), no network. The live suite (live/LiveApi.java) sits outside the Maven module's source root, somvn testand CI never compile or run it; the last full run withZEROBOUNCE_LIVE_SPEND=1 ZEROBOUNCE_LIVE_CHANGES=1was 40 passed, 0 failed, 4 skipped (the four skips are Warmup statistics endpoints, which need a sending account this team does not have). It never creates an Inbox Placement test, sets the Inbox Placement webhook, or starts ZB-managed Warmup onboarding, because the API cannot undo any of those; the filter and sending account it does create are deleted in cleanup even when a test fails.No version bump, tag or publish.
🤖 Generated with Claude Code