API requests made with PHP cURL often return a JSON body that should not be decoded until the transfer and HTTP status are known. Capture the response with CURLOPT_RETURNTRANSFER, check the status, then decode the body into a PHP array so application code reads known fields instead of raw text.
curl_exec() returns the response body to PHP when CURLOPT_RETURNTRANSFER is enabled. HTTP error responses are still completed transfers, so the script needs curl_getinfo() for the status code and content type before it trusts the body as JSON.
json_decode() with JSON_THROW_ON_ERROR turns parse failures into JsonException instead of returning null for several different cases. Add JSON_BIGINT_AS_STRING when API identifiers may exceed PHP's integer range, then validate the expected fields before passing decoded data to the rest of the application.
$ php -r "var_export(extension_loaded('curl')); echo PHP_EOL;"
true
Install or enable the platform package for the extension, such as php-curl on Debian or Ubuntu systems, when this command prints false.
<?php header('Content-Type: application/json; charset=UTF-8'); echo '{"profile":{"name":"Ada Lovelace","plan":"pro","account_id":12345678901234567890},"features":["api","exports"]}';
The unquoted account_id value is intentionally larger than a normal PHP integer on some systems, so the client can prove that JSON_BIGINT_AS_STRING preserves it as text.
$ php -S 127.0.0.1:8080 json-api.php
Keep this terminal open until the verification request finishes, then stop it with Ctrl+C.
<?php $url = $argv[1] ?? 'http://127.0.0.1:8080/profile'; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Accept: application/json', ], CURLOPT_TIMEOUT => 10, ]); $body = curl_exec($ch); if ($body === false) { fwrite(STDERR, 'cURL error: ' . curl_error($ch) . PHP_EOL); curl_close($ch); exit(1); } $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE) ?: ''; curl_close($ch); if ($status < 200 || $status >= 300) { fwrite(STDERR, "HTTP {$status}" . PHP_EOL); fwrite(STDERR, $body . PHP_EOL); exit(1); } if (!preg_match('/^application\/json\b/i', $contentType)) { fwrite(STDERR, "Unexpected Content-Type: {$contentType}" . PHP_EOL); exit(1); } try { $payload = json_decode($body, true, 512, JSON_THROW_ON_ERROR | JSON_BIGINT_AS_STRING); } catch (JsonException $e) { fwrite(STDERR, 'JSON error: ' . $e->getMessage() . PHP_EOL); exit(1); } $profile = $payload['profile'] ?? null; $features = $payload['features'] ?? null; if (!is_array($profile) || !isset($profile['name'], $profile['plan'], $profile['account_id']) || !is_array($features)) { fwrite(STDERR, "JSON response did not include the expected profile and features fields" . PHP_EOL); exit(1); } echo "HTTP {$status}" . PHP_EOL; echo "Content-Type: {$contentType}" . PHP_EOL; echo "Name: " . $profile['name'] . PHP_EOL; echo "Plan: " . $profile['plan'] . PHP_EOL; echo "Account ID type: " . gettype($profile['account_id']) . PHP_EOL; echo "Feature count: " . count($features) . PHP_EOL;
Use https:// for real API URLs and keep certificate verification enabled. Do not hide authentication, TLS, proxy, or redirect failures by decoding the body before checking the transfer and HTTP status.
$ php decode-response.php http://127.0.0.1:8080/profile HTTP 200 Content-Type: application/json; charset=UTF-8 Name: Ada Lovelace Plan: pro Account ID type: string Feature count: 2
The string account ID type confirms that JSON_BIGINT_AS_STRING preserved the large identifier instead of converting it to a floating-point value.
Tool: JSON Validator can inspect a captured sample response for syntax errors, duplicate keys, unsafe numbers, and root-shape issues before field paths are hard-coded.