PostZen

Inbox

Read and reply to comments on Instagram, Facebook, and Threads, and manage Instagram direct messages, from the dashboard or the API.

The inbox brings the conversations that happen around your posts into PostZen. It has two halves: Comments on Instagram, Facebook Page, and Threads posts, and Messages for Instagram direct messages. Both are available in the dashboard at app.postzen.dev/inbox, through the REST API under /v1/inbox, and as tools on the MCP server.

CommentsMessages
PlatformsInstagram, Facebook Pages, ThreadsInstagram
ReadEvery comment on a post, with nested repliesEvery thread on the account, with its messages
WriteComment, reply, hide, unhide, deleteSend text or an attachment, archive, mark read
Source of truthThe platform. PostZen refreshes its copy on every read.The platform, refreshed on a throttle. Read state and archive state are PostZen-local.

Accounts connected before the inbox launched do not hold the new permissions. If a connected account shows no comments or messages, reconnect it from the Connections page and the permissions are granted on the next login.

How syncing works

The inbox is read-through. When you list comments or conversations, PostZen asks the platform, stores what comes back, and returns it. There is nothing to configure and no separate sync job to run. Two details follow from that:

  • Reads are throttled per account. A conversation list is refreshed from Instagram at most once every five minutes, and a single thread's messages at most once every thirty seconds. Polling more often than that returns the stored copy and costs nothing at the platform. Comment reads refresh on every call.
  • Posts come from the platform, not just from PostZen. The comments inbox lists every post on the connected account, including ones published elsewhere, so you can moderate a whole account rather than only the posts PostZen created.

Writes go straight to the platform and are hard-gated on the permission the account actually granted. If an account is missing the permission a write needs, the API answers 403 platformCapabilityMissing before anything is sent.

Comments

Comments cover Instagram, Facebook Pages, and Threads. Each platform exposes a different set of actions, and PostZen reports what the connected account can do per comment through the canReply, canHide, and canDelete flags.

ActionInstagramFacebook PageThreads
Read comments and repliesYesYesYes
Comment on a postYesYesYes
Reply to a commentYes, one level deepYesYes
Hide and unhideYesYes, per commentTop-level replies on your own posts only
DeleteYesYes, per commentNot available
Image attachment on a replyNoYes, by public URLNo

Platform behavior worth knowing before you build on it:

  • Instagram threads are two levels deep. A comment can have replies, but a reply cannot. Replying to a reply is redirected to its top-level parent, and the API tells you the redirect happened.
  • Threads replies are published in two steps. A reply is created as a container and then published. When the publish is still pending after PostZen's internal retries, the call returns 502 with a publishPending code. The reply usually appears on Threads shortly after, so check before sending it again.
  • Hiding on Threads cascades. Hiding a top-level reply also hides everything beneath it, and unhiding restores the branch.
  • Fields a platform does not report are absent, not zero. Threads never reports per-reply like counts, for example.

List comments

Comments are read per post. Pass the PostZen post id and the account that owns it. To page through a comment's replies when the first page did not include them all, pass that comment's id as commentId.

curl "https://api.postzen.dev/v1/inbox/comments/$POST_ID?accountId=$ACCOUNT_ID&limit=25" \
  -H "Authorization: Bearer $POSTZEN_API_KEY"

Each comment carries its id, text, author, timestamp, the counts the platform reports, whether it is hidden, its capability flags, and a first page of replies where the platform returns them cheaply. repliesHasMore tells you when it did not.

Reply, hide, and delete

Replying and commenting share one endpoint. Omit commentId to comment on the post itself, or set it to reply to a specific comment.

curl -X POST "https://api.postzen.dev/v1/inbox/comments/$POST_ID" \
  -H "Authorization: Bearer $POSTZEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "accountId": "'"$ACCOUNT_ID"'", "commentId": "17900000000000001", "message": "Thanks, glad you liked it!" }'

Hide and unhide are a POST and a DELETE on the same path, and delete takes the comment id as a query parameter:

# Hide
curl -X POST "https://api.postzen.dev/v1/inbox/comments/$POST_ID/$COMMENT_ID/hide" \
  -H "Authorization: Bearer $POSTZEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "accountId": "'"$ACCOUNT_ID"'" }'

# Unhide
curl -X DELETE "https://api.postzen.dev/v1/inbox/comments/$POST_ID/$COMMENT_ID/hide?accountId=$ACCOUNT_ID" \
  -H "Authorization: Bearer $POSTZEN_API_KEY"

# Delete
curl -X DELETE "https://api.postzen.dev/v1/inbox/comments/$POST_ID?accountId=$ACCOUNT_ID&commentId=$COMMENT_ID" \
  -H "Authorization: Bearer $POSTZEN_API_KEY"

All three writes need a Read & Write key. Reads work with either key type.

Messages

Messages cover Instagram direct messages. PostZen keeps a copy of every thread it has seen on the account, so listing and searching are fast, and the copy is refreshed from Instagram on the throttle described above.

A few rules come from Meta rather than from PostZen:

  • You can only reply inside 24 hours. Meta accepts a message only within 24 hours of the other person's last message. Outside that window the platform refuses the send and PostZen returns 400 PLATFORM_LIMITATION with Meta's own error attached.
  • One payload per message. A send carries either text or one attachment, never both. A request with both is rejected with a 400 rather than sending half of it.
  • Older messages lose their content. Meta returns full text and attachments for roughly the twenty newest messages in a thread. Older ones still list with an id and timestamp.
  • There is no unread count from the platform. Instagram does not report one for these threads, so PostZen tracks read state itself. Reading a thread never marks it read. You do that explicitly, and the API tells you how many messages crossed from unread to read.
  • Archiving is local. Meta has no archive API. Archiving a thread in PostZen hides it from the active list and a later sync never un-archives it.

List and search conversations

# Active threads, newest activity first
curl "https://api.postzen.dev/v1/inbox/conversations?status=active&limit=25" \
  -H "Authorization: Bearer $POSTZEN_API_KEY"

# Search message text and participant names
curl "https://api.postzen.dev/v1/inbox/conversations/search?query=refund&direction=incoming" \
  -H "Authorization: Bearer $POSTZEN_API_KEY"

Omit accountId and PostZen queries every Instagram account the key can reach. The response's meta reports any account that could not be refreshed, and the stored threads for that account are still returned. Search matches whole words in message text, case- and accent-insensitively, and matches participant names on any substring.

Read and send messages

# Messages in a thread, oldest first
curl "https://api.postzen.dev/v1/inbox/conversations/$CONVERSATION_ID/messages?accountId=$ACCOUNT_ID" \
  -H "Authorization: Bearer $POSTZEN_API_KEY"

# Reply with text
curl -X POST "https://api.postzen.dev/v1/inbox/conversations/$CONVERSATION_ID/messages" \
  -H "Authorization: Bearer $POSTZEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2c1e-3f8b-4d1e-9c0a-2d7d4a6b8e10" \
  -d '{ "accountId": "'"$ACCOUNT_ID"'", "message": "Your order shipped this morning." }'

# Mark the thread read
curl -X POST "https://api.postzen.dev/v1/inbox/conversations/$CONVERSATION_ID/read" \
  -H "Authorization: Bearer $POSTZEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "accountId": "'"$ACCOUNT_ID"'" }'

The recipient is always the other participant on the thread, so no recipient id is needed. Send an Idempotency-Key header on every send: retrying with the same key and body replays the stored outcome and sets Idempotent-Replayed: true, so a network retry never double-sends. The same key with a different body is a 422.

A 502 from a send means the request to Meta did not come back, so PostZen cannot tell whether the message was delivered. It is deliberately not retried for you. Check the thread before sending again.

Send an attachment

Meta fetches attachments by URL, so the file has to be publicly reachable. Upload it with POST /v1/media/upload-direct to get a URL that qualifies, then send it with an attachmentType.

# 1. Upload the file (multipart, up to 25 MB)
curl -X POST https://api.postzen.dev/v1/media/upload-direct \
  -H "Authorization: Bearer $POSTZEN_API_KEY" \
  -F "file=@receipt.pdf"

# 2. Send it
curl -X POST "https://api.postzen.dev/v1/inbox/conversations/$CONVERSATION_ID/messages" \
  -H "Authorization: Bearer $POSTZEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "accountId": "'"$ACCOUNT_ID"'", "attachmentUrl": "<url from step 1>", "attachmentType": "file" }'

attachmentType is one of image, video, audio, or file. Files uploaded this way are deleted after seven days. That is long enough for Meta to fetch them and short enough that the endpoint is not a substitute for POST /v1/media/presign when publishing posts.

Automate replies to comments

Comment automations turn the comments inbox into a trigger: when a comment on a post matches a keyword, PostZen sends the commenter a DM and can post a public reply, without anyone watching the inbox. They are configured per account through /v1/comment-automations and every trigger is logged. See the comment automations guide.

Errors

The inbox uses the same status codes as the rest of the API, with a few that are specific to talking to a platform on your behalf.

StatusMeaning
403 platformCapabilityMissingThe account never granted the permission this write needs. Reconnect the account to grant it.
404 postNotFoundThe post is not on that account, or a PostZen post has not published there yet.
424 connectionDeadThe account's connection needs to be reauthorized before its inbox can be read or written.
429The platform rate limited the upstream request. Retry-After gives the wait.
502The platform could not answer, or a send did not come back. platformError carries the platform's wording when there is one.

Rate limits

Inbox writes have per-operation ceilings on top of your plan's per-minute request limit, so a runaway loop cannot spend the account's platform quota.

OperationLimit per hour
Comment or reply120
Hide or unhide120
Delete a comment60
Send a message120
Archive or unarchive120
Mark read300
Direct media upload60

Meta applies its own limits underneath these. Instagram in particular caps comment and message writes per account, and those come back as 429 with the platform's wording.

Dashboard

The dashboard exposes the same inbox at app.postzen.dev/inbox. Comments lists every post on your connected Instagram, Facebook, and Threads accounts, filterable by profile, platform, and account, and opens into the post's thread where you can reply, hide, and delete. Messages lists your Instagram threads with search, the same filters, and a conversation pane for replying with text or an attachment. Opening a thread in the dashboard marks it read.

On this page