TypeScript SDK
@koda-rising/sdk is the official TypeScript client — the same one the CLI and VS Code extension use. It’s a thin, typed
wrapper over the HTTP API. Any language can call the REST API directly (see /openapi.json); this
SDK is the ergonomic path for TypeScript/Node.
- Runtime: Node 18+ (global
fetch+ web streams). Works in Bun/Deno. - Package:
@koda-rising/sdk.
import { KodaClient } from '@koda-rising/sdk';Authenticate & construct the client
Section titled “Authenticate & construct the client”const koda = new KodaClient( 'https://your-gateway.example.com', // base URL (no trailing path) process.env.KODA_TOKEN!, // an API key — Authorization: Bearer <key> process.env.KODA_ORG, // optional org id (sent as the x-org-id header));Create a key from the dashboard, the CLI (koda keys create --scopes agent:run,wallet:read), or createKey below.
Keys carry scopes: agent:run, wallet:read, keys:manage, metrics:read. The SDK is for servers, CI, and scripts —
never ship a key in client-side code.
Account & wallet
Section titled “Account & wallet”import { minorToNaira } from '@koda-rising/sdk';
const me = await koda.me(); // { userId, orgId, role, balanceMinor, scopes, tier, currency, allowedLanes, ... }console.log(`Balance: ${me.currency?.symbol}${minorToNaira(me.balanceMinor)}`);
const { balanceMinor } = await koda.getWallet();
const intent = await koda.topup(5000); // ₦5,000; { reference, amountMinor, authorizationUrl?, ussdCode? }if (intent.authorizationUrl) redirectUser(intent.authorizationUrl);await koda.topup(3000, { channel: 'mpesa', phone: '2547XXXXXXXX' }); // cardless rails take a channel (+ phone)
const v = await koda.verifyTopup(intent.reference); // { credited, status, balanceMinor? }Run an agent task (streaming)
Section titled “Run an agent task (streaming)”agentRun and agentRunLoop are async generators — iterate them to consume events as they arrive.
for await (const ev of koda.agentRun('Add a null check to parseUser()', [ { path: 'src/user.ts', content: currentSource },])) { if (ev.decision) console.log(`lane=${ev.decision.lane} model=${ev.decision.model}`); if (ev.plan) console.log(ev.plan); if (ev.edits) applyEdits(ev.edits); // FileEdit[] — { path, action, content?, description? } if (ev.review?.findings.length) console.warn(ev.review.findings); // fintech code-quality findings if (ev.summary) console.log(ev.summary, `cost=${ev.costMinor} balance=${ev.balanceMinor}`);}Each AgentEvent may carry: decision, plan, edits, summary, costMinor, balanceMinor, chunk, review.
The verify loop (agentRunLoop)
Section titled “The verify loop (agentRunLoop)”Runs plan→edit→verify→retry server-side, metered per iteration. Pass a sandbox + testCmd to actually run the project’s
tests between turns (use docker in production; local needs the operator opt-in and is single-tenant only).
for await (const ev of koda.agentRunLoop('Make the failing test pass', context, { sandbox: 'docker', testCmd: 'npm test', maxIters: 4,})) { if (ev.step) console.log(`iter ${ev.step.iteration}: ${ev.step.edits} edits, ok=${ev.step.ok}`); if (ev.turn) console.log(` ${ev.turn.lane}/${ev.turn.model} +${ev.turn.costMinor} (${ev.turn.reason})`); if (ev.verified !== undefined) console.log(`done: verified=${ev.verified} in ${ev.iterations} iters, total=${ev.totalCostMinor}`); if (ev.error) throw new Error(ev.error);}Repo index, keys & teams
Section titled “Repo index, keys & teams”await koda.repoIndex([{ path: 'src/pay.ts', content }]); // { indexed, chunks, costMinor, balanceMinor } — vector modeconst status = await koda.repoStatus(); // { enabled, chunks, paths }await koda.repoClear('src/pay.ts'); // one path, or repoClear() for all
const key = await koda.createKey('ci-token', ['agent:run', 'wallet:read']); // { id, key, prefix } — key shown ONCEawait koda.listKeys(); await koda.revokeKey(key.id);
const team = await koda.createTeam('Acme Eng'); // { orgId, walletId }await koda.addMember(team.orgId, { email: 'dev@acme.io', role: 'member', capNaira: 20000 }); // ₦20k monthly capawait koda.listMembers(team.orgId);Error handling
Section titled “Error handling”- Insufficient balance —
agentRun/agentRunLoopthrowError('insufficient balance — top up to continue')on a402. Catch it and prompt a top-up. - Other non-2xx throw
Error('agent <status>')(streaming) or reject with the gateway’s{ error }message. - Idempotency — pass
{ idempotencyKey }to make a retried call exactly-once. - Low bandwidth —
{ lowBandwidth: true }pins the cheap open lane and trims context.
try { for await (const ev of koda.agentRun(prompt, ctx, { idempotencyKey: reqId, lowBandwidth: true })) { /* … */ }} catch (e) { if (String(e).includes('insufficient balance')) await promptTopUp(); else throw e;}Other languages
Section titled “Other languages”The API is plain HTTP + SSE. Point any OpenAPI generator at /openapi.json for a typed client, or call the routes
directly — see the API reference and the interactive docs at /docs.