Skip to content
SocialMate

SocialMate Local API

Media

Browse the media archive, stream files and thumbnails, trigger downloads, and clean up.

SocialMate keeps a local archive of media seen across your chats. Browse it, pull files and thumbnails through the tunnel, trigger downloads, and clean up. File paths are never exposed — each item instead carries a stable apiPath you can fetch.

GET /v1/accounts/{id}/media

scope: readFree

Filtered, paginated archive list. Query params: chatId, type, state, direction, search, sort (newest|oldest|biggest|smallest), limit (max 500), offset, pinnedOnly, downloadedOnly. Each row includes apiPath and thumbnailApiPath (or null until downloadable).

curl "http://localhost:3456/v1/accounts/ACCOUNT_ID/media?type=image&downloadedOnly=true&limit=50" \
  -H "x-api-key: YOUR_KEY"
{ "data": [ { "id": "med_1", "type": "image", "state": "downloaded", "apiPath": "/v1/accounts/a1b2c3/media/med_1/file", "thumbnailApiPath": "/v1/accounts/a1b2c3/media/med_1/thumbnail" } ], "pagination": { "limit": 50, "offset": 0, "total": 1 } }
GET /v1/accounts/{id}/media/stats

scope: readFree

Counts and total size by type/state for the account's archive.

curl http://localhost:3456/v1/accounts/ACCOUNT_ID/media/stats \
  -H "x-api-key: YOUR_KEY"
{ "data": { "total": 128, "downloaded": 110, "pending": 18, "totalBytes": 10485760, "byType": [ { "type": "image", "count": 90, "bytes": 8000000, "downloaded": 80 }, { "type": "video", "count": 20, "bytes": 2400000, "downloaded": 18 }, { "type": "document", "count": 18, "bytes": 85760, "downloaded": 12 } ] } }
GET /v1/accounts/{id}/media/{mediaId}

scope: readFree

One item's metadata (no filesystem path). 404 if it isn't on this account.

curl http://localhost:3456/v1/accounts/ACCOUNT_ID/media/med_1 \
  -H "x-api-key: YOUR_KEY"
{ "data": { "id": "med_1", "type": "image", "state": "downloaded", "sizeBytes": 81920, "apiPath": "/v1/accounts/a1b2c3/media/med_1/file" } }
GET /v1/accounts/{id}/media/{mediaId}/file

scope: readFree

Stream the raw file. Honours Range (206 partial content) for video scrubbing. Returns 409 with the current state if the file isn't downloaded yet. This is a binary response — not wrapped in the JSON envelope.

curl http://localhost:3456/v1/accounts/ACCOUNT_ID/media/med_1/file \
  -H "x-api-key: YOUR_KEY" -o photo.jpg
GET /v1/accounts/{id}/media/{mediaId}/thumbnail

scope: readFree

A JPEG thumbnail (cached an hour). 404 if none is available. Binary JPEG response.

curl http://localhost:3456/v1/accounts/ACCOUNT_ID/media/med_1/thumbnail \
  -H "x-api-key: YOUR_KEY" -o thumb.jpg
POST /v1/accounts/{id}/media/{mediaId}/download

scope: sendPro

Force a media item to download so it's locally cached before you fetch it.

curl -X POST http://localhost:3456/v1/accounts/ACCOUNT_ID/media/med_1/download \
  -H "x-api-key: YOUR_KEY"
{ "data": { "success": true, "state": "queued" } }

A freshly forced item reports state: "queued" (or downloading if a download is already in flight); poll GET …/media/{mediaId} until state is downloaded and apiPath is non-null before fetching the file.

PUT /v1/accounts/{id}/media/{mediaId}/context

scope: sendPro · agentMemory

Agent Memory. Cache the description or transcript your AI produced for this media (image / video / voice note / document), so the same file is never re-analyzed. The cached text then rides GET /messages and GET /ai-context (a media turn reads as [image: <what it shows>]) and fires a media.context_updated webhook. SocialMate ships no model and never generates this — it is a passive store, like the WhatsApp caption. Works for received and sent media. Pass overwrite:false to get a 409 instead of replacing existing context. To find items still needing analysis, list media with ?hasContext=false (each item also carries a needsContext flag).

curl -X PUT http://localhost:3456/v1/accounts/ACCOUNT_ID/media/med_1/context \
  -H "x-api-key: YOUR_KEY" -H "Content-Type: application/json" \
  -d '{ "context": "A signed invoice #A-1001, total $420, due 2026-08-01.", "source": "your-vision-model" }'
{ "data": { "success": true, "media": { "id": "med_1", "state": "downloaded",
  "apiPath": "/v1/accounts/ACCOUNT_ID/media/med_1/file",
  "aiContext": "A signed invoice #A-1001, total $420, due 2026-08-01.",
  "aiContextSource": "your-vision-model", "needsContext": false } } }
DELETE /v1/accounts/{id}/media/{mediaId}

scope: adminPro

Remove the cached file for an item.

curl -X DELETE http://localhost:3456/v1/accounts/ACCOUNT_ID/media/med_1 \
  -H "x-api-key: ADMIN_KEY"
{ "data": { "success": true } }

Download queue & cleanup

GET /v1/media/queue

scope: readFree

The media-downloader's current status (in-flight, pending, failed).

curl http://localhost:3456/v1/media/queue \
  -H "x-api-key: YOUR_KEY"
{ "data": { "active": 1, "pending": 4, "failed": 0 } }

Webhook timing. Webhooks that carry media — message.received, message.sent, and media.discovered — are held until the download settles (usually a few seconds, with a ~2-minute backstop), so their media slice arrives state: "downloaded" with a ready-to-fetch local apiPath — never the WhatsApp URL. See Webhook events.

POST /v1/media/cleanup

scope: adminPro

Run archive cleanup now (an ops convenience — Free users rely on the app's scheduled cleanup). Pro only.

curl -X POST http://localhost:3456/v1/media/cleanup \
  -H "x-api-key: ADMIN_KEY"
{ "data": { "timeEvictions": 30, "quotaEvictions": 12, "bytesFreed": 5242880 } }

timeEvictions are files aged out past the retention window; quotaEvictions are the LRU evictions made to get back under the storage quota.

Free forever · no card Download free