Skip to content

TypeScript / Node.js SDK

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.

ghostflow.ts
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 campaigns
const { data: campaigns, rateLimit } = await gf.campaigns.list();
console.log(`Campaigns: ${campaigns.length}, remaining: ${rateLimit.remaining}`);
// Create a campaign
const { data: campaign } = await gf.campaigns.create({
name: 'Summer Promo',
url: 'https://example.com/promo',
});
// Get dashboard stats
const { data: stats } = await gf.reports.dashboard({
from: '2025-01-01',
to: '2025-01-31',
});
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}`);
}
}