# Use your own AI with Throughway

Let Claude, ChatGPT or an AI running on your own computer read your sessions, notes and clients, so it can write reports, brief you before a call and log notes for you.

## Is an AI reading this for you?

Give it the plain-text version: the same page, written for it. [/docs/ai.md](https://staging.throughway.app/docs/ai.md) · [openapi.json](https://staging.throughway.app/api/v1/openapi.json)

## Connected in three steps

### 1. Connect your AI

#### Claude

1. In Claude (claude.ai, the desktop app or the phone app), open Settings › Connectors and choose Add custom connector.
2. Name it “Throughway”, paste this address and choose Connect:

   ```
   https://staging.throughway.app/api/v1/mcp
   ```
3. Throughway opens. Sign in and choose Read only or Read and log notes.

Once added it works in Claude on the web, on your desktop and on your phone.

#### ChatGPT

1. In ChatGPT, open Settings › Plugins, choose Add, then Add custom MCP server.
2. Name it “Throughway”, paste this address as the Server URL and choose OAuth:

   ```
   https://staging.throughway.app/api/v1/mcp
   ```
3. Accept the warning, choose Create as a plugin, then Install.
4. Sign in to Throughway and choose Read only or Read and log notes.

Until Throughway is listed in ChatGPT's plugin directory, you add it yourself. Custom plugins need a paid plan, and some workplaces turn them off.

#### AI on your computer

1. Ollama, LM Studio and other AIs on your own computer can't do a browser sign-in, so they use a key. Mint a Read only key in [Settings › Your AI](https://staging.throughway.app/settings/your-ai?ai=local).
2. Then run the script in the weekly report recipe below.

Your transcripts never leave your machine.

#### Claude Code

1. In a terminal, run:

   ```
   claude mcp add --transport http throughway https://staging.throughway.app/api/v1/mcp
   ```
2. In Claude Code, run /mcp, choose throughway, then Authenticate and sign in to Throughway.

### 2. Sign in and choose what it may do

**Read only** for reports and briefings. **Read and log notes** if you want it to add notes for you. You can disconnect it at any time.

### 3. Ask it something

“Summarise my sessions with Priya Raman this month and what she committed to.”

## Things to ask your AI

<a id="weekly-report"></a>
## A weekly report from a local LLM

In Claude or ChatGPT, paste this:

> Using Throughway, read my sessions from the last 7 days, with summaries. Write my weekly coaching report: one paragraph per client, the themes across clients, and anything I promised to follow up on.

With an AI on your own computer (Ollama, LM Studio), where transcripts never leave your machine, you or a technical friend can run this script:

```python
import requests, ollama
from datetime import datetime, timedelta, timezone
API = "https://staging.throughway.app/api/v1"
H = {"Authorization": "Bearer trw_…"}
since = (datetime.now(timezone.utc) - timedelta(days=7)).strftime("%Y-%m-%dT%H:%M:%SZ")
rows, cursor = [], None
while True:
    page = requests.get(f"{API}/sessions", headers=H, params={
        "started_since": since, "include": "transcript", "cursor": cursor}).json()
    rows += page["data"]
    if not (cursor := page["next_cursor"]): break
report = ollama.generate(model="llama3.1",
    prompt="Write my weekly coaching report:\n" + str(rows))
print(report["response"])
```

Transcripts are long. If summaries are enough, leave `include` out and the run is much faster.

<a id="prep"></a>
## Prep for tomorrow's calls

In Claude or ChatGPT, paste this:

> Look at my calendar for tomorrow. For each client I'm meeting, use Throughway to read our last two sessions and my recent notes, then give me a one-page briefing: where we left off, what they committed to, and what to ask.

Your AI also needs to see your calendar (for example Claude's Google Calendar connector). Throughway gives it the history, not the schedule.

<a id="log-notes"></a>
## Log notes from your inbox

In Claude or ChatGPT, paste this:

> Find the email from Priya Raman about her promotion and log a note on her Throughway client with the gist and any dates.

Your AI needs Read and log notes access, plus its own email connector.

For a voice memo, paste the transcript and ask it to log a note on the right client. Throughway never reads your email for your AI.

<a id="pipeline-sheet"></a>
## Sync your pipeline to a sheet

In Claude or ChatGPT, paste this:

> Using Throughway, list my clients with their stage and last contact, and put them in a Google Sheet called Pipeline. Update the sheet if it already exists.

Your AI also needs its Google Drive or Sheets connector.

## For developers

### API reference

Sign in with OAuth (chat AIs) or send an API key as a bearer token (scripts) · JSON · lists page with `cursor` · errors as `{"error": {"code", "message"}}`.

### Clients

A person you coach, or might. Everything in the pipeline is a client.

- `GET /api/v1/clients` — List clients, filtered and paginated.
- `POST /api/v1/clients` — Create a client.
- `GET /api/v1/clients/{id}` — Fetch one client.
- `PATCH /api/v1/clients/{id}` — Update a client. Only the fields you send change.
- `GET /api/v1/clients/{id}/timeline` — Everything that has happened with a client, newest first — notes, sessions, stage changes, emails, files.

| Field | Type | Description |
| --- | --- | --- |
| `id` | integer | Throughway's identifier for this client. |
| `name` | string | The client's full name, as you'd write it on a card. Writable. |
| `stage` | string | Where the client sits in the pipeline. Changing it records a stage transition. Null means the person is in the book only, off the board. Writable. One of: nurturing, in_conversation, chemistry_scheduled, deciding, onboarding, active, complete. |
| `email` | string | The client's email address. Writable. |
| `organization` | string | The organization the client works for. Writable. |
| `source` | string | How this client came to you. Writable. |
| `red_flags` | string | Anything to watch out for. Writable. |
| `running_notes` | string | Your rolling notes on this client. Writable. |
| `outcome_note` | string | How the engagement ended. Writable. |
| `timezone` | string | IANA time zone, e.g. `America/New_York`. Writable. |
| `entered_stage_at` | datetime | When the client entered its current stage. |
| `last_contact_at` | datetime | When you last had contact. |
| `inserted_at` | datetime | When the client was created. |
| `updated_at` | datetime | When the client last changed. |

List parameters:

- `stage` — One stage, or several comma-separated. Only clients in those stages are returned.
- `q` — Case-insensitive substring over name, email and organization.
- `updated_since` — ISO 8601 timestamp — only clients changed since then.
- `limit` — Page size. Defaults to 50, capped at 200.
- `cursor` — The `next_cursor` from the previous page.

### Notes

A logged contact with a client — a note, a call, a meeting, a message. This is what a client's timeline calls a note.

- `GET /api/v1/interactions` — List logged notes, filtered and paginated. Newest logged first.
- `POST /api/v1/interactions` — Log a note, call, meeting or message against a client.
- `GET /api/v1/interactions/{id}` — Fetch one logged note.
- `PATCH /api/v1/interactions/{id}` — Update a logged note. Only the fields you send change; it cannot be moved to another client.
- `DELETE /api/v1/interactions/{id}` — Delete a logged note.

| Field | Type | Description |
| --- | --- | --- |
| `id` | integer | Throughway's identifier for this note. |
| `client_id` | integer | Which client this note is about. Set when you log it; an update cannot move it. Set on create. |
| `type` | string | What kind of contact this was. Writable. One of: call, email, meeting, message, note. |
| `note` | string | The note itself, plain text, stored and returned exactly as you send it. Writable. |
| `occurred_at` | datetime | When it happened, ISO 8601. Omit it and you get now. Writable. |
| `source` | string | Who logged this note: `coach` when the coach logged it in Throughway, `agent` when an AI logged it through this API. Set by Throughway; a value you send is ignored. One of: coach, agent. |
| `inserted_at` | datetime | When the note was logged. |
| `updated_at` | datetime | When the note last changed. |

List parameters:

- `client_id` — Only this client's notes.
- `type` — One type, or several comma-separated: call, email, meeting, message, note.
- `occurred_since` — ISO 8601 — only notes that occurred at or after this.
- `limit` — Page size. Defaults to 50, capped at 200.
- `cursor` — The `next_cursor` from the previous page.

### Sessions

A coaching session, call or meeting with a client — its notes, saved summary and transcript. Read-only.

- `GET /api/v1/sessions` — List sessions, filtered and paginated. Newest created first. Transcripts and your auto supervision are left out unless you ask for them.
- `GET /api/v1/sessions/{id}` — Fetch one session, transcript and your auto supervision included.

| Field | Type | Description |
| --- | --- | --- |
| `id` | integer | Throughway's identifier for this session. |
| `client_id` | integer | Which client the session was with. |
| `kind` | string | What kind of session this was. One of: session, chemistry_call, imported, stakeholder_interview, meeting. |
| `status` | string | Whether the session is still in progress or has ended. One of: in_progress, ended. |
| `started_at` | datetime | When the session started. |
| `ended_at` | datetime | When the session ended. Null while it is in progress. |
| `length_minutes` | integer | How long the session was, in minutes. Null when no length is known yet. |
| `body` | string | Your notes from the session. |
| `summary` | string | The session's saved summary. |
| `transcript` | string | The transcript as plain text, or null. Omitted from list rows unless you pass include=transcript. |
| `transcript_entries` | array | The transcript as speaker-labelled turns, or null. Omitted from list rows unless you pass include=transcript. |
| `supervision` | string | Your auto supervision of this session, as Markdown: the write-up's summary, what worked and what is worth sitting with, with quotes. Null when you have no auto supervision of this session or it has no write-up yet. Omitted from list rows unless you pass include=supervision. |
| `inserted_at` | datetime | When the session was created. |
| `updated_at` | datetime | When the session last changed. |

List parameters:

- `client_id` — Only this client's sessions.
- `kind` — One kind, or several comma-separated: session, chemistry_call, imported, stakeholder_interview, meeting.
- `started_since` — ISO 8601 — only sessions that started at or after this.
- `started_before` — ISO 8601 — only sessions that started strictly before this.
- `include` — transcript adds transcript and transcript_entries to each row; supervision adds supervision.
- `limit` — Page size. Defaults to 50, capped at 200.
- `cursor` — The `next_cursor` from the previous page.

### Documents

A client's Throughway page or Google Doc, which you can write, or a file or link, which you can only read. Anything you create is private.

- `GET /api/v1/documents` — A client's documents, newest first. Metadata only, no bodies.
- `GET /api/v1/documents/{id}` — One document, with its body as Markdown.
- `POST /api/v1/documents` — A new page, or a new Google Doc in the client's Drive folder.
- `PATCH /api/v1/documents/{id}` — Rename it, replace its body, or both.
- `DELETE /api/v1/documents/{id}` — Remove it from the client. A Google Doc goes to the coach's Drive trash.

| Field | Type | Description |
| --- | --- | --- |
| `id` | integer | Throughway's identifier. |
| `client_id` | integer | Which client it belongs to. An update can't move it. Set on create. |
| `kind` | string | page and google_doc are writable. A list also shows file and link, read-only. Set on create. One of: page, google_doc, file, link. |
| `title` | string | For a Google Doc, also its name in Drive. Writable. |
| `body` | string | Markdown. Converted to HTML for a page and to Google Docs formatting for a Google Doc. An update replaces the whole body; there is no append. Null for a file, a link, or a Google Doc Throughway can't reach. Never in list rows. Writable. |
| `visibility` | string | Always private on create. Only the coach shares, in the app. One of: private, shared. |
| `url` | string | The Google Doc's link, or the page's link in Throughway. |
| `hidden` | string | Hidden from the coach's Files and Timeline: coach (they hid it) or auto (a signature image or calendar attachment). Null when visible. One of: coach, auto. |
| `inserted_at` | datetime | When the document was created. |
| `updated_at` | datetime | When the document last changed. |

List parameters:

- `client_id` — Required. Only this client's documents.
- `kind` — One kind, or several comma-separated: page, google_doc, file, link.
- `limit` — Page size. Defaults to 50, capped at 200.
- `cursor` — The `next_cursor` from the previous page.

### People

Someone in the coach's people book, as the iPhone app shows them: how to reach them, when they are next due, their open next steps and last notes. REST only; not an MCP tool.

- `GET /api/v1/people` — The people book, A–Z by name. With `updated_since`, also `deleted_ids`: the people deleted or archived since then.
- `GET /api/v1/people/{id}` — One person, with their open next steps, last 3 notes and how many notes they have.
- `POST /api/v1/people` — Add someone to the people book. They are not put on the board.
- `PATCH /api/v1/people/{id}` — Change a person's details. Only the fields you send change.
- `PUT /api/v1/people/{id}/tier` — Set how often to keep in touch, or clear it with null.
- `PUT /api/v1/people/{id}/photo` — Give a person a photo when they have none. The body is the JPEG itself (`Content-Type: image/jpeg`, at most 5 MB); a person who already has one answers 409 `photo_exists`.
- `POST /api/v1/people/{id}/snooze` — Hold a person back until a later day, or skip them till next time.
- `POST /api/v1/people/{id}/done` — Log that you reached out: a "Reached out" note, so they are next due one interval from today.
- `POST /api/v1/people/{id}/archive` — Archive a person: out of the book and off Today until unarchived. Already archived answers them unchanged.
- `POST /api/v1/people/{id}/unarchive` — Bring an archived person back into the book.
- `POST /api/v1/people/{id}/take_off_board` — Take a potential client off the pipeline board, keeping them in the book. Someone not on the board, or an active or past client, is 422 on `stage`.
- `DELETE /api/v1/people/{id}` — Delete a person and everything about them. A delta sync then lists them in `deleted_ids`.
- `POST /api/v1/people/{id}/follow_ups` — Schedule a text to a person, due at a later time. They need a phone on file.
- `POST /api/v1/people/{id}/check_ins` — Start AI-drafted check-in texts at 9:00, until their next session or two weeks out. One open series per person; they need a phone on file.
- `GET /api/v1/people/{id}/notes` — A person's notes, newest logged first.
- `GET /api/v1/people/{id}/timeline` — What has happened with a person, newest first — notes, sessions, emails, failed follow-ups. Nothing still ahead: that is the person's `coming_up`.
- `POST /api/v1/people/{id}/notes` — Log a note about a person.
- `POST /api/v1/people/{id}/next_steps` — Add a next step for a person.
- `PATCH /api/v1/next_steps/{id}` — Mark a next step done, or reopen it.
- `PATCH /api/v1/notes/{id}` — Change a note's text.
- `DELETE /api/v1/notes/{id}` — Delete a note.
- `GET /api/v1/reach_out` — The day's three people to reach out to — the same three as Today, so either spends the day's slots — with today's meetings, the next steps due and the follow-up due texts.
- `GET /api/v1/loose_ends` — The people you met or wrote to who aren't in the book yet, waiting to be added or ignored.
- `POST /api/v1/loose_ends/{id}/add` — Add a suggested person to the people book, off the board, with a tier or none.
- `POST /api/v1/loose_ends/{id}/ignore` — Never suggest this address again.
- `POST /api/v1/follow_ups/{id}/texted` — Mark a due follow-up text as sent: a "Texted" note, which counts as reaching out. Already texted, skipped or cancelled answers its status as it stands.
- `POST /api/v1/follow_ups/{id}/skip` — Skip a due follow-up text, logging nothing. Already texted, skipped or cancelled answers its status as it stands.
- `POST /api/v1/captures/propose` — Read a note's transcript and propose the person it is about — matched against the book or new — with a tier, a next step and the tidied note. Nothing is saved. Audio is never accepted.
- `GET /api/v1/groups` — Your groups, A–Z by name.
- `POST /api/v1/groups` — Add a group. A blank name, or one you already have in any case, is 422 on `name`.

| Field | Type | Description |
| --- | --- | --- |
| `id` | integer | Throughway's identifier for this person. |
| `name` | string | The person's full name. Writable. |
| `organization` | string | Where they work. Writable. |
| `title` | string | Their role, e.g. `COO`. Writable. |
| `email` | string | Their primary email address. Writable. |
| `phone` | string | Their phone number, as written. Writable. |
| `phone_apps` | array | The apps their phone is also on, from `whatsapp` and `signal`; `[]` when none. Cleared with the phone. Writable. One of: whatsapp, signal. |
| `facebook_username` | string | Their Facebook username. A profile link is accepted and stored as the username. Writable. |
| `linkedin_url` | string | Their LinkedIn profile, stored as `linkedin.com/in/…`. A profile link, `@name` or bare name is accepted; a company page or any other link is refused. Writable. |
| `tier` | string | How often to keep in touch: every_2_weeks, monthly, quarterly, twice_a_year or yearly. Null is untiered. Writable. One of: every_2_weeks, monthly, quarterly, twice_a_year, yearly. |
| `due_on` | date | The day they are next due, in the coach's time zone. Null when untiered. |
| `snooze_until` | date | Held back until this day, or null. |
| `last_touch` | object | The latest contact as `{kind, at}`, or null. |
| `preferred_method` | string | The coach's preferred way to reach them, or null. Writable. One of: text, call, email, whatsapp, signal, linkedin, messenger. |
| `ways_to_reach` | object | `{available, primary, preferred, preferred_available}`: the ways their details allow, the one to use first, the stored preference and whether it is available. |
| `photo_url` | string | Their photo, an absolute URL, or null when they have none. |
| `updated_at` | datetime | When the person last changed. |
| `stage` | string | Their pipeline stage, or null when they are in the book only. One person only. One of: nurturing, in_conversation, chemistry_scheduled, deciding, onboarding, active, complete, not_a_fit. |
| `days_in_stage` | integer | Whole days since they entered their stage, in the coach's time zone. Null with no stage. |
| `timezone` | string | Their own time zone, e.g. `Europe/Lisbon`, or null. One person only. Writable. |
| `groups` | array | The names of the groups they are in, A–Z. One person only. |
| `group_ids` | array | The ids of the groups they are in, in `groups`' order. Sent, it is their exact membership; an id that is not one of your groups is refused and nothing is written. Writable. |
| `archived_at` | datetime | When they were archived, or null. One person only. |
| `background` | string | What the coach keeps in mind about them, or null. One person only. An empty string or null clears it. Writable. |
| `source` | string | Where this person came from, e.g. `iPhone Contacts`, or null. One person only. Writable. |
| `bill_to` | string | Who their invoices are made out to, or null. One person only. Writable. |
| `billing_email` | string | Where their invoices are sent, or null. One person only. Writable. |
| `look_for_transcripts` | boolean | Whether their meetings' transcripts are looked for. One person only. Writable. |
| `next_steps` | array | Their open next steps, soonest due first. One person only, never in list rows. |
| `notes` | array | Their last 3 notes, newest first. One person only, never in list rows. |
| `notes_count` | integer | How many notes they have in all. One person only. |
| `coming_up` | array | What is still ahead, soonest first: `meeting`, `follow_up`, `check_in_series` or `next_step`. `at` is an ISO datetime (a next step's a date) or null; `title` the meeting's session label or event title, a follow-up's text or subject, a step's body, null for a series; `detail` per kind. One person only. |

List parameters:

- `q` — Case-insensitive substring over name, organization, title and every email address.
- `tier` — One tier: every_2_weeks, monthly, quarterly, twice_a_year, yearly, or none for the untiered.
- `updated_since` — ISO 8601 — only people changed at or after this. Adds `deleted_ids`: the people deleted or archived since.
- `cursor` — The `next_cursor` from the previous page.
- `limit` — Page size. Defaults to 50, capped at 200.

### A client's timeline

`GET /api/v1/clients/{id}/timeline` returns everything that has happened with a client, newest first — notes, sessions, stage changes, contracts, invoices, email threads, files, pages and shares — merged into one list. Every event carries the same six keys: `at`, `kind`, `id`, `title`, `body` and `detail`. `id` is `null` for an event that is not a row you can fetch, and `body` is non-null for `interaction` events only — every other kind's content belongs to a resource of its own. Email bodies and snippets are never included.

`before` is an **exclusive filter** on `at`, not a cursor: the merged feed has no unique monotonic key, so exhaustive cursor paging over it cannot be made correct. The response says `has_more` rather than `next_cursor` for exactly that reason. If you need every row of one kind, page that kind's own endpoint, which does have a correct cursor.

`limit` is how many events come back — 50 by default, 200 at most. A truncated page says `has_more: true`; walk backwards by passing the `at` of the last event you got as the next `before`.

### MCP tools

The same operations are available as MCP tools at `https://staging.throughway.app/api/v1/mcp` — JSON-RPC 2.0 over streamable HTTP, JSON responses, stateless. A chat AI signs in with OAuth; a script sends a key as `Authorization: Bearer trw_…`. A read-only connection is only ever offered the read tools.

- `list_clients` — List clients, filtered and paginated.
- `create_client` — Create a client.
- `get_client` — Fetch one client.
- `update_client` — Update a client. Only the fields you send change.
- `get_client_timeline` — Everything that has happened with a client, newest first — notes, sessions, stage changes, emails, files.
- `list_interactions` — List logged notes, filtered and paginated. Newest logged first.
- `create_interaction` — Log a note, call, meeting or message against a client.
- `get_interaction` — Fetch one logged note.
- `update_interaction` — Update a logged note. Only the fields you send change; it cannot be moved to another client.
- `delete_interaction` — Delete a logged note.
- `list_sessions` — List sessions, filtered and paginated. Newest created first. Transcripts and your auto supervision are left out unless you ask for them.
- `get_session` — Fetch one session, transcript and your auto supervision included.
- `list_documents` — A client's documents, newest first. Metadata only, no bodies.
- `get_document` — One document, with its body as Markdown.
- `create_document` — A new page, or a new Google Doc in the client's Drive folder.
- `update_document` — Rename it, replace its body, or both.
- `delete_document` — Remove it from the client. A Google Doc goes to the coach's Drive trash.

### Errors, paging & limits

A list answers `{"data": [...], "next_cursor": "1042"}`. Pass `next_cursor` back as `cursor` to get the next page; it is `null` on the last one, and a page that comes back empty means you are done. `limit` is the page size — 50 by default, 200 at most. Each resource's own filters are listed with its fields above.

```json
{"error": {"code": "validation_failed",
           "message": "The record could not be saved.",
           "fields": {"name": ["can't be blank"]}}}
```

Codes and statuses: `bad_request` 400, `unauthorized` 401, `forbidden` 403, `not_found` 404, `validation_failed` 422. `fields` appears only for `validation_failed`. A record belonging to another practice answers `404`, never `403`.

### Authentication

There are two ways in. A chat AI — Claude, ChatGPT, Claude Code — signs in with OAuth: it discovers Throughway's authorization server at [https://staging.throughway.app/.well-known/oauth-protected-resource](https://staging.throughway.app/.well-known/oauth-protected-resource), and the coach signs in and approves **Read only** or **Read and log notes**.

A script or an AI on your own computer sends an API key as a bearer token on every request, from a key minted in [Settings › Your AI](https://staging.throughway.app/settings/your-ai?ai=local):

```sh
curl -H "Authorization: Bearer trw_…" https://staging.throughway.app/api/v1/clients
```

No credential, an unknown one or a revoked one all answer `401` with the same body. Read only access answers `403` on any write.

## What your AI can't do

Your AI can't delete a client or send email for you. It never sees email bodies or a client's portal link.