JavaScript / TypeScript
Typed client
A minimal TypeScript client covering all read endpoints:
const BASE_URL = "https://api.fuelkenya.com/v1";
export interface FuelPrice {
id: number;
town: string;
super_petrol: number;
diesel: number;
kerosene: number;
valid_from: string;
valid_to: string;
}
async function get<T>(path: string, params?: Record<string, string>): Promise<T> {
const url = new URL(`${BASE_URL}${path}`);
if (params) {
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
}
const res = await fetch(url.toString());
if (!res.ok) throw new Error(`FuelKenya API error ${res.status}: ${await res.text()}`);
return res.json();
}
export const fuelKenya = {
/** All towns with price data */
towns: () => get<string[]>("/towns"),
/** Latest cycle prices — optionally filtered to one town */
latestPrices: (town?: string) =>
get<FuelPrice[]>("/prices/latest", town ? { town } : undefined),
/** Price history — newest first */
priceHistory: (town: string, limit = 12, offset = 0) =>
get<FuelPrice[]>("/prices", {
town,
limit: String(limit),
offset: String(offset),
}),
/** Health check */
health: () => get<{ status: string }>("/health"),
};Usage examples
Get current prices for a town
const prices = await fuelKenya.latestPrices("Nairobi");
const [nairobi] = prices;
console.log(`Super Petrol: KSh ${nairobi.super_petrol.toFixed(2)}`);
console.log(`Diesel: KSh ${nairobi.diesel.toFixed(2)}`);
console.log(`Kerosene: KSh ${nairobi.kerosene.toFixed(2)}`);Build a price ranking (cheapest to most expensive)
const all = await fuelKenya.latestPrices();
const ranking = [...all]
.sort((a, b) => a.super_petrol - b.super_petrol)
.map((p, i) => ({
rank: i + 1,
town: p.town,
petrol: p.super_petrol,
savings: p.super_petrol - all[0]?.super_petrol,
}));
console.table(ranking.slice(0, 10)); // top 10 cheapestFetch historical trend data
const history = await fuelKenya.priceHistory("Mombasa", 12);
// Oldest → newest for charting
const chronological = [...history].reverse();
const labels = chronological.map(h =>
new Date(h.valid_from).toLocaleDateString("en-GB", { month: "short", year: "2-digit" })
);
const petrolSeries = chronological.map(h => h.super_petrol);Populate a React dropdown
import { useEffect, useState } from "react";
import { fuelKenya } from "./fuelKenya";
export function TownSelector({ onChange }: { onChange: (town: string) => void }) {
const [towns, setTowns] = useState<string[]>([]);
useEffect(() => {
fuelKenya.towns().then(setTowns);
}, []);
return (
<select onChange={e => onChange(e.target.value)}>
{towns.map(t => <option key={t} value={t}>{t}</option>)}
</select>
);
}Check price for a given date
async function priceOnDate(town: string, date: Date): Promise<FuelPrice | null> {
const d = date.toISOString().slice(0, 10); // YYYY-MM-DD
const records = await fuelKenya.priceHistory(town, 24);
return records.find(r => r.valid_from <= d && d <= r.valid_to) ?? null;
}
const price = await priceOnDate("Nakuru", new Date("2026-03-01"));
console.log(price?.super_petrol); // e.g. 217.36Next.js integration
The FuelKenya web app uses fetch with { cache: "no-store" } on the server side to always load fresh cycle prices:
// lib/api.ts — server-side fetching in Next.js App Router
const BASE = process.env.NEXT_PUBLIC_FUELKENYA_API_URL ?? "http://localhost:8000/v1";
export async function fetchLatestPrices(town?: string): Promise<FuelPrice[]> {
const query = town ? `?town=${encodeURIComponent(town)}` : "";
const res = await fetch(`${BASE}/prices/latest${query}`, { cache: "no-store" });
if (!res.ok) throw new Error(`Failed to fetch prices: ${res.status}`);
return res.json();
}For a static site you can use revalidate instead:
const res = await fetch(`${BASE}/prices/latest`, {
next: { revalidate: 86400 }, // revalidate daily
});