TypeScript / Node.js SDK
GhostFlowClient Wrapper
Section titled “GhostFlowClient Wrapper”A lightweight, typed wrapper around fetch that handles authentication, error parsing,
rate-limit headers, and retries. Copy the class below into your project or extract it as
a shared module.
Full Client
Section titled “Full Client”export interface GhostFlowConfig { apiKey: string; baseUrl?: string; /** Max automatic retries on 429 / 5xx (default: 2) */ maxRetries?: number;}
export interface GhostFlowError { code: string; message: string; status: number;}
export interface RateLimitInfo { limit: number; remaining: number; reset: number;}
export class GhostFlowClient { private readonly apiKey: string; private readonly baseUrl: string; private readonly maxRetries: number;
constructor(config: GhostFlowConfig) { this.apiKey = config.apiKey; this.baseUrl = config.baseUrl ?? 'https://devcore.getghostflow.io/api/v1'; this.maxRetries = config.maxRetries ?? 2; }
// ── Core request method ────────────────────────────────────
async request<T = unknown>( path: string, options: RequestInit = {}, ): Promise<{ data: T; rateLimit: RateLimitInfo }> { let lastError: GhostFlowError | null = null;
for (let attempt = 0; attempt <= this.maxRetries; attempt++) { const response = await fetch(`${this.baseUrl}${path}`, { ...options, headers: { Authorization: `Bearer ${this.apiKey}`, 'Content-Type': 'application/json', ...options.headers, }, });
const rateLimit: RateLimitInfo = { limit: Number(response.headers.get('x-ratelimit-limit') ?? 0), remaining: Number(response.headers.get('x-ratelimit-remaining') ?? 0), reset: Number(response.headers.get('x-ratelimit-reset') ?? 0), };
if (response.ok) { const data = (await response.json()) as T; return { data, rateLimit }; }
const body = await response.json().catch(() => ({})); lastError = { code: body.code ?? 'UNKNOWN', message: body.message ?? response.statusText, status: response.status, };
// Retry on 429 (rate limited) or 5xx if ((response.status === 429 || response.status >= 500) && attempt < this.maxRetries) { const retryAfter = Number(response.headers.get('retry-after') ?? 1); await new Promise((r) => setTimeout(r, retryAfter * 1000)); continue; }
break; }
throw lastError; }
// ── Convenience shortcuts ──────────────────────────────────
get<T = unknown>(path: string) { return this.request<T>(path, { method: 'GET' }); }
post<T = unknown>(path: string, body: unknown) { return this.request<T>(path, { method: 'POST', body: JSON.stringify(body) }); }
put<T = unknown>(path: string, body: unknown) { return this.request<T>(path, { method: 'PUT', body: JSON.stringify(body) }); }
delete<T = unknown>(path: string) { return this.request<T>(path, { method: 'DELETE' }); }
// ── Resource helpers ───────────────────────────────────────
campaigns = { list: () => this.get<Campaign[]>('/campaigns'), get: (id: string) => this.get<Campaign>(`/campaigns/${id}`), create: (data: CreateCampaign) => this.post<Campaign>('/campaigns', data), delete: (id: string) => this.delete(`/campaigns/${id}`), };
offers = { list: () => this.get<Offer[]>('/offers'), get: (id: string) => this.get<Offer>(`/offers/${id}`), create: (data: CreateOffer) => this.post<Offer>('/offers', data), delete: (id: string) => this.delete(`/offers/${id}`), };
reports = { dashboard: (params: { from: string; to: string }) => this.get(`/reports/dashboard?from=${params.from}&to=${params.to}`), };}
// ── Types ──────────────────────────────────────────────────
export interface Campaign { id: string; name: string; status: string; url: string; created_at: string;}
export interface CreateCampaign { name: string; url: string; offer_id?: string;}
export interface Offer { id: string; name: string; url: string; network_id?: string;}
export interface CreateOffer { name: string; url: string; network_id?: string;}import { GhostFlowClient } from './ghostflow';
const gf = new GhostFlowClient({ apiKey: process.env.GF_API_KEY! });
// List campaignsconst { data: campaigns, rateLimit } = await gf.campaigns.list();console.log(`Campaigns: ${campaigns.length}, remaining: ${rateLimit.remaining}`);
// Create a campaignconst { data: campaign } = await gf.campaigns.create({ name: 'Summer Promo', url: 'https://example.com/promo',});
// Get dashboard statsconst { data: stats } = await gf.reports.dashboard({ from: '2025-01-01', to: '2025-01-31',});Error Handling
Section titled “Error Handling”import { GhostFlowClient, type GhostFlowError } from './ghostflow';
const gf = new GhostFlowClient({ apiKey: process.env.GF_API_KEY! });
try { await gf.campaigns.get('non-existent-id');} catch (err) { const error = err as GhostFlowError; switch (error.code) { case 'NOT_FOUND': console.log('Campaign does not exist'); break; case 'AUTH_INVALID_API_KEY': console.log('Check your API key'); break; case 'RATE_LIMITED': console.log('Slow down — retry after backoff'); break; default: console.error(`API error ${error.status}: ${error.message}`); }}