# Build with ChatWithHuman.ai

> Everything you need to integrate human escalation into your AI agent: REST API, live event stream, and copy-paste examples.

Machine-readable spec of the implemented routes: https://chatwithhuman.chrismai.ai/openapi.json (OpenAPI 3.1). Full text for LLMs: https://chatwithhuman.chrismai.ai/llms-full.txt.

## Quickstart

1. **Get your API key** — Sign up and generate a key from your dashboard (/dashboard/keys). Keys are prefixed cwh_live_, shown once, and can be revoked at any time.
2. **Escalate a session** — POST to /api/sessions with your agent_id and context. Add "tier": "priority" for guaranteed fast response at $25/min (Standard is $10/min). ChatWithHuman.ai matches the session to an available human expert.
3. **Stream the reply** — Open GET /api/sessions/{id}/events (Server-Sent Events) to receive status changes and the human's messages, then resume your agent flow. Webhooks are planned.

## API reference

Base URL: `https://chatwithhuman.ai/api`

### `POST /sessions`

Create a new escalation session. Returns a session_id, estimated wait time, and the tier and rate that apply.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `agent_id` | string | yes | Your registered agent identifier. |
| `context` | string | no | Plain-text summary of the issue for the human expert. Strongly recommended. |
| `tier` | "standard" \| "priority" | no | Pricing tier for this session. Defaults to standard ($10/min); priority is $25/min with guaranteed fast human response. Legacy priority: "high" also selects priority. |
| `webhook_url` | string | no | Stored with the session. Webhook delivery is planned, not live — use the event stream. |
| `metadata` | object | no | Arbitrary key-value pairs, echoed back in the response. |

```json
{
  "session_id": "ses_x9k2m",
  "status": "queued",
  "eta_seconds": 12,
  "billing_started": false,
  "tier": "standard",
  "rate_cents_per_minute": 1000,
  "metadata": {}
}
```

### `GET /sessions/{id}`

Retrieve the current state of a session, including its tier and full message transcript.

```json
{
  "id": 42,
  "sessionId": "ses_x9k2m",
  "agentId": "agent_abc123",
  "status": "connected",
  "priority": "normal",
  "tier": "standard",
  "context": "User is asking about a refund for order #8821",
  "webhookUrl": null,
  "operatorId": "operator_1",
  "billingStartedAt": "2026-10-14T21:18:04.000Z",
  "closedAt": null,
  "createdAt": "2026-10-14T21:17:52.000Z",
  "userId": "usr_\u2026",
  "messages": [
    {
      "id": 7,
      "sessionId": "ses_x9k2m",
      "role": "human",
      "content": "Hi \u2014 I can help with that refund.",
      "createdAt": "2026-10-14T21:18:10.000Z"
    }
  ]
}
```

### `POST /sessions/{id}/messages`

Send a message into a session. Agents should pass role "agent".

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `content` | string | yes | The message text to send. |
| `role` | "agent" \| "human" \| "system" | no | Defaults to human — set "agent" when your agent is speaking. |

```json
{
  "message_id": "msg_7",
  "created_at": "2026-10-14T21:18:04.000Z"
}
```

### `GET /sessions/{id}/events`

Server-Sent Events stream: status changes, new messages, and a ping every 20 s.

```json
data: {"type":"status","status":"connected"}

data: {"type":"message","message":{"id":7,"role":"human","content":"Hi — I can help with that refund.", …}}

data: {"type":"ping"}
```

### `PATCH /sessions/{id}`

Close a session with {"status": "closed"}. Billing stops and an invoice is created (whole minutes × tier rate, 1-minute minimum, no platform fee). Returns the updated session.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `status` | "connected" \| "closed" | yes | "closed" ends the session; "connected" is set when an operator joins. |
| `operator_id` | string | no | Operator joining the session (with status "connected"). |

```json
{
  "sessionId": "ses_x9k2m",
  "status": "closed",
  "priority": "normal",
  "billingStartedAt": "2026-10-14T21:18:04.000Z",
  "closedAt": "2026-10-14T21:20:18.000Z",
  "\u2026": "\u2026"
}
```

### `GET /billing/usage`

Invoices plus all-time and 30-day totals for your account (signed-in session). Filter by agent.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `agentId` | string (query) | no | Only this agent's invoices. |
| `limit / offset` | integer (query) | no | Pagination; defaults 20 / 0. |

```json
{
  "invoices": [
    {
      "invoiceId": "inv_3f9a\u2026",
      "sessionId": "ses_x9k2m",
      "totalMinutes": 2,
      "ratePerMinute": "10.0000",
      "subtotalCents": 2000,
      "platformFeeCents": 0,
      "totalCents": 2000,
      "currency": "usd",
      "status": "pending"
    }
  ],
  "totals": {
    "totalSessions": 47,
    "totalMinutes": 312,
    "totalCents": 312000
  },
  "last30Days": {
    "sessions": 12,
    "minutes": 80,
    "revenueCents": 80000
  }
}
```

## Webhooks (planned)

Not live yet: the server stores webhook_url but does not deliver events. Today, consume the same events from GET /api/sessions/{id}/events (Server-Sent Events). The planned events are listed below; signing will be documented when delivery ships.

| Event | Description |
| --- | --- |
| `session.queued` | Session created and waiting for a human expert. |
| `session.connected` | Human expert joined. Billing starts. |
| `session.message` | New message from the human expert. |
| `session.closed` | Session ended. Includes final duration and cost. |

```js
// Live today: stream session events over SSE
const res = await fetch(`https://chatwithhuman.ai/api/sessions/${sessionId}/events`, {
  headers: { Authorization: `Bearer ${process.env.CHATWITHHUMAN_API_KEY}` },
});
for await (const chunk of res.body) {
  // data: {"type":"message","message":{...}}
  process.stdout.write(chunk);
}
```

## API keys

Keys are prefixed cwh_live_ and shown once at creation. Sandbox (test) keys are planned; today every session is matched to real humans and billed.

- Store keys in environment variables — never commit them to source control.
- Rotate by creating a new key, deploying it, then revoking the old one (DELETE /api/keys/{keyId}).
- Scope keys per agent to isolate usage and billing.

## SDKs

| SDK | Install | Status |
| --- | --- | --- |
| Node.js | `npm install @chatwithhuman/node` | planned |
| Python | `pip install chatwithhuman` | planned |
| Go | `go get github.com/chatwithhuman/go-sdk` | planned |
| REST | `No install required` | stable |

## Implemented routes (current server)

| Method | Path | Summary |
| --- | --- | --- |
| POST | `/api/sessions` | Escalate: create a session (queued for a human) |
| GET | `/api/sessions` | List sessions (operator queue) |
| GET | `/api/sessions/history` | Search past sessions with duration and invoice |
| GET | `/api/sessions/{id}` | Get a session with its full transcript |
| PATCH | `/api/sessions/{id}` | Change session status (connect = billing starts, close = invoice) |
| POST | `/api/sessions/{id}/messages` | Send a message into a session |
| GET | `/api/sessions/{id}/events` | Server-Sent Events stream for a session |
| GET | `/api/operators` | List human operators |
| PATCH | `/api/operators/status` | Set an operator online/offline |
| GET | `/api/schedule` | Bookings, operators and availability |
| POST | `/api/schedule` | Book a session with an operator in advance |
| GET | `/api/keys` | List the signed-in user's API keys (hash never returned) |
| POST | `/api/keys` | Create an API key (raw key returned once, prefix cwh_live_) |
| DELETE | `/api/keys/{keyId}` | Revoke an API key |
| GET | `/api/billing/usage` | Invoices plus all-time and 30-day totals |
| POST | `/api/billing/meter` | Record a billing event for a session |
| GET | `/api/billing/invoices/{invoiceId}` | Invoice with its billing events |
| POST | `/api/billing/invoices/{invoiceId}/charge` | Charge an invoice to the signed-in user's saved card (Stripe) |
| GET | `/api/stripe/config` | Whether payments are configured (publishable key, mode) |
| GET | `/api/stripe/products` | Active Stripe products with their default price |
| POST | `/api/stripe/create-checkout-session` | Create a Stripe Checkout session (single price or cart) |
| GET | `/api/stripe/session/{sessionId}` | Verify a Checkout session after redirect |
| POST | `/api/stripe/webhook` | Stripe webhook receiver (verifies stripe-signature) |
| GET | `/api/auth/{path}` | better-auth GET handler (e.g. get-session) |
| POST | `/api/auth/{path}` | better-auth POST handler (sign-in, sign-up, sign-out) |
| GET | `/api/sales-chat` | Sales chat mode (llm \| scripted), lead fields, handoff contact and limits |
| POST | `/api/sales-chat` | Send one message to the AI sales assistant (lead qualification + human handoff) |
| POST | `/api/leads` | Submit a sales lead directly (for agents and forms) |
| GET | `/api/admin/leads/{leadId}` | Full lead + transcript (admins only; linked from lead alert emails) |
| GET | `/api/health` | Liveness probe |
| GET | `/api/status` | Service health checks (API, Database, Authentication, Chat Sessions, Webhook Handler) |

## MCP (planned)

There is no MCP server yet. The descriptor at /.well-known/mcp.json documents the intended shape so agent runtimes can plan for it. Until then, use the REST API in /openapi.json.

| Planned tool | Maps to | Arguments |
| --- | --- | --- |
| escalate_to_human | `POST /api/sessions` | agent_id, context, priority?, metadata? |
| send_message | `POST /api/sessions/{id}/messages` | session_id, content |
| get_session | `GET /api/sessions/{id}` | session_id |
| close_session | `PATCH /api/sessions/{id}` | session_id (status: "closed") |
