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

# Developer overview

The Rydhm API gives your code the same experts your team uses in the app. Everything is scoped to one workspace and authenticated with an API key.

## What you can build

#### [Chat from your backend](/developers/overview#chat-with-an-expert)

Start a conversation with any expert, private ones included, and stream its replies.

#### [Voice in your own app](/developers/overview#voice-in-your-own-frontend)

Mint a voice-session token on your server and connect from your frontend.

#### [Experts in meetings](/developers/overview#send-an-expert-to-a-meeting)

Send an expert into a Google Meet, Zoom or Teams call, then read the transcript and summary.

#### [Knowledge in sync](/api-reference)

Add, search and remove an expert's documents from your own content pipeline.

#### [React to events](/developers/webhooks)

Get signed webhooks when meetings end, summaries are ready or documents finish processing.

#### [Use experts from AI tools](/developers/mcp)

Connect Claude, Cursor and other MCP clients to your experts.

#### [Embed on your website](/developers/embed)

Add the chat and voice widget to your site with one tag.

#### [Talk to other agents](/developers/a2a)

Publish an Agent Card and let other agents talk to public experts.

## Base URLs

| What                                 | URL                                            |
| ------------------------------------ | ---------------------------------------------- |
| The API                              | `https://api.rydhm.ai/v1`                      |
| The MCP endpoint (not under `/v1`)   | `https://api.rydhm.ai/mcp/experts/{expert id}` |
| The OpenAPI document (no key needed) | `https://api.rydhm.ai/v1/openapi.json`         |

**Developers → API reference** in the app shows the address for your workspace. To call the API through another host, such as your own proxy, set `baseUrl` (TypeScript) or `base_url` (Python) on the SDK client.

> **Note**
>
> **Rydhm is in early access.** Until launch, workspaces on `staging.rydhm.ai` use `https://api-staging.rydhm.ai` in place of `https://api.rydhm.ai`, for the API, the MCP endpoint and the OpenAPI document alike. The examples on this site, and **Try it** in the API Reference, use the launch address. The snippets in the app's **Developers** pages already use the right one.

> **Note**
>
> The API calls experts **agents** in its paths and fields: `/v1/agents/{id}` is an expert, and `agentId` is an expert's id.

### Requests and responses

* Send JSON bodies with `Content-Type: application/json`. A body can be up to 12 MB, which matters for file uploads sent as base64.
* Ids are UUIDs. Times are ISO 8601 strings in UTC.
* Every response carries an `X-Request-Id` header. Quote it to support. See [Errors](/developers/errors).
* Errors share one JSON shape, and rate-limited routes send `X-RateLimit-*` headers. See [Errors](/developers/errors).

## Versioning

The version is in the path: every route is under `/v1`. The OpenAPI document also carries a date version in `info.version` (currently `2026-11-09`).

New fields and new routes can appear within `v1`. Build your client to ignore fields it doesn't know.

## Your first request

You need to be a workspace admin to create a key.

#### Create a key

In the app, go to **Developers → API keys** and choose **Create API key**. Name it, set **Experts** under **Permissions** to **Read**, and choose **Create key**. Copy the key: it is shown once.

#### Store it

Put the key in an environment variable on your machine:

```bash
export RYDHM_API_KEY="rydhm_live_…"
```

#### List your experts

**`curl`**

```bash title="curl"
curl https://api.rydhm.ai/v1/agents \
  -H "Authorization: Bearer $RYDHM_API_KEY"
```

**`TypeScript`**

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

const client = new RydhmClient(); // reads RYDHM_API_KEY
const experts = await client.experts.list();
console.log(experts.map((e) => `${e.id} ${e.name}`));
```

**`Python`**

```python title="Python"
from rydhm import Rydhm

client = Rydhm()  # reads RYDHM_API_KEY
for expert in client.experts.list():
    print(expert.id, expert.name)
```

The response is a list of experts:

```json
[
  {
    "id": "7b08028d-0a4c-47ee-a91e-4b8d65c43cd1",
    "orgId": "594284c3-1ee2-482e-8f1c-209fb6539a4b",
    "name": "Ava",
    "description": "Answers product questions and walks prospects through features.",
    "firstMessage": "Hi, I'm Ava. What can I help you with?",
    "isActive": true,
    "visibility": "unlisted",
    "slug": "ava-k8nzvywsyuhw",
    "createdAt": "2026-10-26T16:25:46.372Z",
    "updatedAt": "2026-11-09T14:32:34.155Z"
  }
]
```

Each expert also carries its full settings (`personaPrompt`, `voiceConfig`, `widgetConfig` and more). Secrets in its settings are masked. Keep one expert's `id`: the next sections use it.

The SDKs aren't published to npm or PyPI yet. See [TypeScript SDK](/developers/sdk-typescript) and [Python SDK](/developers/sdk-python).

## Chat with an expert

A chat takes three calls: start a conversation, send a message, then read the reply as a stream. The key needs `chat:write`.

**`curl`**

```bash title="curl"
# 1. Start a conversation
curl -X POST https://api.rydhm.ai/v1/chat/conversations \
  -H "Authorization: Bearer $RYDHM_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"agentId\":\"$EXPERT_ID\"}"
# → {"id":"e153628d-…","agentId":"…","kind":"text","channel":"api",…}

# 2. Send a message
curl -X POST https://api.rydhm.ai/v1/chat/conversations/$CONVERSATION_ID/messages \
  -H "Authorization: Bearer $RYDHM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content":"What can you help with?"}'

# 3. Stream the reply
curl -N https://api.rydhm.ai/v1/chat/conversations/$CONVERSATION_ID/stream \
  -H "Authorization: Bearer $RYDHM_API_KEY" \
  -H "Accept: text/event-stream"
```

**`TypeScript`**

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

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

const stream = await client.chat.stream(conversation.id);
for await (const frame of stream) {
  if (frame.type === "delta") process.stdout.write(frame.content);
  if (frame.type === "error") throw new Error(frame.message);
}
```

**`Python`**

```python title="Python"
from rydhm import Rydhm

client = Rydhm()
conversation = client.chat.start(agent_id=expert_id)
client.chat.send(conversation.id, content="What can you help with?")

for frame in client.chat.stream(conversation.id):
    if frame.type == "delta":
        print(frame.content, end="", flush=True)
    elif frame.type == "error":
        raise RuntimeError(frame.message)
```

To continue the conversation, send the next message and stream again. To read it later, call `GET /v1/chat/conversations/{id}/messages` (needs `conversations:read`).

To ground a chat in a meeting's transcript, pass `meetingId` when you start it.

### Stream the reply

The stream is [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). Each event's `data` is one JSON frame:

```text
id: 1
data: {"type":"sources","count":6,"items":[{"documentId":"cee182e0-…","filename":"pricing.md","kind":"text","sourceUrl":null,"ord":0}]}

id: 2
data: {"type":"delta","content":"I help teams "}

id: 3
data: {"type":"delta","content":"understand pricing."}

id: 4
data: {"type":"message","id":"65acd98a-…","role":"agent","content":"I help teams understand pricing."}

id: 5
data: {"type":"done"}
```

| Frame         | What it carries                                                                                     |
| ------------- | --------------------------------------------------------------------------------------------------- |
| `sources`     | How many knowledge passages ground the reply (`count`), and which documents (`items`). Comes first. |
| `delta`       | The next piece of the reply's text (`content`). Append them in order.                               |
| `tool_call`   | A tool the expert decided to call (`id`, `label`, `args`), sent before it runs.                     |
| `tool_result` | The tool's outcome (`id`, `ok`, `ms`, and a `preview` of up to 400 characters).                     |
| `message`     | The whole reply as stored (`id`, `role`, `content`).                                                |
| `done`        | The reply is complete. The stream ends.                                                             |
| `error`       | The reply failed (`message`). The stream ends.                                                      |

Each reply is a model call, metered as chat usage.

## Voice in your own frontend

Your server mints a short-lived voice-session token; your frontend uses it to join the call with the [LiveKit client SDK](https://docs.livekit.io/reference/client-sdk-js/). The key never leaves your server. The key needs `voice_sessions:write`.

```bash
curl -X POST https://api.rydhm.ai/v1/voice/sessions \
  -H "Authorization: Bearer $RYDHM_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"agentId\":\"$EXPERT_ID\",\"variables\":{\"name\":\"Priya\"}}"
```

```json
{
  "serverUrl": "wss://…",
  "roomName": "…",
  "participantToken": "eyJhbGciOi…"
}
```

Join with `serverUrl` and `participantToken` within 15 minutes. The body can also carry `conversationId`, to continue an earlier conversation by voice, and `variables`, values for the expert's `{{variables}}` (up to 20).

## Send an expert to a meeting

`POST /v1/meetings` sends an expert into a Google Meet, Zoom or Microsoft Teams call, now or at `scheduledAt`. It needs its own scope, `meetings:join`, because it costs money and joins calls with people outside your workspace.

```bash
curl -X POST https://api.rydhm.ai/v1/meetings \
  -H "Authorization: Bearer $RYDHM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-event-4821" \
  -d "{\"agentId\":\"$EXPERT_ID\",\"joinUrl\":\"https://meet.google.com/abc-defg-hij\",\"mode\":\"speaker\"}"
```

* `joinUrl` is the meeting link. A whole pasted invite works too; the link is taken from it.
* `mode` is `speaker` (the expert talks) or `listener` (it only listens).
* `brief` tells the expert what it should know going in.

Always send an `Idempotency-Key`. A retry with the same key returns the first result instead of sending a second bot. Keys are kept for 24 hours, per workspace and route; a replayed response carries `Idempotent-Replayed: true`.

Read the meeting, its transcript and its summary with `GET /v1/meetings/{id}` (needs `meetings:read`), or subscribe to the `meeting.*` [webhooks](/developers/webhooks).

## Next steps

* [Authentication](/developers/authentication): keys, restrictions and rotation.
* [Scopes](/developers/scopes): what each permission allows.
* [Errors](/developers/errors): the error shape and rate limits.
* The [API reference](/api-reference): every route, with its scope.