MESS Exchange Server API¶
Overview¶
The Exchange Server provides a REST API for MESS operations. Use this instead of GitHub when you need: - Multi-user support with separate API keys - Self-hosted deployment (Docker, Kubernetes, Cloudflare Workers) - Event-sourced storage queryable with DuckDB - Custom notification integrations
Base URL¶
Replace {exchange_id} with your exchange name (e.g., home, office).
Authentication¶
All endpoints except /register require a Bearer token:
Endpoints¶
Register Executor¶
POST /api/v1/exchanges/{exchange_id}/register
No authentication required. Returns an API key (save it - cannot be retrieved again).
curl -X POST http://localhost:3000/api/v1/exchanges/home/register \
-H "Content-Type: application/json" \
-d '{
"executor_id": "my-phone",
"display_name": "My Phone",
"capabilities": ["photo:capture", "location:indoor"],
"notifications": [
{"type": "ntfy", "topic": "mess-home"}
]
}'
Response:
{
"executor_id": "my-phone",
"api_key": "mess_home_abc123def456...",
"message": "Save this API key - it cannot be retrieved again."
}
List Requests¶
GET /api/v1/exchanges/{exchange_id}/requests
Query parameters:
- status - Filter by status (optional)
curl http://localhost:3000/api/v1/exchanges/home/requests \
-H "Authorization: Bearer mess_home_abc123..."
Response:
{
"threads": [
{
"ref": "2026-02-01-XY1Z",
"status": "pending",
"intent": "Check the front door",
"requestor_id": "claude-agent",
"executor_id": null,
"priority": "normal",
"created_at": "2026-02-01T22:00:00Z",
"updated_at": "2026-02-01T22:00:00Z"
}
]
}
Create Request¶
POST /api/v1/exchanges/{exchange_id}/requests
curl -X POST http://localhost:3000/api/v1/exchanges/home/requests \
-H "Authorization: Bearer mess_home_abc123..." \
-H "Content-Type: application/json" \
-d '{
"intent": "Check if the garage door is closed",
"context": ["Getting ready for bed"],
"priority": "normal",
"response_hint": ["image"]
}'
Response:
Get Request Details¶
GET /api/v1/exchanges/{exchange_id}/requests/{ref}
curl http://localhost:3000/api/v1/exchanges/home/requests/2026-02-01-XY1Z \
-H "Authorization: Bearer mess_home_abc123..."
Response:
{
"thread": {
"ref": "2026-02-01-XY1Z",
"status": "completed",
"intent": "Check if the garage door is closed",
"requestor_id": "claude-agent",
"executor_id": "my-phone",
"priority": "normal",
"created_at": "2026-02-01T22:00:00Z",
"updated_at": "2026-02-01T22:05:00Z",
"messages": [
{
"from": "claude-agent",
"ts": "2026-02-01T22:00:00Z",
"mess": [{"request": {"intent": "Check if the garage door is closed"}}]
},
{
"from": "my-phone",
"ts": "2026-02-01T22:05:00Z",
"mess": [
{"status": {"re": "2026-02-01-XY1Z", "code": "completed"}},
{"response": {"content": ["Garage is closed"]}}
]
}
]
}
}
Update Request¶
PATCH /api/v1/exchanges/{exchange_id}/requests/{ref}
Claim a request:
curl -X PATCH http://localhost:3000/api/v1/exchanges/home/requests/2026-02-01-XY1Z \
-H "Authorization: Bearer mess_home_abc123..." \
-H "Content-Type: application/json" \
-d '{"status": "claimed"}'
Complete with response:
curl -X PATCH http://localhost:3000/api/v1/exchanges/home/requests/2026-02-01-XY1Z \
-H "Authorization: Bearer mess_home_abc123..." \
-H "Content-Type: application/json" \
-d '{
"status": "completed",
"mess": [{"response": {"content": ["Door is locked", {"image": "data:image/..."}]}}]
}'
List Executors¶
GET /api/v1/exchanges/{exchange_id}/executors
curl http://localhost:3000/api/v1/exchanges/home/executors \
-H "Authorization: Bearer mess_home_abc123..."
Response:
{
"executors": [
{
"id": "my-phone",
"display_name": "My Phone",
"capabilities": ["photo:capture", "location:indoor"],
"last_seen": "2026-02-01T22:00:00Z",
"created_at": "2026-02-01T20:00:00Z"
}
]
}
Update Executor Profile¶
PATCH /api/v1/exchanges/{exchange_id}/executors/{executor_id}
Can only update your own profile.
curl -X PATCH http://localhost:3000/api/v1/exchanges/home/executors/my-phone \
-H "Authorization: Bearer mess_home_abc123..." \
-H "Content-Type: application/json" \
-d '{
"display_name": "Teague Phone",
"capabilities": ["photo:capture", "location:indoor", "location:outdoor"],
"notifications": [
{"type": "ntfy", "topic": "mess-alerts"},
{"type": "slack", "webhook_url": "https://hooks.slack.com/..."}
],
"preferences": {
"min_priority": "normal",
"quiet_hours": {"enabled": true, "start": "22:00", "end": "07:00"}
}
}'
Notification Types¶
| Type | Configuration |
|---|---|
ntfy |
{"type": "ntfy", "topic": "your-topic", "server": "https://ntfy.sh"} |
slack |
{"type": "slack", "webhook_url": "https://hooks.slack.com/..."} |
google_chat |
{"type": "google_chat", "webhook_url": "https://chat.googleapis.com/..."} |
webhook |
{"type": "webhook", "url": "https://your-server.com/notify"} |
Deployment¶
Docker¶
Kubernetes¶
helm install mess-exchange deploy/helm \
--set image.repository=your-registry/mess-exchange \
--set ingress.enabled=true
Cloudflare Workers¶
Environment Variables¶
| Variable | Description | Default |
|---|---|---|
PORT |
Server port | 3000 |
STORAGE_TYPE |
filesystem or s3 |
filesystem |
STORAGE_PATH |
Path for filesystem storage | ./data |
S3_ENDPOINT |
S3/MinIO endpoint | - |
S3_BUCKET |
S3 bucket name | - |
S3_ACCESS_KEY |
S3 access key | - |
S3_SECRET_KEY |
S3 secret key | - |
Event Storage¶
All events are stored as JSON lines, partitioned by date:
Query with DuckDB:
SELECT * FROM read_json_auto('data/events/**/*.jsonl', hive_partitioning=true)
WHERE exchange_id = 'home' AND thread_ref IS NOT NULL;
Error Responses¶
| Status | Meaning |
|---|---|
400 |
Bad request (missing required field) |
401 |
Unauthorized (invalid or missing API key) |
403 |
Forbidden (wrong exchange or executor) |
404 |
Not found (thread or executor doesn't exist) |
409 |
Conflict (executor already registered) |
500 |
Internal server error |
Example error response: