$headers * @return array> */ public function fetch(string $endpoint, array $headers = [], ?string $jsonPath = null): array { if (! filter_var($endpoint, FILTER_VALIDATE_URL)) { throw new \InvalidArgumentException('Invalid API endpoint URL.'); } $curlHeaders = ['Accept: application/json']; foreach ($headers as $key => $value) { $k = trim((string) $key); if ($k === '') { continue; } $curlHeaders[] = $k . ': ' . (string) $value; } $ch = curl_init($endpoint); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, CURLOPT_HTTPHEADER => $curlHeaders, ]); $raw = (string) curl_exec($ch); $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); if ($error !== '') { throw new \RuntimeException('API request failed: ' . $error); } if ($httpCode < 200 || $httpCode >= 400) { throw new \RuntimeException('API request failed with HTTP ' . $httpCode . '.'); } $decoded = json_decode($raw, true); if (! is_array($decoded)) { return []; } $data = $decoded; if ($jsonPath !== null && trim($jsonPath) !== '') { foreach (explode('.', $jsonPath) as $segment) { $segment = trim($segment); if ($segment === '' || ! is_array($data) || ! array_key_exists($segment, $data)) { return []; } $data = $data[$segment]; } } if (! is_array($data)) { return []; } // Normalize to list of rows. if ($data !== [] && array_keys($data) !== range(0, count($data) - 1)) { return [$data]; } $rows = []; foreach ($data as $row) { if (is_array($row)) { $rows[] = $row; } } return $rows; } }