The msgviz backend is a thin FastAPI layer over the local SQLite DB. It powers the bundled frontend but is also usable as a pure data API for your own clients.
- Base URL:
http://127.0.0.1:8753/in standalone mode. In embedded mode (see EMBEDDING.md), the base URL is<host>/<mount-prefix>/. - Format: JSON (request + response).
- Read-only DB connection for all GETs. POSTs use a separate write connection.
- Pagination via
?limit=andbefore/since/{ts}. Default 50, maximum 500. - Chat slugs:
<device-slug>/<chat-slug>, e.g.my_mac/bob.
Overview of devices and chats. Loaded by the index frontend.
Response:
{
"devices": [
{
"id": 1,
"slug": "my_mac",
"name": "MacBook",
"me_name": "Alice",
"owner_avatar": "media/avatars/1c/1ca2e4524f921933.jpg"
}
],
"chats": [
{
"slug": "my_mac/bob",
"title": "Bob",
"subtitle": "+49 1234 567890 · iMessage",
"origin": "apple",
"device": "MacBook",
"device_slug": "my_mac",
"me_name": "Alice",
"is_group": false,
"live": true,
"total": 1024,
"me": 600,
"them": 424,
"new_count": 3,
"first": 1700000000,
"last": 1730000000,
"media": { "image": {"me": 12, "them": 8}, "video": {...}, "audio": {...}, "other": {...} },
"bytes": { "image": 134217728, "video": 268435456, "audio": 0, "other": 0 },
"bytes_total": 402653184,
"bytes_orig": 0,
"chat_avatar": "media/avatars/78/78b575cd78bf518f.jpg"
}
]
}live: true marks iMessage chats on a mac_live device.
new_count is the number of messages received since the last /seen
call — it drives the "unread" badge in the frontend.
Avatars (optional, only present when set):
owner_avataron a device — the device owner's avatar, relative path from the project root (e.g.media/avatars/<prefix>/<hash>.jpg).chat_avataron a 1:1 chat — the counterpart's avatar. Group chats and 1:1 chats whose counterpart has no avatar omit the field.
Resolve avatar paths against the server root (<base>/<avatar_src>).
All chat endpoints accept {slug:path}, so slashes inside the slug are
allowed — encode each path segment with encodeURIComponent.
Chat header data: title, subtitle, statistics, media overview.
{
"slug": "my_mac/bob",
"title": "Bob",
"subtitle": "+49 1234 567890",
"origin": "apple",
"device": "MacBook",
"me_name": "Alice",
"is_group": false,
"stats": {
"total": 1024,
"me": 600,
"them": 424,
"first": 1700000000,
"last": 1730000000,
"media": { ... },
"bytes": { ... },
"bytes_total": 402653184
},
"owner_avatar": "media/avatars/1c/1ca2e4524f921933.jpg",
"chat_avatar": "media/avatars/78/78b575cd78bf518f.jpg"
}owner_avatar and chat_avatar are present only when set (see
GET /api/index).
Most recent N messages, chronological (oldest first).
{
"messages": [
{
"t": "msg",
"ts": 1730000000,
"me": false,
"sender": "Bob",
"sender_avatar": "media/avatars/78/78b575cd78bf518f.jpg",
"text": "Hello!",
"edits": [{ "text": "Hi", "ts": 1729999990 }],
"reactions": [{ "emoji": "❤️", "sender": "Alice" }],
"media": [{ "kind": "image", "src": "media/images/ab/abc123.jpg" }]
}
],
"has_more": true
}Optional fields: sender_avatar, edits, reactions, apps,
retracted, media. sender_avatar is omitted when the sender's
person has no avatar set — the frontend then renders initials.
N messages older than ts. For scroll-up pagination.
All messages newer than ts. For live polling.
Block around a timestamp (jumping from heatmap to a date).
All edited messages of a chat, regardless of the currently loaded
window. Also returns total.
Per-day histogram over the entire history. Used by the heatmap.
{
"days": {
"2025-01-15": 12,
"2025-01-16": 3,
...
}
}All media of a chat, chronological. For the media overview.
{
"media": [
{
"kind": "image",
"src": "media/images/ab/abc123.jpg",
"me": false,
"sender": "Bob",
"ts": 1730000000,
"cap": "Check this!",
"cat": "selfie",
"portrait": true
}
]
}Marks every message in the chat with sync_state='new' as
'published'. The frontend calls this when a chat is opened, clearing
the unread badge.
{ "ok": true, "marked_seen": 3 }Live push for new messages (mac_live sources) and DB connection status.
Server push types:
{ "type": "dbstatus", "online": true }
{ "type": "update", "chats": [{"slug": "my_mac/bob", "new": 2, "last": 1730000123}] }Clients don't have to send anything — all messages come from the server.
dbstatus is broadcast when Apple's chat.db reachability changes,
update after a sync round that produced new messages.
On non-Darwin systems the watcher doesn't run — the WebSocket stays
open but only emits the initial dbstatus.
| Path | Content |
|---|---|
/ |
Index HTML (rendered with the base placeholder) |
/chat/{slug} |
Chat template HTML |
/favicon.ico |
App icon |
/app/... |
CSS, JS, fonts, icons |
/data/... |
Frontend data files (transcripts.json, ocr.json) |
/media/... |
Hash-based media (images/audio/video) |
/originals/... |
Original resolutions if stored |
Mount paths are configurable in embedded mode (see EMBEDDING.md).
The API is not designed for public exposure:
- No CORS headers.
- No authentication.
- No TLS termination in uvicorn (use a reverse proxy like Caddy locally).
If you expose the server publicly, it's your responsibility to add an auth layer — easiest as FastAPI middleware in the host app (see EMBEDDING.md).