Skip to content

fix: make the S3 jobs, the email finder and the IPT match the live API - #60

Merged
franciscbalint merged 1 commit into
masterfrom
fix/api-parity-s3-return-url-and-ipt-limits
Oct 25, 2026
Merged

franciscbalint merged 1 commit into
masterfrom
fix/api-parity-s3-return-url-and-ipt-limits

Conversation

@franciscbalint

Copy link
Copy Markdown
Collaborator

Five calls whose signatures promised something the live API would not accept, plus two stale comments.

Fixed

scoreS3File could never succeed. ZBScoreS3FileOptions had no returnUrl field, so return_url was never sent, and the live API answers 400 {"error_message":"Provided return URL is not permitted: None"}. The field is added and required.

validateS3File's return_url was optional (options.returnUrl?.let) though the API requires it, so the same 400 was one default argument away.

Both now route through bulkRootFileJob, which already enforced file_name and return_url for the other six S3/SFTP jobs. All eight reject a missing field with the same message before a request goes out. The jobs keep their own extra fields (email_address_column, the name/gender/IP columns, allow_phase_2, remove_duplicate), and has_header_row is still omitted when unset on the two S3 jobs.

The bulk email finder accepted a file with no name column. Only domain_column >= 1 was checked; the docs require first_name_column or full_name_column alongside it. sendEmailFinderFile and sendEmailFinderFileStream now reject that call.

createInboxPlacementTest only rejected a blank name. It now checks the documented limits before spending a round trip on an endpoint that bills inbox-test credits: name at most 64 characters, testType in 1|2|3, and senderEmail required when testType == 1.

listInboxPlacementTests sent pageSize, which the API silently ignores (page_size=1 paginates; pageSize=1 returns the full list), so a caller asking for a small page quietly got everything. It now sends page_size, bounds it to 1-100, and gains the createdAfter / createdBefore filters, which were absent from ZBListInboxPlacementTestsOptions entirely.

setListEvaluatorBaseUrl redirected only half the List Evaluator. The result fetch used listEvaluatorBaseUrl, but the upload and send-evaluated-file were built from bulkApiBaseUrl, so overriding the host split one flow across two hosts. All three legs now follow the List Evaluator base; send-evaluated-file resolves one path segment above it, which leaves the default URLs unchanged.

Docs

  • validateSFTPFile's KDoc told callers they could leave fileName null, but bulkRootFileJob errors with Empty parameter: file_name first. The code is right per the live API; the comment was stale.
  • The Inbox Placement Tester block still claimed the API key goes on the query string. The code sends Authorization: Bearer.
  • CHANGELOG, README and documentation.md updated; documentation.md also still carried both stale claims.

Public API

No names change. ZBScoreS3FileOptions.returnUrl and ZBListInboxPlacementTestsOptions.createdAfter / createdBefore are new fields with defaults, appended so existing positional construction keeps working. The new requirements surface through the error callback, matching how ZBBulkFileJobOptions already enforces its own.

Tests

Seven new offline tests and six updated ones, all against the repo's fake OkHttp transport (no network, no key): method, host, path, fields, auth placement, and the error bodies (success: false on a 200, the IPT detail key). ./gradlew :zero_bounce_sdk:testDebugUnitTest — 124 tests, 0 failures. ./gradlew :zero_bounce_sdk:assembleDebug — BUILD SUCCESSFUL.

The opt-in live suite (-PzbLive) was not run here; it needs real keys from the environment.

🤖 Generated with Claude Code

Five of these calls could not do what their signatures promised.

scoreS3File had no returnUrl field at all, so return_url was never sent and
the call could never succeed: the live API answers 400 "Provided return URL is
not permitted: None". validateS3File had the field but sent it only when set,
so the same 400 was one default argument away. Both now go through
bulkRootFileJob, which already enforced file_name and return_url for the other
six S3/SFTP jobs, so all eight reject a missing field with the same message
before spending a request on it.

The bulk email finder accepted a file with no name column. The docs require
first_name_column or full_name_column next to domain_column, and without one
the job has nothing to build an address from, so it is rejected up front.

createInboxPlacementTest only rejected a blank name, and a test the API
refuses still costs a round trip on an endpoint that spends inbox-test
credits, so the documented limits are checked first: name at most 64
characters, testType 1, 2 or 3, and a senderEmail for type 1.

listInboxPlacementTests sent the page size as pageSize, which the API accepts
and then ignores, so callers silently got the whole list however small a page
they asked for. It now sends page_size, bounds it to 1-100, and carries the
createdAfter / createdBefore filters that were missing entirely.

Also: setListEvaluatorBaseUrl redirected only the result fetch. The upload and
send-evaluated-file were built from the bulk base, so overriding the host
split one flow across two hosts; they now follow the list evaluator base too.
And two comments that documented behaviour the code had already moved past:
validateSFTPFile's KDoc offered a null fileName that bulkRootFileJob rejects,
and the Inbox Placement Tester block still described an api_key query string
where the code sends Authorization: Bearer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@franciscbalint
franciscbalint merged commit 6f98b16 into master Oct 25, 2026
7 checks passed
@franciscbalint
franciscbalint deleted the fix/api-parity-s3-return-url-and-ipt-limits branch October 25, 2026 21:19
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.

1 participant