Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.kash.bot/llms.txt

Use this file to discover all available pages before exploring further.

The @kashdao/sdk package is a hand-written TypeScript client for the REST API. It’s the recommended way to integrate from any TS/JS runtime — Node 22+, modern browsers, Deno, Bun, or Cloudflare Workers.
pnpm add @kashdao/sdk
# or: npm install @kashdao/sdk
# or: yarn add @kashdao/sdk
# or: bun add @kashdao/sdk

Construct the client

import { KashClient } from '@kashdao/sdk';

const kash = new KashClient({
  apiKey:  process.env.KASH_API_KEY!,                        // 'kash_live_...' or 'kash_test_...'
  baseUrl: 'https://api.kash.bot/v1',                        // staging: https://api-staging.kash.bot/v1
  timeoutMs: 10_000,                                         // optional, default 30s
  maxRetries: 3,                                             // optional, default 3 (with exp backoff + jitter)
  webhookSecret: process.env.KASH_WEBHOOK_SECRET,            // optional, only needed for constructEvent
});
The client is a thin façade over typed sub-clients: kash.markets, kash.trades, kash.portfolio, kash.webhooks, kash.account, kash.traces.

Place a trade

const trade = await kash.trades.create({
  marketId:     '11111111-1111-4111-8111-111111111111',
  outcomeIndex: 0,
  amount:       '100',
  side:         'buy',
  metadata:     { strategy: 'momentum-v2', cohort: 'beta' },
}, { idempotencyKey: crypto.randomUUID() });

console.log(trade.id, trade.status, trade.idempotent);

if (trade.confirmation) {
  // High-value gate hit — confirm with the one-time token
  await kash.trades.confirm(trade.id, { token: trade.confirmation.token });
}

// Wait for the on-chain result (polls + respects rate limits)
const final = await kash.trades.waitForCompletion(trade.id, {
  pollIntervalMs: 1000,
  timeoutMs:      60_000,
});

console.log(final.status, final.txHash, final.tokensOut);

Pagination — async iteration

Every list method returns a Page<T> that’s both the first page and an AsyncIterable<T>:
const page = await kash.trades.list({ limit: 25, status: 'completed' });

// First page only
console.log(page.data.length, page.pagination.hasMore);

// Or walk every trade across every page
for await (const trade of page) {
  console.log(trade.id, trade.txHash);
}

Get a quote

const quote = await kash.markets.quote({
  marketId:     '...',
  outcomeIndex: 0,
  action:       'buy',
  amount:       '10000000',  // atomic USDC (10 USDC)
});

console.log(quote.tokensOut, quote.effectivePrice, quote.impliedProbability);

Verify a webhook

import express from 'express';

app.post('/webhooks/kash',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    try {
      const event = await kash.webhooks.constructEvent(
        req.body.toString('utf8'),
        req.header('x-kash-signature')!,
        process.env.KASH_WEBHOOK_SECRET!
      );

      switch (event.type) {
        case 'trade.completed':
          // event.data is fully typed (TradeCompletedPayload)
          await onTradeCompleted(event.data);
          break;
        case 'trade.failed':
          await onTradeFailed(event.data);
          break;
        case 'trade.confirmation-required':
          await onConfirmationRequired(event.data);
          break;
      }
      res.sendStatus(200);
    } catch (err) {
      // KashWebhookSignatureError on bad signature, expired, malformed
      res.sendStatus(401);
    }
  }
);

Typed errors

Every error is a typed subclass of KashError:
import {
  KashError,
  KashAuthenticationError,    // 401
  KashAuthorizationError,     // 403
  KashConflictError,          // 409
  KashNotFoundError,          // 404
  KashNotModifiedError,       // 304
  KashRateLimitError,         // 429
  KashServerError,            // 5xx
  KashNetworkError,
  KashTimeoutError,
  KashValidationError,
  KashWebhookSignatureError,
  KashConfigurationError,
} from '@kashdao/sdk';

try {
  await kash.trades.create(body);
} catch (err) {
  if (err instanceof KashRateLimitError) {
    console.log(`Backoff for ${err.retryAfterMs}ms`);
  } else if (err instanceof KashAuthorizationError && err.code === 'INSUFFICIENT_SCOPE') {
    console.log('Re-issue the key with trades:write');
  } else if (KashError.isKashError(err)) {
    // Cross-realm-safe brand check (works across worker postMessage boundaries)
    console.log(err.code, err.requestId);
  } else {
    throw err;
  }
}

Lifecycle hooks

Tap into every request, response, retry, and error for logging or observability:
const kash = new KashClient({
  apiKey,
  hooks: {
    onRequest:  (e) => log.info('kash.request', { method: e.method, url: e.url, attempt: e.attempt }),
    onResponse: (e) => log.info('kash.response', { status: e.status, durationMs: e.durationMs }),
    onRetry:    (e) => log.warn('kash.retry', { attempt: e.attempt, reason: e.reason }),
    onError:    (e) => log.error('kash.error', { code: e.code, attempt: e.attempt }),
  },
});
A throwing hook never breaks the request — exceptions are swallowed and the call proceeds. Hooks are safe to add for telemetry without thinking about resilience.

Cloning a client

withConfig returns a new client with overridden fields. Useful for tenant switching or per-request key swaps:
const tenantA = new KashClient({ apiKey: 'kash_live_AAAA...' });
const tenantB = tenantA.withConfig({ apiKey: 'kash_live_BBBB...' });
// tenantA and tenantB are independent — same baseUrl, different keys
withConfig re-validates — passing an invalid override throws KashConfigurationError.

Mock client for tests

@kashdao/sdk/testing exports a fake client with realistic default fixtures:
import { createMockKashClient, DEFAULT_TRADE } from '@kashdao/sdk/testing';

const mock = createMockKashClient({
  // Override specific methods
  trades: {
    create: async () => ({ ...DEFAULT_TRADE, status: 'pending', idempotent: false }),
  },
});

// Inject into your code under test
const result = await yourTradingBot.run(mock);
The defaults pass the wire-shape contract tests, so they’re indistinguishable from real production responses for downstream consumers.

Versioning

The SDK follows SemVer. The KashError shape, public method signatures, and exported types are SemVer-stable. Internals (private fields, pagination tokens, transport details) are not — pin to a major version range in package.json. The SDK auto-includes a User-Agent header identifying its version, so we can correlate API logs with SDK versions when investigating issues.

Pin against a specific API contract version

Send the X-Kash-Api-Version request header to lock your integration against a specific contract date. The SDK can be configured to send it on every request:
const kash = new KashClient({
  apiKey:    process.env.KASH_API_KEY!,
  apiVersion: '2026-04-29', // optional; omits the header (server defaults) when not set
});
Server response carries X-API-Version (the canonical version) plus Sunset / Deprecation headers when your pinned version is winding down (12-month deprecation window per RFC 8594). The SDK’s onResponse hook surfaces both — wire it into your monitoring to flag deprecations early. Pinning to an unknown date returns 410 API_VERSION_UNSUPPORTED with the supported list in metadata.supported.

Quality gates

The SDK ships with strict quality gates that are part of the public contract:
  • Bundle size budgets — main entry ≤ 24 KB gzipped, /testing subpath ≤ 8 KB. Enforced at publish time via size-limit.
  • Static type testspnpm test:types runs vitest’s typecheck mode against tests/types/public-types.test-d.ts. The public TypeScript surface is locked: any change to a public type’s shape fails compilation, forcing a deliberate CHANGELOG + version bump.
  • Public-barrel snapshottests/unit/barrel-snapshot.test.ts pins the exact set of exported names. Accidental removals fail the build.
  • Wire-shape contracttests/contract/wire-shape.test.ts asserts every resource schema matches canonical fixtures. Ships to the public mirror.

Source & changelog

Next

CLI

kash — the command-line interface. Same primitives, scripting-friendly.

Endpoint Reference

The full live spec at /v1/docs.