> ## 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.

# TypeScript SDK

> @kashdao/sdk — typed everything. Trades, markets, portfolios, webhooks, retries, idempotency.

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.

```bash theme={null}
pnpm add @kashdao/sdk
# or: npm install @kashdao/sdk
# or: yarn add @kashdao/sdk
# or: bun add @kashdao/sdk
```

## Construct the client

```ts theme={null}
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

```ts theme={null}
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>`:

```ts theme={null}
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

```ts theme={null}
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

```ts theme={null}
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`:

```ts theme={null}
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:

```ts theme={null}
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:

```ts theme={null}
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:

```ts theme={null}
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:

```ts theme={null}
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 tests** — `pnpm 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 snapshot** — `tests/unit/barrel-snapshot.test.ts` pins the exact set of exported names. Accidental removals fail the build.
* **Wire-shape contract** — `tests/contract/wire-shape.test.ts` asserts every resource schema matches canonical fixtures. Ships to the public mirror.

## Source & changelog

* npm: [`@kashdao/sdk`](https://www.npmjs.com/package/@kashdao/sdk)
* GitHub: [`KashDAO/sdk-typescript`](https://github.com/KashDAO/sdk-typescript) (public mirror)
* Changelog: [`CHANGELOG.md`](https://github.com/KashDAO/sdk-typescript/blob/main/CHANGELOG.md)
* Versioned OpenAPI snapshots: the live spec is served at [`api.kash.bot/v1/openapi.json`](https://api.kash.bot/v1/openapi.json); historical pinned versions are listed at [`api.kash.bot/v1/openapi/index.json`](https://api.kash.bot/v1/openapi/index.json) — pin against a specific date for reproducible code generation.
* Issues / discussions: [github.com/KashDAO/sdk-typescript/issues](https://github.com/KashDAO/sdk-typescript/issues)

## Next

<CardGroup cols={2}>
  <Card title="CLI" icon="terminal" href="/developer-docs/cli/overview">
    `kash` — the command-line interface. Same primitives, scripting-friendly.
  </Card>

  <Card title="Endpoint Reference" icon="book" href="/developer-docs/rest-api/endpoint-reference">
    The full live spec at `/v1/docs`.
  </Card>
</CardGroup>
