> ## Documentation Index
> Fetch the complete documentation index at: https://ara-90a60a07.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a chat UI on the API

> Create a Reason Session over HTTP, poll its events, send follow-ups and cancel, on your computer or in the cloud, with a TypeScript sample.

The HTTP API creates Sessions, reads their events and sends follow-ups. Use it when your own product shows the conversation. This page builds a small TypeScript client that starts a Session on your computer, follows it, and sends a follow-up.

## What you will have

* A script that creates a Session on a computer you own, or in the cloud if none is connected.
* A printed transcript of the agent's replies, read from `/events`.
* A follow-up message in the same Session.

## Prerequisites

```bash theme={null}
curl -fsSL https://reasonmachines.com/install.sh | bash
reason login
reason connect                       # makes this computer a target
```

Create an API key in **Settings > API** with the scopes below, and export it. Do not paste the key into a file.

```bash theme={null}
export REASON_API_KEY=reason_...
```

| Scope | Why |
| - | - |
| `run` | Create, send, cancel |
| `sessions:read` | Read the Session and its events |
| `devices:read` | List your computers as targets |
| `devices:use` | Run work on a computer you connected |

See [Authentication and scopes](https://ara-90a60a07.mintlify.site/docs/build/auth-and-scopes.md).

## Base URL and calls

Every call goes to `https://api.reasonmachines.com/v3`, with `Authorization: Bearer $REASON_API_KEY`. Get your organization ID once from `GET /v3/self` (the field is `org_id`).

| Step | Call |
| - | - |
| Discover your organization | `GET /v3/self` |
| List your computers | `GET /v3/organizations/{orgId}/devices` |
| Create a Session | `POST /v3/organizations/{orgId}/sessions` |
| Read status and events | `GET /v3/organizations/{orgId}/sessions/{id}` and `.../events` |
| Send a follow-up | `POST /v3/organizations/{orgId}/sessions/{id}/messages` with body `{"message": "..."}` |
| Cancel | `POST /v3/organizations/{orgId}/sessions/{id}/cancel` with optional body `{"mode": "graceful"}` or `"hard"` |

## Where the Session runs

The `target` field selects the computer:

* `{"kind": "headless", "device_id": "...", "cwd": "/path"}` runs on a computer you connected with `reason connect`. Get `device_id` from the devices list. Use `cwd` to choose the folder.
* `{"kind": "cloud"}` runs on a Reason cloud computer.
* Omit `target` and it runs in the cloud.

Creating a Session returns at once. The Session runs in the background, so poll it.

## Sample

Save as `reason-chat.ts` and run it with `bun reason-chat.ts`.

```ts theme={null}
export {};

const BASE = "https://api.reasonmachines.com/v3";
const KEY = process.env.REASON_API_KEY;
if (!KEY) throw new Error("Set REASON_API_KEY first.");

type Device = {
  id: string;
  name: string;
  kind: "mac" | "headless";
  online: boolean;
  roots: { id: string; label: string }[];
};
type SessionEvent = {
  id: string;
  seq: number;
  position: number;
  type: string;
  text: string | null;
};
type EventsPage = {
  session: { status: "running" | "exit" | "error" | "suspended"; outcome: string | null };
  items: SessionEvent[];
  next_seq: number;
};

async function api<T>(path: string, init: RequestInit = {}): Promise<T> {
  const response = await fetch(`${BASE}${path}`, {
    ...init,
    headers: {
      authorization: `Bearer ${KEY}`,
      "content-type": "application/json",
      ...(init.headers as Record<string, string> | undefined),
    },
  });
  const body = await response.json().catch(() => ({}));
  if (!response.ok) {
    throw new Error(`${init.method ?? "GET"} ${path} failed: ${response.status} ${JSON.stringify(body)}`);
  }
  return body as T;
}

const { org_id: orgId } = await api<{ org_id: string }>("/self");

// 1. Pick a connected computer you own, or fall back to the cloud.
const { devices } = await api<{ devices: Device[] }>(`/organizations/${orgId}/devices?first=50`);
const computer = devices.find((device) => device.online && device.kind === "headless");
const target = computer
  ? { kind: "headless", device_id: computer.id, cwd: process.cwd() }
  : { kind: "cloud" };

// 2. Create the Session. It returns at once and runs in the background.
const created = await api<{ session_id: string; url: string }>(`/organizations/${orgId}/sessions`, {
  method: "POST",
  body: JSON.stringify({
    prompt: "Run pwd and report the result. Do not change any files.",
    target,
  }),
});
const sessionId = created.session_id;
console.log(`Session ${sessionId} on ${computer ? computer.name : "the cloud"}: ${created.url}`);

// 3. Poll events with the cursor, keeping the highest seq per event id.
async function follow(id: string): Promise<Map<string, SessionEvent>> {
  const events = new Map<string, SessionEvent>();
  let after = 0;
  let status: EventsPage["session"]["status"] = "running";
  while (status === "running") {
    const page = await api<EventsPage>(
      `/organizations/${orgId}/sessions/${id}/events?after_seq=${after}&wait_ms=20000`,
    );
    for (const event of page.items) {
      const previous = events.get(event.id);
      if (!previous || event.seq > previous.seq) events.set(event.id, event);
    }
    after = page.next_seq;
    status = page.session.status;
  }
  return events;
}

function printReplies(events: Map<string, SessionEvent>) {
  const ordered = [...events.values()].sort((a, b) => a.position - b.position);
  for (const event of ordered) {
    if (event.type === "assistant" && event.text) console.log(`\n${event.text}`);
  }
}

printReplies(await follow(sessionId));

// 4. Send a follow-up in the same Session and follow again.
await api(`/organizations/${orgId}/sessions/${sessionId}/messages`, {
  method: "POST",
  body: JSON.stringify({ message: "Now list the files in this folder." }),
});
printReplies(await follow(sessionId));
```

## Verify

* Run the script. It prints the Session URL, then the `pwd` reply, then the follow-up reply.
* Open the URL. The same Session shows the same two turns in the Reason app.
* Check the exit status: a finished Session has status `exit` and outcome `success`. Treat any other outcome as not done.

To stop a Session early, send `POST .../cancel` with `{"mode": "graceful"}`.

## Notes

* Events can repeat across pages. The sample keeps the highest `seq` per `id` and orders by `position`. [Events reference](https://ara-90a60a07.mintlify.site/docs/build/events-reference.md) explains why.
* The sample reads `assistant` events only. Tool calls and reasoning are in the same feed with other `type` values.
* Pending: a public TypeScript SDK with event long polling is in progress. Until it ships, use the calls above.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.