Skip to content

API reference

chatwire's web server exposes a REST API, an SSE stream for real-time updates, and an MCP stdio server for LLM agent access.

Base URL

Default: http://localhost:8723

Configure port in ~/.chatwire/config.json:

{ "web": { "port": 8723 } }

Authentication

When no password is set, all endpoints are open. When a password is set:

  • Browser sessions — cookie-based (set by /login); 30-day sliding expiry
  • API clients — Bearer token: Authorization: Bearer cwk_<hex>

Generate API keys from Settings → Security → API Keys.

Scopes: - read — read conversations, messages, contacts - write — send messages

Endpoints requiring write scope are noted below.

Health

GET /healthz

Basic alive check. Returns 200 when the web server is running.

{"ok": true, "version": "abc1234", "release": "1.14.0"}

GET /api/heartbeat

Detailed health including bridge liveness.

{
  "ok": true,
  "ts": 1715900000.0,
  "bridge_alive": true,
  "last_heartbeat": 1715899998.0,
  "last_message_ts": "2026-05-16T14:30:00Z"
}
Field Type Description
ok bool Always true when the web server responds
ts float Current server unix timestamp
bridge_alive bool True if bridge heartbeat is < 10s old
last_heartbeat float or null Unix timestamp of last bridge poll
last_message_ts string or null Timestamp of most recent message

GET /version

{"release": "1.14.0", "build": "abc1234"}

Conversations

GET /api/v1/conversations

List recent conversations.

Query params: - limit (int, default 50) — max conversations to return - offset (int, default 0) — pagination offset

Response:

[
  {
    "guid": "iMessage;-;+15551234567",
    "handle": "+15551234567",
    "display_name": "Alice",
    "last_message": "Are you around?",
    "last_message_ts": "2026-05-16T14:00:00Z",
    "unread_count": 2,
    "is_group": false
  }
]

GET /api/v1/conversations/{guid}/messages

Get messages in a conversation.

Path params: - guid — chat GUID (URL-encoded)

Query params: - limit (int, default 100) — messages to return - before_rowid (int) — paginate backwards from this ROWID

Response:

[
  {
    "rowid": 12345,
    "guid": "message-guid",
    "text": "Are you around?",
    "sender": "Alice",
    "handle": "+15551234567",
    "is_from_me": false,
    "ts": "2026-05-16T14:00:00Z",
    "read_at": null,
    "tapbacks": ["❤️"],
    "reply_to_guid": null,
    "edited": false,
    "attachments": []
  }
]

Sending

POST /api/v1/send (requires write scope)

Send a message to a handle.

Request body:

{
  "handle": "+15551234567",
  "text": "Hello!"
}

Response:

{"ok": true, "rowid": 12346}

POST /api/v1/send/chat (requires write scope)

Send a message to a chat GUID (for group chats).

{
  "chat_guid": "iMessage;+;group-guid",
  "text": "Hello group!"
}

POST /api/v1/send/file (requires write scope)

Upload and send an attachment. Multipart form data: - handle — recipient handle or chat_guid - file — the file bytes

Settings

GET /api/v1/settings

Get all settings.

Response: the full config dict (password hash excluded).

POST /api/v1/settings

Update settings. Sends the config fields to update (partial update supported).

{
  "web": {
    "theme": "dracula"
  }
}

GET /api/v1/whitelist

List whitelisted handles.

{"handles": ["+15551234567", "+15559876543"]}

POST /api/v1/whitelist/add

Add a handle to the whitelist.

{"handle": "+15551234567"}

POST /api/v1/whitelist/remove

Remove a handle from the whitelist.

{"handle": "+15551234567"}

Plugins

GET /api/v1/plugins

List discovered plugins with their enable state and schema.

[
  {
    "name": "ntfy",
    "display_name": "ntfy Notifications",
    "description": "Push notifications via ntfy.sh",
    "enabled": true,
    "tier": "notify",
    "settings_schema": { ... }
  }
]

POST /api/v1/plugins/{name}/enable

Enable a plugin.

POST /api/v1/plugins/{name}/disable

Disable a plugin.

Automation rules

GET /api/v1/rules

List all automation rules.

[
  {
    "id": "rule-001",
    "name": "Urgent from Alice",
    "enabled": true,
    "trigger": "on_inbound",
    "conditions": "from:Alice AND contains:urgent",
    "action": {"type": "auto_reply", "body": "On my way!"}
  }
]

POST /api/v1/rules

Create a new rule. Returns the created rule with its assigned id.

PUT /api/v1/rules/{id}

Update a rule.

DELETE /api/v1/rules/{id}

Delete a rule.

SSE stream

GET /api/logs

Server-Sent Events stream of structured log entries. Connect and keep the connection open to receive real-time events.

Each event is a JSON object:

data: {"ts": "2026-05-16T14:00:00Z", "source": "bridge", "level": "info", "msg": "new message from Alice"}

data: {"ts": "2026-05-16T14:00:01Z", "source": "ntfy", "level": "info", "msg": "notification sent"}

The React frontend subscribes to this stream to invalidate TanStack Query caches when new messages arrive.

GET /api/v1/events

Alternative SSE stream scoped to message events only (not log entries). Format:

data: {"type": "new_message", "chat_guid": "...", "rowid": 12345}

data: {"type": "message_sent", "handle": "+15551234567", "rowid": 12346}

UI-specific endpoints

GET /api/ui/stats

Message statistics (requires Stats plugin enabled).

{
  "enabled": true,
  "sent_total": 1234,
  "received_total": 5678,
  "top_contacts": [{"name": "Alice", "count": 200}],
  "by_hour": [0, 5, 12, ...],
  "by_day": [120, 180, ...]
}

GET /api/ui/themes

List available themes for the picker.

{
  "built_in": ["dracula", "catppuccin-mocha", ...],
  "packages": ["my-dark-theme"],
  "active": "dracula"
}

MCP tools

The MCP stdio server exposes these tools to LLM clients. Start with chatwire mcp.

Tool Description Scope
list_conversations List recent conversations read
get_messages Get messages in a conversation read
send_message Send an iMessage or SMS write
search_messages Full-text search read
get_contact Look up a contact read
list_whitelist List whitelisted contacts read

See MCP integration guide for setup and authentication.