Plow Seeds
seeds/plow-pbc/seed-plow-chat

Plow Chat SEED

Specification and reference examples for the Plow Chat API — activate users, send messages, and receive replies over SMS/iMessage via REST and WebSocket.

  • messaging
  • api-spec
  • chat
  • websocket
  • sms
  • iMessage
by plow-pbc·branch main·commit bcf7c18·updated Jun 1, 2026

Setup & installation

Install in your terminal with command:
curl -fsSL https://raw.githubusercontent.com/plow-pbc/seed/main/install.sh | bash -s -- https://github.com/plow-pbc/seed-plow-chat/blob/main/SEED.md

What this seed does

This seed documents how an AI agent can connect a person to a messaging line (iMessage or SMS) using Plow's chat service. It covers every step: registering and activating a user, linking their phone or messaging identity, sending messages, and subscribing to live updates as they arrive. The core flow requires nothing more than curl — no libraries, no SDK, no special tooling.

The seed is a specification, not a library. It defines the exact API surface — objects, lifecycle states, and required actions — and ships a set of small Python reference scripts to illustrate the full create/activate/send/receive flow. Downstream seeds that need to talk over Plow Chat declare this seed as a dependency and build against this contract.

Because the Plow Chat API is pre-1.0, this seed defers fine-grained detail to the live OpenAPI document at https://api.plow.co/openapi.json and explicitly flags what may change. Security hygiene — keeping session tokens and activation codes out of committed files — is a first-class concern throughout.

When to use it

  • When I want to send a text or iMessage to a user from my AI agent and need to understand how the Plow API works end-to-end.
  • When I'm building a chatbot that activates users via a single text and then sends and receives messages on their behalf.
  • When I need real-time delivery of inbound messages and want to know how to open and maintain a WebSocket subscription.
  • When I want to verify a user's phone identity before allowing them into a conversation, using Plow's one-text activation flow.
  • When another seed I'm building needs to integrate Plow Chat and I need the canonical API contract to depend on.
View raw SEED.md
# Purpose

> See [[README#Purpose]].

## Normative Language

The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in RFC 2119.

`Implementation-defined` means the behavior is part of the implementation contract; this specification does not prescribe a single policy.

Sub-folder SEEDs in this tree inherit the RFC 2119 declaration. They MUST NOT re-declare it.

## Dependencies

### API

- The Plow API MUST be reachable at `https://api.plow.co`. ^dep-api
- The OpenAPI document MUST be published at `https://api.plow.co/openapi.json`. ^dep-openapi
- The API is pre-1.0 and MAY change without backwards compatibility guarantees.

### Software

- `curl` is sufficient for a consumer that activates a user, surfaces verification instructions, and polls redeem over REST. ^dep-curl-consumer
- `python3` is REQUIRED to run the reference examples. ^dep-python
- The WebSocket example additionally REQUIRES `pip install websockets`. ^dep-websockets

## Objects

The named entities exposed by the API.

### Plow Chat API

- The HTTP and WebSocket API at `https://api.plow.co/v1`. ^obj-api
- Exposes OAuth-authenticated chat listing, chat status reads, messages, invitation resend, and WebSocket ticket endpoints.

### Plow line

- A provisioned messaging line returned by authenticated `GET /v1/lines`. ^obj-line
- Has a `uid` matching `ln_...` and a `provider_key` (the phone number users text for activation and see as the sender on inbound replies).

### Plow session token

- A Bearer token returned by `POST /v1/auth/activate/redeem` after activation completes. ^obj-token
- The token authenticates all chat API calls with `Authorization: Bearer <token>` and MUST NOT be logged or committed.
- The shortcut activation token is a user credential, not a chat-scoped secret; it can authorize user-wide reads such as identity and channel state. ^obj-token-scope
- Callers MUST treat it with the same care as an API key: store it only in private local state or chmod-600 environment files, never in source, logs, or shared transcripts.
- Consumers SHOULD minimize token lifetime and exposure in their own tooling because compromise is broader than one chat.

### Plow chat

- A conversation returned by `POST /v1/chats`, `GET /v1/chats`, `GET /v1/chats/{chat_uid}`, or the `chat` field embedded in a verified activation redeem response. ^obj-chat
- Lifecycle states: `pending` → `active`, or terminal `failed`.
- Has a stable `uid` matching `cht_...`.
- `GET /v1/chats/{chat_uid}` returns the current chat resource, including `status`, when authenticated with the user's Bearer token. ^obj-chat-status

### Plow member

- A human participant in a chat. ^obj-member
- In the one-on-one activation flow, Plow binds the user's messaging identity from the inbound `Plow Activate: <display_code>` text.
- Once active, inbound `message_received` frames identify the member in `message.sender` with `type='member'`, `uid`, `display_name`, and `provider_key`.

### Plow message

- A message in a chat. ^obj-message
- Has `uid`, `body`, `direction` (`inbound` or `outbound`), and `sender` (member or agent).
- Outbound messages are echoes of the API caller's own sends; subscribers SHOULD filter them out when reacting to user input.

### WebSocket frame types

Frames received over `wss://api.plow.co/v1/ws?ticket=<ticket>`. ^obj-frames

- `connected` — the subscription is live. ^frame-connected
- `chat_active` — the chat moved to `active` state. ^frame-chat-active
- `chat_activation_failed` — terminal; recovery is delete and recreate. ^frame-chat-activation-failed
- `participant_verified` — a member completed verification. ^frame-participant-verified
- `message_received` — new message arrived. Body in `message.body`, direction in `message.direction`. ^frame-message-received
- `message_status_updated` — outbound delivery transition. ^frame-message-status-updated

### Reference examples

- `ref/examples/create_chat.py` starts activation with `provision_chat=true` and prints verification instructions while writing activation state to a local file. ^obj-example-create
- `ref/examples/check_chat.py` reads a state file, redeems activation, and stores the token and chat uid when verified. ^obj-example-check
- `ref/examples/send_message.py` sends one plain-text message using a state file's token. ^obj-example-send
- `ref/examples/listen_websocket.py` mints a ticket and prints incoming WebSocket frames. ^obj-example-listen

The examples are illustrative implementations of the Actions below; they are not a library.
`ref/verify.sh` and the Python files under `ref/examples/` are maintainer/reference tooling; they do not add Python, an SDK, or WebSocket requirements to the curl-only creation and verification contract. ^obj-reference-not-consumer

## Actions

The verbs performed against the API.

### User is activated with a first chat

- For the default one-text flow, the caller starts activation with `POST /v1/auth/activate` and `provision_chat=true`. ^act-create-chat
- Plow assigns a pool line and returns `display_code`, `activation_secret`, `send_to`, and `line_id`.
- A curl-only consumer can start activation with:

```sh
curl -fsS -X POST https://api.plow.co/v1/auth/activate \
  -H 'Content-Type: application/json' \
  --data '{"name":"Ada","provision_chat":true}'
```

- The caller MUST capture `activation_secret` and store it outside committed files until redeem returns a token. ^act-create-capture
- The caller MUST surface `Plow Activate: <display_code>` and the `send_to` number to the human so they can text the activation message to the line.

### Activation is verified

- The user texts `Plow Activate: <display_code>` to `send_to` from the messaging identity they want bound to the chat. ^act-verify-chat
- The caller polls `POST /v1/auth/activate/redeem` with `{"activation_secret":"..."}` until the response returns `status:"verified"`.
- The verified redeem response includes `token` and, when chat provisioning succeeded, `chat`.
- The caller MUST store the token and chat uid outside committed files.

### User is activated before chat creation

- As an alternate generic flow, the caller MAY start activation without `provision_chat`; this registers the user first and does not create the initial chat. ^act-activate-only
- After the human texts `Plow Activate: <display_code>` to `send_to`, the caller redeems `activation_secret` and stores the returned Bearer token.
- The caller then uses that token to list lines with `GET /v1/lines` and create a chat with authenticated `POST /v1/chats`.
- `POST /v1/chats` uses exactly one `agent` participant (`line_id` set) and at least one `member` participant (`display_name` set). The response includes the chat `uid` and a `verification_code` for each pending member.
- For a separately created chat, the caller MUST surface each `VERIFY-XXXXXX` member code and the chosen line's `provider_key`; the human texts the code to that line to bind their messaging identity to the chat.

### Chat status is read

- The caller reads current chat state with authenticated `GET /v1/chats/{chat_uid}` and header `Authorization: Bearer <token>`. ^act-read-chat-status
- The response includes `status`, which is `pending`, `active`, or `failed`; callers MAY poll this REST endpoint to guide a human through verification without opening a WebSocket.
- A curl-only status check is:

```sh
curl -fsS https://api.plow.co/v1/chats/cht_... \
  -H 'Authorization: Bearer <token>'
```

- A `pending` status means verification has not completed for every member. `active` means the chat can send messages. `failed` is terminal; recovery is to delete and recreate the chat.

### Message is sent

- The caller calls `POST /v1/chats/{chat_uid}/messages` with JSON body `{"body": "..."}` and header `Authorization: Bearer <token>`. ^act-send-message
- A 409 `chat_not_ready` response indicates the chat has not yet activated; the send is not retried until verification completes.

### WebSocket subscription is opened

- The caller mints a short-lived ticket with `POST /v1/ws/ticket`, JSON body `{"chat_id":"<chat_uid>"}`, and header `Authorization: Bearer <token>`. ^act-subscribe
- The caller connects to `wss://api.plow.co/v1/ws?ticket=<ticket>` before the ticket expires.
- The caller treats `{"type":"connected"}` as confirmation of subscription.
- On disconnect, the caller SHOULD reconnect by minting a fresh ticket and SHOULD backfill missed messages with `GET /v1/chats/{chat_uid}/messages`.

### Message is received

- The subscriber receives `message_received` frames on the WebSocket. ^act-receive-message
- Frames where `message.direction == 'outbound'` are echoes of the caller's own sends and MUST be ignored when reacting to user input.
- For inbound frames, the message body is in `message.body`; the sender's identity is in `message.sender.uid`, `message.sender.display_name`, and `message.sender.provider_key`.

## Verify

1. **OpenAPI reachable.** Fetch `https://api.plow.co/openapi.json`. Does it return a JSON OpenAPI document with an `openapi` version field? Expected: yes. ^v-openapi

2. **Endpoints documented.** Does the OpenAPI document define `POST /v1/auth/activate`, `POST /v1/auth/activate/redeem`, `GET /v1/lines`, `GET /v1/chats`, `POST /v1/chats`, `GET /v1/chats/{chat_uid}`, `POST /v1/chats/{chat_uid}/messages`, and `POST /v1/ws/ticket`? Expected: yes. ^v-endpoints

3. **Bearer-only chat API.** Read this SEED and the examples. Do all chat endpoints use `Authorization: Bearer <token>` rather than per-chat secret keys? Expected: yes. ^v-bearer-auth

4. **Examples compile.** Run `python3 -m py_compile ref/examples/*.py`. Does it exit 0? Expected: yes. ^v-compile

5. **Secret hygiene.** Search committed files. Do they avoid literal-looking Bearer tokens and activation codes? Expected: yes. ^v-secret-hygiene

6. **Curl-only verification path.** Read this SEED. Can a consumer start activation, instruct a human to text `Plow Activate: <display_code>` to the assigned line, and poll redeem using only `curl` and REST? Expected: yes. ^v-curl-only-verification

## Feedback

(default)

## Open

- The Plow Chat API is pre-1.0 and the spec at `/openapi.json` is the source of truth; this SEED documents the shape but defers detail to that document. ^o-pre1
- No language client library. The protocol is the surface; the `ref/examples/` scripts are reference implementations, not a published SDK. ^o-no-sdk
- No multi-chat orchestration helpers. ^o-multi

## Non-Goals

- This SEED does not define the Plow Chat service itself; that is a separate codebase.
- This SEED does not publish a Python (or any other language) package. Consumers implement against the spec directly.
- This SEED does not store or transmit session tokens, activation codes, or phone numbers in committed files.
View raw README.md
# Plow Chat SEED

## Purpose

A SEED that documents the Plow Chat API — the protocol any agent uses to send and receive messages on top of provisioned messaging lines (iMessage / SMS).

This repo is a **spec**, not a library. It describes the API surface in markdown and ships small example scripts that demonstrate the create / send / receive flow. Downstream SEEDs depend on this one when they need to integrate the API.

## Spec

- Base URL: `https://api.plow.co`
- OpenAPI: `https://api.plow.co/openapi.json`

## Quick start: bundled activation + first chat

Start activation. Setting `provision_chat=true` assigns a Plow line and creates a one-on-one chat after the human texts the activation code.

```bash
curl -fsS -X POST https://api.plow.co/v1/auth/activate \
  -H 'Content-Type: application/json' \
  --data '{"name":"Ada","provision_chat":true}'
```

Ask the human to text `Plow Activate: <display_code>` to the response `send_to` number.

Poll activation redeem until it returns `status:"verified"`. Capture the returned `token` and embedded chat `uid`; the token is the Bearer credential for every chat API call.

```bash
curl -fsS -X POST https://api.plow.co/v1/auth/activate/redeem \
  -H 'Content-Type: application/json' \
  --data '{"activation_secret":"YOUR_ACTIVATION_SECRET"}'
```

Use Bearer auth for chat mutations:

```bash
curl -fsS https://api.plow.co/v1/chats/cht_YOUR_CHAT_UID \
  -H 'Authorization: Bearer YOUR_TOKEN'
```

No SDK, Python, or WebSocket client is required for activation and REST sends.

## Optional reference tooling

The scripts under `ref/examples/` are illustrative maintainer/reference tools, not the consumer contract. They require Python, and the WebSocket listener additionally requires `websockets`.

```bash
python3 ref/examples/create_chat.py --state /tmp/plow.json
python3 ref/examples/check_chat.py /tmp/plow.json   # repeat after texting the activation code
python3 ref/examples/send_message.py /tmp/plow.json "hello from a SEED"
python3 ref/examples/listen_websocket.py /tmp/plow.json
```

## Alternate flow: user first, chat later

The bundled flow above is the default for demos because it avoids two human verification texts. A generic consumer can also register the user first, then create a chat with the returned Bearer token.

```bash
curl -fsS -X POST https://api.plow.co/v1/auth/activate \
  -H 'Content-Type: application/json' \
  --data '{"name":"Ada"}'
```

After the human texts `Plow Activate: <display_code>` to `send_to`, redeem the activation secret and capture the returned `token`.

```bash
curl -fsS -X POST https://api.plow.co/v1/auth/activate/redeem \
  -H 'Content-Type: application/json' \
  --data '{"activation_secret":"YOUR_ACTIVATION_SECRET"}'
```

Then use that token to choose a line and create a chat:

```bash
curl -fsS https://api.plow.co/v1/lines \
  -H 'Authorization: Bearer YOUR_TOKEN'

curl -fsS -X POST https://api.plow.co/v1/chats \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  --data '{
    "participants": [
      {"type": "agent", "line_id": "ln_YOUR_LINE_ID"},
      {"type": "member", "display_name": "Ada"}
    ]
  }'
```

For this separate chat-create path, ask the human to text the returned `VERIFY-XXXXXX` member code to the chosen line's `provider_key`, then poll `GET /v1/chats/{chat_uid}` with the Bearer token until the chat is `active`.

See [`SEED.md`](SEED.md) for the full Objects / Actions / Verify spec.

## License

MIT.

Version history

1 release
v1.0.0Jun 1, 2026

Initial Seed Release.

Dependencies

3 required · 2 optional
Plow Chat SEED
  • APIPlow Chat APIrequired· link ›· Base URL https://api.plow.co; OpenAPI at https://api.plow.co/openapi.json; pre-1.0
  • SWcurlrequired· Sufficient for the core activation and REST consumer path (no Python or WebSocket needed)
  • SWpython3optional· link ›· REQUIRED only to run the ref/examples/ reference scripts; not required by the curl-only consumer path
  • SWwebsockets (Python package)optional· link ›· Optional; needed only for ref/examples/listen_websocket.py — install via pip install websockets
  • StatePlow session tokenrequired· Bearer token returned by POST /v1/auth/activate/redeem after activation; must be stored securely outside committed files

Contributors

1 contributor
plonkusContributor@plonkus

Activity

0 comments

You need to be signed in with GitHub to comment.

No comments yet — be the first to share how this seed worked for you.