Guides
FOMO API TypeScript Quickstart: Traders to Trades
A small TypeScript client for the two calls most FOMO integrations need first: resolve a handle, then fetch that trader’s available positions and trade history.

A useful FOMO API integration starts with two calls: resolve the trader a user typed, then request the trade data available for that trader. This quickstart wraps both calls in a small TypeScript client without inventing fields that are not in the current API contract.
What you will build
The finished example accepts a fomo.family handle and returns:
- the normalized trader profile;
- the trader's Solana and EVM wallets when resolved;
- PnL for the available time windows;
- the available trade and position history;
- the credit cost and remaining balance reported in response headers.
The examples use the documented REST origin, https://api.fomoapi.io. Keep the API key on your server. If browser code contains the key, anyone who opens the application can copy it.
Create a key in the dashboard before continuing. The API reference remains the source of truth for current fields and credit costs.
Create a typed request helper
Node 18 and newer include fetch. Start with a helper that sends the Bearer key, checks the HTTP response before trusting the body, and exposes credit headers to the caller:
const API_ORIGIN = "https://api.fomoapi.io";
type FomoResponse<T> = {
data: T;
credits: {
cost: number | null;
remaining: number | null;
};
};
class FomoApiError extends Error {
constructor(
readonly status: number,
readonly body: unknown,
) {
super(`FOMO API returned ${status}`);
}
}
async function fomoGet<T>(path: string, apiKey: string): Promise<FomoResponse<T>> {
const response = await fetch(new URL(path, API_ORIGIN), {
headers: { authorization: `Bearer ${apiKey}` },
});
const body: unknown = await response.json().catch(() => null);
if (!response.ok) throw new FomoApiError(response.status, body);
const numberHeader = (name: string) => {
const value = response.headers.get(name);
return value === null ? null : Number(value);
};
return {
data: body as T,
credits: {
cost: numberHeader("x-credits-cost"),
remaining: numberHeader("x-credits-remaining"),
},
};
}
The helper deliberately does not retry. Retry behavior depends on the status and operation; a separate production client should back off on transient failures without retrying invalid keys or exhausted credit balances.
Describe the trader fields you use
Do not create a giant interface for fields your application never reads. A narrow type is easier to update when the API adds data:
type Trader = {
handle: string;
displayName: string;
verified: boolean;
wallets: {
solana: string | null;
evm: string | null;
};
pnl: {
"24h": number;
"7d": number;
"30d": number;
all: number;
};
pnlUsd: number;
volumeUsd: number;
trades: number;
followers: number;
fomoCreatedAt?: string;
accountAgeDays?: number;
};
This is compile-time documentation, not runtime validation. Applications crossing a trust boundary should validate the response with a schema library before persisting it.
Resolve a handle to its wallets
The handle lookup is case-insensitive and accepts an optional leading @:
async function getTrader(handle: string, apiKey: string) {
const safeHandle = encodeURIComponent(handle.replace(/^@/, ""));
return fomoGet<Trader>(`/v2/users/${safeHandle}`, apiKey);
}
const apiKey = process.env.FOMO_API_KEY;
if (!apiKey) throw new Error("FOMO_API_KEY is required");
const trader = await getTrader("frankdegods", apiKey);
console.log(trader.data.wallets);
console.log(trader.credits);
Treat a missing wallet as data, not as permission to substitute another address. Wallet resolution can still be in progress, and downstream chain reads must wait for the intended address.
Fetch the available trade history
The current route is GET /v2/users/{handle}/positions. Two older names still answer: the original query-parameter form, and the user-scoped trades resource. The second one now returns deprecation: true with a link header naming /positions as its successor, so migrate onto /positions rather than onto it.
type Trade = {
tradeId?: string;
token: { symbol: string; address: string };
side: string;
status?: string;
sizeUsd?: number;
realizedPnlUsd?: number | null;
unrealizedPnlUsd?: number | null;
chainId?: number;
chain?: string;
ts?: number;
source?: "captured" | "feed";
};
type TradesResponse = {
key: string;
kind: string;
count: number;
trades: Trade[];
partial?: boolean;
complete?: boolean;
upstreamCalls?: number;
};
async function getTrades(handle: string, apiKey: string) {
const safeHandle = encodeURIComponent(handle.replace(/^@/, ""));
return fomoGet<TradesResponse>(
`/v2/users/${safeHandle}/positions?limit=25`,
apiKey,
);
}
const history = await getTrades("frankdegods", apiKey);
console.log(history.data.trades);
console.log({
partial: history.data.partial ?? false,
complete: history.data.complete ?? false,
});
The endpoint does not promise a complete lifetime ledger. FOMO exposes every open position but limits closed-position history. The optional deep mode fans out across chains and sort orders, but even that response identifies itself as incomplete. When a product requires complete on-chain history, resolve the wallets first and read those chains directly.
Put the calls behind your backend
A minimal application endpoint can combine the two calls while keeping the key private:
export async function loadTrader(handle: string) {
const apiKey = process.env.FOMO_API_KEY;
if (!apiKey) throw new Error("FOMO_API_KEY is required");
const [profile, history] = await Promise.all([
getTrader(handle, apiKey),
getTrades(handle, apiKey),
]);
return {
profile: profile.data,
trades: history.data.trades,
coverage: {
partial: history.data.partial ?? false,
complete: history.data.complete ?? false,
},
creditsRemaining: history.credits.remaining,
};
}
Do not call this function on every render without caching or request coalescing. The credit headers tell you what each response cost and how much remains.
Production checklist
Before shipping:
- Store
FOMO_API_KEYin server-side secrets. - Encode handles before placing them in a URL.
- Validate the response fields your database depends on.
- Treat
partial,complete, andretryableas product states rather than hiding them. - Log status, endpoint, duration, and credit headers, but never log the API key.
- Add bounded retries only for transient failures.
- Link users to the FOMO Family API overview when they need the wider endpoint map.
That is enough for a typed first integration. Add WebSocket streaming only when the application actually needs pushed events rather than occasional REST reads.
Sources
- FOMO API OpenAPI 3.1 specification FOMO API, accessed 2026-09-17
- FOMO API documentation FOMO API, accessed 2026-09-17
Ship on fomo.family trader data
Both-chain wallets, PnL and holdings, plus two live streams: the app feed on every plan, the on-chain stream on Growth. One API.
Get an API key