A minimal client
kash-embed-client.js
const KASH_API = 'https://app.kash.bot/api';
export class KashApiError extends Error {
constructor(status, body) {
super(body?.message ?? `HTTP ${status}`);
this.status = status;
this.code = body?.code;
this.details = body?.details;
this.retryAfter = body?.details?.retryAfter;
}
}
async function get(path, params = {}) {
const query = new URLSearchParams(
Object.entries(params).filter(([, v]) => v !== undefined && v !== null)
);
const qs = query.toString();
const url = `${KASH_API}${path}${qs ? `?${qs}` : ''}`;
const res = await fetch(url);
const body = await res.json().catch(() => null);
if (!res.ok) throw new KashApiError(res.status, body);
return body;
}
export const kash = {
markets: (params) => get('/markets', params),
market: (id, params) => get(`/markets/${id}`, params),
marketsState: (ids) => get('/markets/state', { ids: ids.join(',') }),
quote: (id, outcomeIndex, action, amountAtomic) =>
get(`/markets/${id}/quote`, { outcomeIndex, action, amount: String(amountAtomic) }),
twitterLink: (id, outcomeIndex, amount, locale) =>
get(`/markets/${id}/twitter-link`, { outcomeIndex, amount, locale }),
threads: (params) => get('/threads', params),
threadMarkets: (id, params) => get(`/threads/${id}/markets`, params),
};
/** Implied probabilities as shown on Kash: one percentage per outcome. */
export function formatPrices(market) {
return market.outcomes.map((o) => ({
label: o.label,
percent: Math.round(o.impliedChance * 100),
}));
}
/** Atomic USDC (6 decimals) to a display string. */
export function formatUsdc(atomic) {
return (Number(atomic) / 1_000_000).toLocaleString(undefined, {
style: 'currency',
currency: 'USD',
maximumFractionDigits: 0,
});
}
List live markets
import { kash, formatPrices, formatUsdc } from './kash-embed-client.js';
const { markets, pagination } = await kash.markets({
status: 'active',
tradableOnly: true,
sortBy: 'total_volume',
limit: 10,
});
for (const m of markets) {
const prices = formatPrices(m).map((o) => `${o.label} ${o.percent}%`).join(' · ');
console.log(`${m.title} — ${prices} — ${formatUsdc(m.totalVolume)} volume`);
}
console.log(`${pagination.total} live markets`);
Search
const { markets } = await kash.markets({ search: 'champions league', status: 'all', limit: 20 });
Keep prices live
Load the cards once, then poll the state endpoint. It refreshes about every second, so polling every 5 to 10 seconds is plenty.import { kash } from './kash-embed-client.js';
function watchPrices(marketIds, onUpdate, intervalMs = 5000) {
let stopped = false;
async function tick() {
try {
const { markets } = await kash.marketsState(marketIds); // up to 200 ids
for (const state of markets) onUpdate(state.id, state);
} catch (err) {
if (err.status === 429 && err.retryAfter) {
await new Promise((r) => setTimeout(r, err.retryAfter * 1000));
}
}
if (!stopped) setTimeout(tick, intervalMs);
}
tick();
return () => { stopped = true; };
}
const stop = watchPrices(['93f1445c-912c-47b4-8065-d1ff5a6b8dfc'], (id, state) => {
console.log(id, state.outcomes.map((o) => o.impliedChance));
});
Price a trade
const usdc = 25;
const { quote } = await kash.quote(
'93f1445c-912c-47b4-8065-d1ff5a6b8dfc',
0,
'buy',
usdc * 1_000_000 // atomic USDC
);
console.log(`$${usdc} buys ${quote.estimate?.tokensReceived?.toFixed(2)} tokens`);
console.log(`Pays about $${quote.estimate?.payoutAtCurrentPriceUsdc?.toFixed(2)} if it wins at today's prices`);
“Predict on X” buttons
import { kash, KashApiError } from './kash-embed-client.js';
async function predictOnX(marketId, outcomeIndex, amount) {
try {
const { twitterUrl } = await kash.twitterLink(marketId, outcomeIndex, amount);
window.open(twitterUrl, '_blank', 'noopener');
} catch (err) {
if (err instanceof KashApiError && err.status === 400) {
alert(err.message); // e.g. "This market is no longer accepting predictions"
} else {
throw err;
}
}
}
document.querySelectorAll('[data-kash-market]').forEach((button) => {
button.addEventListener('click', () =>
predictOnX(button.dataset.kashMarket, Number(button.dataset.outcome), 10)
);
});
<button data-kash-market="93f1445c-912c-47b4-8065-d1ff5a6b8dfc" data-outcome="0">Yes · $10</button>
<button data-kash-market="93f1445c-912c-47b4-8065-d1ff5a6b8dfc" data-outcome="1">No · $10</button>
Paginate through everything
async function* allMarkets(params) {
let offset = 0;
while (true) {
const page = await kash.markets({ ...params, limit: 500, offset });
yield* page.markets;
if (!page.pagination.hasMore) return;
offset += page.markets.length;
}
}
for await (const m of allMarkets({ status: 'resolved' })) {
// offset is capped at 10,000. To go further back, narrow by resolvedAfter / resolvedBefore
}
Listen to iframe events
window.addEventListener('message', (event) => {
if (event.origin !== 'https://app.kash.bot') return;
const { type, data } = event.data ?? {};
if (type === 'kash-open-twitter') window.open(data.url, '_blank', 'noopener');
if (type === 'kash-iframe-ready') console.log('ready', data);
});
Next steps
React examples
Components and hooks
API overview
Endpoints, limits and errors