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.

Steps to decode a JSON response with PHP cURL:

  1. Confirm that the PHP cURL extension is loaded in the runtime that will run the script.
    $ 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.

  2. Create a local JSON endpoint for repeatable testing.
    json-api.php
    <?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.

  3. Start PHP's built-in server for the test endpoint in a second terminal.
    $ 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.

  4. Create the PHP cURL client script that checks the transfer, HTTP response, content type, and decoded fields.
    decode-response.php
    <?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.

  5. Run the decoder against the local endpoint and confirm that PHP receives decoded fields.
    $ 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.

  6. Point the script at the real API endpoint and adjust the field checks to the response shape.

    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.