Poll messages
GET /api/v1/messages/poll — destructive read of inbound messages, with optional limit and per-credential rate limiting.
GET /api/v1/messages/poll pops inbound messages off your queue and returns them. This is the only way to receive messages — PrivacyFlow is poll-only. Polling is a destructive read: once popped, the message will not be returned again.
Request
GET /api/v1/messages/poll?limit=10 HTTP/1.1
Host: privacyflow.app
Authorization: Bearer pf_live_your_key_here
Auth required. The only supported parameter is limit.
Query parameters
| Name | Required | Type | Range | Default |
|---|---|---|---|---|
limit | no | integer | 1–50 | 10 |
A value outside the range returns 400:
limit=0orlimit=-1→ 400"Invalid limit. Must be between 1 and 50".limit=51→ 400"Invalid limit. Must be between 1 and 50".- Non-numeric → 400
"Invalid limit parameter".
curl
curl -s "https://privacyflow.app/api/v1/messages/poll?limit=10" \
-H "Authorization: Bearer $PRIVACYFLOW_API_KEY"
Response
200 OK:
{
"messages": [
{
"appId": "app_123",
"messenger": "signal",
"contactId": "+12025550104",
"messageId": "5dd6c2c0d5d7b3a9b0c1a2c3d4e5f6a7",
"content": "Hi, I need help with my order",
"timestamp": 1718000000000,
"isGroupMessage": false,
"isCommand": false
}
],
"count": 1,
"queue": "poll:app_123"
}
When your key covers more than one appId, queue reads poll:app_123,app_456 (multi) and the response contains messages round-robined across both queues.
PolledMessage schema
| Field | Type | Notes |
|---|---|---|
appId | string | Server-stamped from the credential’s appIds[]. The inbound payload does not carry it; the API injects it for the client. |
messenger | "signal" | "simplex" | "session" | Pass this back unchanged on send. |
contactId | string | Sender identifier in the messenger’s native format. |
messageId | string | 32-char hex; chat-handler-assigned inbound id. |
content | string | Message body. |
timestamp | number (ms) | Falls back to Date.now() if the inbound payload omits it. |
groupId | string? | Present iff isGroupMessage is true. |
isGroupMessage | boolean | True for group messages. |
isCommand | boolean | True if the inbound was a /command recognized by PrivacyFlow. |
command | string? | The command name when isCommand is true. |
Status codes
| Code | When |
|---|---|
200 | Success. Empty queue returns messages: [], count: 0. |
400 | Invalid limit. |
401 | Missing header, wrong-header-for-credential-type, or no appIds mapped. |
403 | Key recognized but invalid (intentional split from /auth/verify’s 401). See Errors & Limits. |
429 | Rate-limited. X-RateLimit-Remaining: 0 and X-RateLimit-Reset: <epochMs>. |
503 | Redis poll failure. |
Rate headers on success
Every 200 response includes:
X-RateLimit-Remaining: <int>
Track it locally and back off before you hit 0 — see rate limiting.
Operational model
- One round-trip per call. Each call pops up to
limitmessages from eachpoll:{appId}queue, round-robins them into a single response, and stops whenlimitis reached or queues are exhausted. - No ordering guarantees across appIds or across polls. A single message is delivered once and only once per pop, but if your client crashes after the pop the message is gone.
- Group messages carry
isGroupMessage: trueandgroupId. Reply with the samegroupIdon send to land back in the thread. - Commands like
/signinare consumed internally and surface withisCommand: true— most automation clients skip these (the Agent Zero channel does).
Warning
A successful poll is a destructive operation. Persist the popped messages before doing anything else. The MCP server, Agent Zero channel, and the n8n node all treat the poll response as already-consumed.