PHP SDK
GhostFlowClient Wrapper
Section titled “GhostFlowClient Wrapper”A production-ready PHP client using Guzzle 7+. Handles Bearer authentication,
rate-limit headers, automatic retries on 429 / 5xx, and structured error responses.
Full Client
Section titled “Full Client”<?phpdeclare(strict_types=1);
namespace GhostFlow;
use GuzzleHttp\Client;use GuzzleHttp\Exception\RequestException;use GuzzleHttp\Psr7\Response;
class GhostFlowError extends \RuntimeException{ public string $errorCode; public int $httpStatus;
public function __construct(string $code, string $message, int $status) { parent::__construct($message, $status); $this->errorCode = $code; $this->httpStatus = $status; }}
class RateLimitInfo{ public function __construct( public readonly int $limit, public readonly int $remaining, public readonly int $reset, ) {}
public static function fromResponse(Response $response): self { return new self( limit: (int) ($response->getHeaderLine('X-RateLimit-Limit') ?: 0), remaining: (int) ($response->getHeaderLine('X-RateLimit-Remaining') ?: 0), reset: (int) ($response->getHeaderLine('X-RateLimit-Reset') ?: 0), ); }}
class GhostFlowClient{ private Client $http; private int $maxRetries;
public function __construct( ?string $apiKey = null, string $baseUrl = 'https://devcore.getghostflow.io/api/v1', int $maxRetries = 2, float $timeout = 30.0, ) { $apiKey ??= getenv('GF_API_KEY') ?: throw new \RuntimeException('GF_API_KEY not set');
$this->maxRetries = $maxRetries; $this->http = new Client([ 'base_uri' => rtrim($baseUrl, '/') . '/', 'timeout' => $timeout, 'headers' => [ 'Authorization' => "Bearer {$apiKey}", 'Content-Type' => 'application/json', 'Accept' => 'application/json', ], ]); }
// ── Core request method ────────────────────────────────
/** * @return array{0: mixed, 1: RateLimitInfo} */ public function request(string $method, string $path, array $options = []): array { $lastError = null;
for ($attempt = 0; $attempt <= $this->maxRetries; $attempt++) { try { $response = $this->http->request($method, ltrim($path, '/'), $options); $rateLimit = RateLimitInfo::fromResponse($response); $data = json_decode((string) $response->getBody(), true);
return [$data, $rateLimit]; } catch (RequestException $e) { $response = $e->getResponse(); if ($response === null) { throw $e; }
$body = json_decode((string) $response->getBody(), true) ?? []; $status = $response->getStatusCode(); $lastError = new GhostFlowError( $body['code'] ?? 'UNKNOWN', $body['message'] ?? $e->getMessage(), $status, );
if (in_array($status, [429, 500, 502, 503], true) && $attempt < $this->maxRetries) { $retryAfter = (int) ($response->getHeaderLine('Retry-After') ?: 1); sleep($retryAfter); continue; } } }
throw $lastError; }
// ── Convenience shortcuts ──────────────────────────────
public function get(string $path, array $query = []): array { return $this->request('GET', $path, ['query' => $query]); }
public function post(string $path, array $json = []): array { return $this->request('POST', $path, ['json' => $json]); }
public function put(string $path, array $json = []): array { return $this->request('PUT', $path, ['json' => $json]); }
public function delete(string $path): array { return $this->request('DELETE', $path); }}<?php
require_once __DIR__ . '/vendor/autoload.php';
use GhostFlow\GhostFlowClient;use GhostFlow\GhostFlowError;
$gf = new GhostFlowClient(); // picks up GF_API_KEY from env
// List campaigns[$campaigns, $rl] = $gf->get('campaigns');echo "Found " . count($campaigns) . " campaigns, {$rl->remaining} requests left\n";
// Create a campaign[$campaign, $_] = $gf->post('campaigns', [ 'name' => 'Summer Promo', 'url' => 'https://example.com/promo',]);echo "Created campaign {$campaign['id']}\n";
// Dashboard stats[$stats, $_] = $gf->get('reports/dashboard', [ 'from' => '2025-01-01', 'to' => '2025-01-31',]);Error Handling
Section titled “Error Handling”<?php
try { $gf->get('campaigns/non-existent-id');} catch (GhostFlowError $e) { match ($e->errorCode) { 'NOT_FOUND' => echo "Campaign does not exist\n", 'AUTH_INVALID_API_KEY' => echo "Check your API key\n", 'RATE_LIMITED' => echo "Slow down — retry after backoff\n", default => echo "API error {$e->httpStatus}: {$e->getMessage()}\n", };}