# Instagram, Facebook and Threads

Source: https://postgale.com/docs/instagram-facebook

DM settings (ice breakers, persistent menu, greeting, Get Started), interactive DMs and private replies, reactions, typing and handover, Facebook reviews, Page info and analytics, and Instagram stories, tags, mentions and follow status through the Postgale API and MCP server.

Beyond publishing, comments and direct messages, Postgale covers the rest of what Meta's APIs offer for Instagram (Instagram API with
Instagram Login), Facebook Pages and Threads. Paths and field names follow Zernio's, so a client written for Zernio works by changing the
base URL to `https://postgale.com/api/v1`. On the [MCP server](https://postgale.com/docs/mcp) the same operations are `meta_account_settings`, `instagram_lookup`,
`facebook_page_data`, `list_reviews`, `reply_to_review` and `meta_conversation_action`, plus the cross-platform tools
`moderate_comment` (private replies), `send_message` (`quickReplies`, `buttons`), `send_typing`, `delete_conversation`, `like_post`,
`pin_post`, `list_platform_posts` and `reply_to_mention`.

Every change that reaches Instagram or Facebook (settings, Page info, review replies, private replies, reactions, handover, likes) is
**queued for human approval** while approval applies to the key, the organization or the account: the answer is
`202 {id, status: "pending_approval"}` and nothing reaches Meta until a person approves it. Reads are never queued. Typing indicators are not
queued: they publish nothing.

## DM settings

What people see when they open a conversation with the account. Each setting has `GET`, `PUT` (replaces every locale) and `DELETE`.

| Path | Platforms | Body of `PUT` |
|---|---|---|
| `/accounts/{id}/instagram-ice-breakers` | Instagram | `ice_breakers`: up to 4 `{question (≤80), payload}` |
| `/accounts/{id}/messenger-ice-breakers` | Facebook | `ice_breakers`: `[{locale, call_to_actions: [{question, payload}]}]`, one locale `default` |
| `/accounts/{id}/messenger-menu` | Facebook, Instagram | `persistent_menu`: `[{locale, composer_input_disabled?, call_to_actions}]`, up to 3 items (`postback`, `web_url`, or `nested` with up to 5) |
| `/accounts/{id}/messenger-greeting` | Facebook | `greeting`: `[{locale, text (≤160)}]`; `{{user_first_name}}` is personalized |
| `/accounts/{id}/messenger-get-started` | Facebook | `{payload}` |

```bash
curl -X PUT https://postgale.com/api/v1/accounts/acc_ig/instagram-ice-breakers \
  -H 'Authorization: Bearer sk_…' \
  -H 'Content-Type: application/json' \
  -d '{"ice_breakers":[{"question":"Do you ship to Canada?","payload":"SHIPPING"},{"question":"Where is my order?","payload":"ORDER_STATUS"}]}'
```

A tap sends the `payload` as a postback, which arrives as `message.received`. Facebook shows a persistent menu only on a Page with a Get
Started button, and refuses deleting Get Started while a menu is set.

## Facebook Page info

`GET /accounts/{id}/facebook-page-info` returns `about`, `description`, `website`, `phone`, `emails` and `hours`
(`{day}_{1|2}_{open|close}`, e.g. `mon_1_open: "09:00"`). `PATCH` sends only the fields in the body (`""` clears a text field) and returns
the info read back from Meta.

## Interactive DMs and private replies

`POST /inbox/conversations/{id}/messages` with `text` plus `quickReplies` (up to 13 chips) or `buttons` (1 to 3, `postback` with
`payload` or `url`), inside the 24-hour window:

```bash
curl -X POST https://postgale.com/api/v1/inbox/conversations/conv_123/messages \
  -H 'Authorization: Bearer sk_…' \
  -H 'Content-Type: application/json' \
  -d '{"text":"Which size?","quickReplies":[{"title":"S"},{"title":"M"},{"title":"L"}]}'
```

A private reply DMs the author of a comment: one per comment, within 7 days of it. The conversation with the commenter is stored and
`message.sent` fires.

```bash
curl -X POST https://postgale.com/api/v1/inbox/comments/17895695668004550/17858893269000001/private-reply \
  -H 'Authorization: Bearer sk_…' \
  -H 'Content-Type: application/json' \
  -d '{"accountId":"acc_ig","message":"Sent you the link!"}'
```

The first segment is the post (media) id and the second the comment id, as in Zernio (`/inbox/comments/{commentId}/private-reply`
also works).

## Conversation actions

| Call | What it does |
|---|---|
| `POST` / `DELETE /inbox/conversations/{id}/messages/{messageId}/reactions` | React with any emoji (`{emoji}`) or remove the account's reaction. `messageId` is the Postgale or the platform message id |
| `POST /inbox/conversations/{id}/typing` | Typing indicator; always 200, `success` says whether it was sent |
| `POST /inbox/conversations/{id}/accept` | Accept a Message Request by replying `{message}` (Meta has no separate accept call) |
| `POST /inbox/conversations/{id}/thread-control` | Handover: `pass` (with `targetAppId`; Page Inbox is `263902037430900`), `take`, `request`, `release`. Instagram Login cannot `take` or `request` |
| `PUT /inbox/conversations/{id}` | Archive or activate (`{status}`) |
| `DELETE /inbox/conversations/{id}` | Delete Postgale's copy of the conversation and its messages (nothing changes on the platform) |

## Facebook reviews

`GET /inbox/reviews` lists the reviews (recommendations) of every connected Page, or of one with `accountId`, with `minRating`,
`maxRating`, `hasReply`, `sortBy` (`date`, `rating`) and `sortOrder`. `POST /inbox/reviews/{reviewId}/reply` publishes the Page's reply.
Deleting a reply is a Google Business Profile feature and answers 400 on Facebook.

## Facebook analytics

| Path | Returns |
|---|---|
| `GET /analytics/facebook/page-insights?accountId&fromDate&toDate&metrics&metricType` | Page metrics (default `page_media_view`, `page_post_engagements`, `page_follows`), at most 89 days |
| `GET /analytics/facebook/post-earnings?accountId&postId` | Lifetime monetization earnings of a post (`content_monetization_earnings` in `micro_amount` with a currency) |
| `GET /accounts/{id}/facebook-post-reactions?postId` | Count per reaction type (like, love, haha, wow, sad, angry, care) and the total |
| `GET /accounts/{id}/facebook-tagged-posts` | Public posts that tag the Page (`limit`, `cursor`) |
| `GET /accounts/{id}/facebook-visitor-posts` | Posts other people published on the Page |

A metric Meta does not return is listed in `unavailableMetrics` instead of being reported as 0.

## Instagram reads

| Path | Returns |
|---|---|
| `GET /accounts/{id}/instagram/stories` | Active stories |
| `GET /accounts/{id}/instagram/stories/{storyId}/insights` | Story metrics; `source` is `live`, `cached` (from Meta's `story_insights` webhook after the story expired) or `unavailable` |
| `GET /accounts/{id}/instagram/tags` | Media the account is photo-tagged in |
| `GET /accounts/{id}/follow-status/{userId}` | Whether a person (IGSID from a webhook) follows the account; Meta answers only for people who messaged it, else `isFollower: null` and `unavailableReason` |
| `GET /accounts/{id}/instagram/publishing-limit` | `{quotaUsage, quotaTotal, quotaDurationSeconds}` |

Mentioned media and comments (`/instagram/mentions/media/{mediaId}`, `/instagram/mentions/comments/{commentId}`), business discovery
(`/instagram/business-discovery?username`), the audio catalog (`/instagram/audio`, `/instagram/audio/{audioId}`), live media
(`/instagram/live-media`), media collaborators (`/instagram/media/{mediaId}/collaborators`) and likes are served by Meta only to Instagram
accounts connected through Facebook Login. Postgale connects Instagram with Instagram Login, so these answer 400 with a code such as
`instagram_business_discovery_requires_facebook_login` (in mock mode they return sample data).

## Posts and mentions

- `GET /accounts/{id}/posts`: the account's latest posts as they are on Instagram, Facebook or Threads (also used by
  `POST /posts/sync-external`).
- `POST` / `DELETE /inbox/posts/{postId}/like` with `{accountId}`: like or unlike a post as a Facebook Page. Instagram needs Facebook
  Login and Threads has no like API: both answer 400.
- `POST /posts/{id}/pin` and `/unpin`: pin a published post to the top of the Facebook Page.
- `POST /inbox/mentions/reply` `{accountId, mediaId, commentId?, message}`: reply where the Instagram account was @mentioned.

## Threads

Replies can be listed, answered, hidden and deleted (`DELETE /inbox/comments/{commentId}?accountId`, which needs `threads_delete`).
New replies to the account's threads arrive as `comment.received`.

## Webhooks

Besides `message.received`, `message.sent`, `message.read`, `message.delivered` and `message.reaction`:

- `reaction.received`: the person added or removed a reaction (sent alongside `message.reaction`).
- `message.edited`: the person edited a message; `text` is the new text and `previousText` the old one (Instagram and Messenger).
- `message.deleted`: the person unsent a message on Instagram; `text` and `attachments` carry the original content.

Edits need the `message_edit` (Instagram) and `message_edits` (Facebook) webhook fields on the account's subscription
(`POST /accounts/{id}/webhook-subscription`).
