You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
8.8.2: empty PHP arrays now reach the API as JSON arrays where objects are expected — request rejected with no pointer to the cause #392
The v8.8.2 change ("Empty arrays and objects are no longer stripped when passed as params in the request") has a PHP-specific consequence that's easy to hit and hard to trace back to the SDK: an empty PHP array now reaches the API as a JSON array ([]) in positions where the API expects an object, and the request is rejected.
We're not asking you to revert — forwarding what the caller passed is a reasonable position. We're raising this because the failure mode is hard to diagnose, and other consumers will likely hit it as 8.8.2 propagates.
Details
PHP has no way to distinguish an empty list from an empty map — [] is the only natural empty value, and json_encode picks the array reading. Code like this is common:
$options = [];
if ($needsDdp) {
$options['incoterm'] = 'DDP';
}
$shipment = $client->shipment->create([
// ...'options' => $options, // [] when no branch fired
]);
Before 8.8.2 the SDK stripped the empty options, so this worked. On 8.8.2 the API receives "options": [] and rejects the shipment with:
The provided options are not valid.
Nothing in that message points at the SDK, the upgrade, or the empty array — for us it surfaced only as runtime API errors on previously working code after a routine patch-level composer update.
We verified against the live API that the rejection is purely about JSON type: the same shipment with "options": {} (raw request; the SDK can't produce an empty object from PHP) returns 201 with rates, "options": [] returns 422, and omitting the key returns 201.
Suggestions
Any of these would help, roughly in order of value:
Coerce empty arrays to {} for params documented as objects (options, customs_info, …). Since {} is accepted by the API, this fully restores the conditional-build pattern above — and there's currently no way to send an empty object through the SDK at all (Requestor::encodeObjects handles arrays and EasypostObjects, so a caller-supplied stdClass doesn't survive encoding either).
Add a compatibility warning to the 8.8.2 changelog entry and UPGRADE guide noting that previously-stripped empty params are now sent, and that object-typed params passed as [] will be rejected by the API — so people who hit "The provided options are not valid." can find their way here by searching.
Failing either, a note in the README's request-params section about the [] vs {} ambiguity.
Happy to open a PR for (1) or (2) if that's welcome.
Environment
easypost-php 8.8.2 (upgraded from 8.7.0)
PHP 8.4
Observed with shipment->create when options is an empty array; any object-typed param passed as [] should reproduce.
Thanks for the quick turnaround. Sorry for the delayed response. This seems straightforward to me but I had Claude verify it from my perspective to be exhaustive. You can ignore his verbose response, but I will include it for posterity. The bottom line is that this can ship.
What follows was written by Claude and is posted on behalf of Matthew Chambers (@chamby), who reviewed and approved it before submission. The verification below was run by Claude locally at his direction.
I checked out plain_php_objects (e752c3f) and verified it locally on PHP 8.5:
So the fix does what it says, and the null/empty-string stripping inside a stdClass matches the array behavior. For comparison, on 8.8.2 the (object) [] case doesn't get stringified, it throws Error: Object of class stdClass could not be converted to string from strval() in encodeObjects, so the changelog wording could say "instead of throwing" if you want it exact.
Keeping [] as a JSON array and making {} an explicit (object) opt-in is the right call. The SDK can't know which params are object-typed, and auto-coercing would break list-typed ones.
One small request: since the error message the API returns still gives no hint, a sentence in the 8.8.3 changelog entry (or UPGRADE guide) along the lines of "if you hit The provided options are not valid. after 8.8.2, pass (object) [] for object-typed params you build conditionally" would let people find this from a search. Otherwise this addresses #392 for us. Happy to see it ship.
Activity
Justintime50 commentedon Aug 26, 2026
I put up #393 as a potential fix, let me know if this addresses the introduced issue in 8.8.2 and we'll ship it!
Justintime50 commentedon Sep 8, 2026
Matthew Chambers (@chamby) wanted to check once more before merging, care to chime in?
chamby commentedon Sep 8, 2026
Thanks for the quick turnaround. Sorry for the delayed response. This seems straightforward to me but I had Claude verify it from my perspective to be exhaustive. You can ignore his verbose response, but I will include it for posterity. The bottom line is that this can ship.
I checked out
plain_php_objects(e752c3f) and verified it locally on PHP 8.5:RequestorEncodingTestpasses (3 tests, 15 assertions).shipment->createagainst the test API, watchingrequest_bodyvia the request hook:'options' => [][]'options' => (object) []{}'options' => (object) ['incoterm' => 'DDU', 'currency' => '']{"incoterm":"DDU"}So the fix does what it says, and the null/empty-string stripping inside a
stdClassmatches the array behavior. For comparison, on 8.8.2 the(object) []case doesn't get stringified, it throwsError: Object of class stdClass could not be converted to stringfromstrval()inencodeObjects, so the changelog wording could say "instead of throwing" if you want it exact.Keeping
[]as a JSON array and making{}an explicit(object)opt-in is the right call. The SDK can't know which params are object-typed, and auto-coercing would break list-typed ones.One small request: since the error message the API returns still gives no hint, a sentence in the 8.8.3 changelog entry (or UPGRADE guide) along the lines of "if you hit
The provided options are not valid.after 8.8.2, pass(object) []for object-typed params you build conditionally" would let people find this from a search. Otherwise this addresses #392 for us. Happy to see it ship.fix: preserves caller-provided plain PHP objects in request params (c…