> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.rydhm.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.rydhm.ai/_mcp/server.

# TypeScript SDK

`@rydhm/sdk` is the TypeScript client for the Rydhm API. It is generated from the API's OpenAPI document, so its types match what the API accepts and returns.

> **Note**
>
> The SDK isn't published to npm yet. Until it is, call the API over HTTP: every example in these docs has a curl version, and the [API reference](/api-reference) shows each request in TypeScript.

## Install

```bash
npm install @rydhm/sdk
```

The SDK uses the platform's `fetch`. Run it on your server, never in a browser: it needs your API key.

## Initialize

```typescript
import { RydhmClient } from "@rydhm/sdk";

const client = new RydhmClient({ apiKey: process.env.RYDHM_API_KEY });
```

If you pass no `apiKey`, the client reads `RYDHM_API_KEY` from the environment.

| Option             | Default                       | What it does                                                                                                                            |
| ------------------ | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `apiKey`           | `RYDHM_API_KEY`               | Your API key, `rydhm_live_…`.                                                                                                           |
| `environment`      | `RydhmEnvironment.Production` | `https://api.rydhm.ai`.                                                                                                                 |
| `baseUrl`          |                               | Another address for the API, such as your own proxy. Give the host only, without `/v1`: the SDK adds the path. Overrides `environment`. |
| `timeoutInSeconds` | `60`                          | How long a request may take.                                                                                                            |
| `maxRetries`       | `2`                           | How many times a failed request is retried. See [Retries](#retries).                                                                    |
| `fetch`            | global `fetch`                | A `fetch` to use instead.                                                                                                               |

## How methods are named

Methods are grouped by resource: `client.experts`, `client.knowledge`, `client.conversations`, `client.chat`, `client.voice`, `client.meetings`, `client.tools`, `client.analytics`. Path parameters come first, in order. Query parameters and the body go together in one request object after them:

```typescript
await client.experts.get(expertId);                          // GET /v1/agents/{id}
await client.knowledge.search(expertId, { q: "refunds" });    // GET /v1/agents/{id}/kb/search?q=refunds
await client.chat.send(conversationId, { content: "Hi" });    // POST /v1/chat/conversations/{id}/messages
```

Every method needs a scope on your key. See [Scopes](/developers/scopes).

## Common tasks

### List experts

```typescript
const experts = await client.experts.list();
for (const expert of experts) console.log(expert.id, expert.name, expert.visibility);

const expert = await client.experts.get(experts[0].id);
```

Scope: `experts:read`.

### Start a chat and send a message

```typescript
const conversation = await client.chat.start({ agentId: expertId });
const message = await client.chat.send(conversation.id, {
  content: "What can you help with?",
});
```

`chat.send` stores your message. Read the expert's reply from the stream. Scope: `chat:write`.

### Stream the reply

```typescript
const stream = await client.chat.stream(conversation.id);

let reply = "";
for await (const frame of stream) {
  switch (frame.type) {
    case "sources":
      console.log(`Grounded in ${frame.count} passages`);
      break;
    case "delta":
      reply += frame.content;
      process.stdout.write(frame.content);
      break;
    case "error":
      throw new Error(frame.message);
    case "done":
      break;
  }
}
```

Each frame is typed by its `type`: `sources`, `delta`, `tool_call`, `tool_result`, `message`, `done` or `error`. See [Stream the reply](/developers/overview#stream-the-reply).

To continue the conversation, call `chat.send` and `chat.stream` again with the same id.

### Read a conversation

```typescript
const messages = await client.conversations.get(conversation.id);
for (const m of messages) console.log(`${m.role}: ${m.content}`);

const recent = await client.conversations.list({ agentId: expertId }); // the 50 most recent
```

Scope: `conversations:read`.

### Search knowledge

```typescript
const hits = await client.knowledge.search(expertId, { q: "refund policy" });
for (const hit of hits) console.log(hit.score.toFixed(2), hit.text.slice(0, 80));
```

Scope: `knowledge:read`. `mode` (`hybrid` or `vector`) is optional.

### Add a document

```typescript
const doc = await client.knowledge.add(expertId, {
  filename: "returns.md",
  mimeType: "text/markdown",
  content: "# Returns\n\nItems can be returned within 30 days.",
});
console.log(doc.status); // "ready": indexed before the call returns
```

Scope: `knowledge:write`. For a PDF, Word, Markdown, text or CSV file, use `client.knowledge.addFile(expertId, { filename, data })` with the file as base64.

### Send an expert to a meeting

```typescript
const meeting = await client.meetings.join({
  "Idempotency-Key": "crm-event-4821",
  agentId: expertId,
  joinUrl: "https://meet.google.com/abc-defg-hij",
  mode: "speaker",
});
```

Scope: `meetings:join`. Always pass an idempotency key: a retry with the same key returns the first result instead of sending a second bot.

## Errors

A non-`2xx` response throws. Each common status has its own class, and all of them extend `RydhmError`:

```typescript
import { Rydhm, RydhmClient, RydhmError } from "@rydhm/sdk";

try {
  await client.experts.create({ name: "Support" });
} catch (err) {
  if (err instanceof Rydhm.ForbiddenError) {
    console.log(err.body.error);         // "insufficient_scope"
    console.log(err.body.missingScopes); // ["experts:write"]
  } else if (err instanceof RydhmError) {
    console.log(err.statusCode, err.body);
  } else {
    throw err;
  }
}
```

| Class                        | Status                                                                               |
| ---------------------------- | ------------------------------------------------------------------------------------ |
| `Rydhm.UnauthorizedError`    | `401`                                                                                |
| `Rydhm.PaymentRequiredError` | `402`                                                                                |
| `Rydhm.ForbiddenError`       | `403`                                                                                |
| `Rydhm.ConflictError`        | `409`, such as `code: "stale_edit"` when someone saved an expert after you loaded it |
| `Rydhm.TooManyRequestsError` | `429`                                                                                |
| `RydhmError`                 | Any other non-`2xx` status                                                           |
| `RydhmTimeoutError`          | The request took longer than the timeout                                             |

`err.body` is the API's [error body](/developers/errors), with `error` (or `code`), `message` and `requestId`.

## Retries

By default the SDK retries a request up to 2 times, with exponential backoff, when the response is `408`, `429` or any `5xx`. Change it for the whole client with `maxRetries`, or for one call:

```typescript
await client.chat.start({ agentId: expertId }, { maxRetries: 0 });
```

> **Warning**
>
> Retries also apply to calls that start paid work. Always pass an `Idempotency-Key` to `meetings.join`, so a retried request can't send a second bot.

## Headers and request ids

`.withRawResponse()` gives you the response headers too, such as the request id and rate-limit state:

```typescript
const { data, rawResponse } = await client.chat
  .start({ agentId: expertId })
  .withRawResponse();

console.log(rawResponse.headers.get("x-request-id"));
console.log(rawResponse.headers.get("x-ratelimit-remaining"));
```

Every method also takes request options as its last argument: `timeoutInSeconds`, `maxRetries`, `abortSignal` and extra `headers`.