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

NameRequiredTypeRangeDefault
limitnointeger1–5010

A value outside the range returns 400:

  • limit=0 or limit=-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

FieldTypeNotes
appIdstringServer-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.
contactIdstringSender identifier in the messenger’s native format.
messageIdstring32-char hex; chat-handler-assigned inbound id.
contentstringMessage body.
timestampnumber (ms)Falls back to Date.now() if the inbound payload omits it.
groupIdstring?Present iff isGroupMessage is true.
isGroupMessagebooleanTrue for group messages.
isCommandbooleanTrue if the inbound was a /command recognized by PrivacyFlow.
commandstring?The command name when isCommand is true.

Status codes

CodeWhen
200Success. Empty queue returns messages: [], count: 0.
400Invalid limit.
401Missing header, wrong-header-for-credential-type, or no appIds mapped.
403Key recognized but invalid (intentional split from /auth/verify’s 401). See Errors & Limits.
429Rate-limited. X-RateLimit-Remaining: 0 and X-RateLimit-Reset: <epochMs>.
503Redis 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 limit messages from each poll:{appId} queue, round-robins them into a single response, and stops when limit is 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: true and groupId. Reply with the same groupId on send to land back in the thread.
  • Commands like /signin are consumed internally and surface with isCommand: 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.

Last updated: