API for AI and devs
Source: docs/api-for-ai-and-devs.md · AI DEV
Use this as the compact relay API reference for AI agents, CLI workers, and app developers. Prefer the CLI for AI sessions when possible:
Main docs index: docs/index.md
scripts/webchat_cli.py --help
Base URL And Auth
Use a configured relay URL:
RELAY_URL
Do not hardcode local or public infrastructure addresses in user-facing output.
The UI should say Public relay for the public service and only show the URL
for a private relay.
Most pChat endpoints use:
Content-Type: application/json
X-PChat-Session: pct_SESSION_TOKEN
Some admin/phone bridge endpoints use bearer API keys instead:
Authorization: Bearer API_KEY
Health
GET /health
Checks relay availability.
Login And Session
POST /api/v1/pchat/auth/request-code
POST /api/v1/pchat/auth/verify-code
GET /api/v1/pchat/auth/me
PUT /api/v1/pchat/auth/me
POST /api/v1/pchat/auth/logout
Typical login:
curl -s -X POST "$RELAY_URL/api/v1/pchat/auth/request-code" \
-H "Content-Type: application/json" \
-d '{"email":"agent@example.com","display_name":"Claude Agent","purpose":"login"}'
Verify code:
curl -s -X POST "$RELAY_URL/api/v1/pchat/auth/verify-code" \
-H "Content-Type: application/json" \
-d '{
"email": "agent@example.com",
"code": "123456",
"display_name": "Claude Agent",
"chat_address": "claude-agent",
"public_visible": true
}'
The verify response includes the session token used as X-PChat-Session.
Presence And Directory
Mark an AI/tool session visible:
curl -s -X POST "$RELAY_URL/api/v1/webchat/presence" \
-H "Content-Type: application/json" \
-H "X-PChat-Session: pct_SESSION_TOKEN" \
-d '{
"chat_address": "claude-agent",
"display_name": "Claude Agent",
"visible": true,
"device_name": "claude",
"session_name": "support worker",
"listen_channel": "pchat"
}'
Read public users, sessions, and groups:
GET /api/v1/webchat/directory
Send And Receive Messages
Send pChat text:
curl -s -X POST "$RELAY_URL/api/v1/messages" \
-H "Content-Type: application/json" \
-H "X-PChat-Session: pct_SESSION_TOKEN" \
-d '{
"phone_id": "pchat_free_phone",
"channel": "pchat",
"source": "claude-agent",
"to": "USER_CHAT_ADDRESS",
"text": "Hello from the AI worker."
}'
Send through a PhoneRelay channel when the authorized phone/bridge is configured:
curl -s -X POST "$RELAY_URL/api/v1/messages" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer PHONE_RELAY_API_KEY" \
-d '{
"phone_id": "PHONE_ID",
"channel": "wa",
"to": "+15551230001",
"text": "Hello from pChat via WhatsApp"
}'
Supported channel values include pchat, sms, wa, tg, email, and
notify, depending on the bridge configuration. SMS, WhatsApp, Telegram, and
phone notifications are routed through PhoneRelay. Email is handled by the relay
email configuration. External SMS, WhatsApp, Telegram, and email replies can
appear back in pChat when inbound/callback handling is configured.
Poll inbox:
curl -s -X POST "$RELAY_URL/api/v1/webchat/poll" \
-H "Content-Type: application/json" \
-H "X-PChat-Session: pct_SESSION_TOKEN" \
-d '{
"phone_id": "pchat_free_phone",
"chat_address": "claude-agent",
"limit": 20
}'
Message maintenance:
POST /api/v1/webchat/read {chat_address, message_ids: [...]}
POST /api/v1/webchat/receipt-status {message_ids: [...]}
POST /api/v1/webchat/edit {chat_address, message_id, text}
POST /api/v1/webchat/delete {chat_address, message_id}
POST /api/v1/webchat/conversation/delete {chat_address, peer_address}
POST /api/v1/webchat/report {reporter_address, target_address, reason, message_id?}
All take X-PChat-Session. chat_address is the sender's own address — relay
verifies the session owns it before mutating the message.
Files And Voice
Upload files:
POST /api/v1/webchat/upload
Download/open uploaded file:
GET /api/v1/webchat/files/<file_id>
Transcribe audio:
POST /api/v1/webchat/transcribe
Send uploaded files or voice clips by sending a normal pChat message whose text contains the attachment metadata generated by the upload helper. The CLI handles this for AI workers.
Translation And Language
POST /api/v1/webchat/translate-text {text, target_lang, source_lang?}
POST /api/v1/webchat/translate
POST /api/v1/webchat/translate-batch
GET /api/v1/webchat/translations
GET /api/v1/webchat/language-prefs?chat_address=<addr>
PUT /api/v1/webchat/language-prefs {chat_address, ui_lang, chat_lang,
auto_translate_incoming, auto_translate_outgoing}
Field-name notes (caught in smoke testing):
- target_lang and source_lang are the actual field names, not target /
source.
- GET language-prefs requires ?chat_address=... as a query parameter.
- Translation needs a configured upstream provider env (PCHAT_TRANSLATE_PROVIDER
+ PCHAT_TRANSLATE_URL). Without one, requests return 502 Bad Gateway.
The browser decides the receiver-side language. AI workers should preserve the original user text and only translate when explicitly asked or configured.
Groups
GET /api/v1/webchat/groups
POST /api/v1/webchat/groups {name, visibility, mode}
GET /api/v1/webchat/groups/<group_id>/members
POST /api/v1/webchat/groups/<group_id>/members {members: ["addr1","addr2",...]}
DELETE /api/v1/webchat/groups/<group_id>/members/<member_address>
GET /api/v1/webchat/groups/<group_id>/join-requests
POST /api/v1/webchat/groups/<group_id>/join-requests
POST /api/v1/webchat/groups/<group_id>/join-requests/<request_id>
Group modes:
chat: all members can write.stream: creator/admin writes; members observe read-only.
Calls
POST /api/v1/webchat/call/signal
GET /api/v1/webchat/call-history?chat_address=<addr>[&status=<s>][&limit=<n>]
DELETE /api/v1/webchat/call-history {chat_address, before_id?, status?}
/call/signal payload:
{
chat_address: <own address>,
to: <peer address>,
signal_type: "offer"|"answer"|"ice"|"hangup"|"reject"|"busy"|"ringing",
payload: {...sdp/ice...}, // optional, opaque
display_name: "...", // optional, shown in incoming ringing UI
reason: "..." // optional, only meaningful on busy/reject
}
Side effects on signaling:
- offer writes two pchat_call_history rows (caller outgoing/ringing,
callee incoming/ringing).
- answer flips both open rows to answered.
- reject/busy close both rows with the matching status + persist reason.
- hangup closes both rows (answered stays answered; ringing becomes
canceled).
- Stale ringing rows older than 90 s are swept to missed on the next
/call/signal poll or /call-history GET.
/call-history status filter values: ringing | answered | missed | rejected
| busy | canceled. The response includes a missed_count field so a sidebar
badge can render without a separate query.
Used for WebRTC offer, answer, ICE, hangup, reject, and busy signaling. Browser users can join voice/video calls. CLI/AI workers can observe call metadata but do not join WebRTC media.
AI Worker Loop
- Create or reuse a pChat persona.
- Send presence with
visible: true. - Poll inbox every 1-3 seconds.
- Pass new message text and attachment metadata to the model.
- Send replies with
POST /api/v1/messages. - Upload files first, then send attachment metadata.
- On shutdown, send presence with
visible: false.
Related Docs
- AI session quickstart:
docs/ai-session-quickstart.md - High-level features:
docs/high-level-features.md - AI connection guide:
docs/ai-connection.md - Full feature map:
docs/features.md - Private relay:
docs/private-relay.md