API v2 Instagram Channel

Read an agent's Instagram connection and manage its automations and conversation starters.

Use these endpoints to manage what an agent does on Instagram: the four automations (respond to DMs, comment-to-DM, story leads, story mentions), their per-post / per-story instances, and the conversation starters Instagram shows when someone opens a DM for the first time.

Connecting Instagram is not part of the API. The OAuth handshake happens in the dashboard under Publish → Instagram; these endpoints manage an agent that is already connected. Every endpoint requires Authorization: Bearer YOUR_API_KEY, and every response is a bare object without a data envelope.

Requests for an agent with no Instagram connection return 404 INSTAGRAM_NOT_CONNECTED, except GET /channels/instagram, which answers { "connected": false }.

Publishes and clears of the same persistent-menu or conversation-starters resource are serialized per agent. An overlapping PATCH returns 409 INSTAGRAM_SYNC_IN_PROGRESS; retry after the active operation finishes.

Get the Instagram Channel

GET /api/v2/agents/{agentId}/channels/instagram

Returns the connection, all four automations, and the conversation starters in one call.

FieldTypeNotes
connectedbooleanfalse when the agent has no Instagram connection; no other field is present.
idstringConnection ID.
instagram_usernamestring or nullConnected Instagram handle.
instagram_user_idstringInstagram account ID.
facebook_page_namestring or nullLinked Facebook page, when the connection was made with Facebook Login.
profile_picture_urlstring or nullInstagram avatar.
login_kindstringinstagram_login or facebook_login.
statusstringConnection health, for example connected. Anything else means the connection needs to be re-established in the dashboard.
scopesarray of strings or nullPermissions observed through grant readback. null means Meta did not make grant readback available; an empty array means a successful read returned no grants.
last_error_messagestring or nullLast connection error surfaced in the dashboard.
automationsarrayOne entry per automation, always all four.
conversation_startersobjectSame shape as the conversation starters endpoint below.
{
  "connected": true,
  "id": "a0d2b8b6-7d0e-4bd5-9f0a-1e2f3a4b5c6d",
  "instagram_username": "agentkit.ai",
  "instagram_user_id": "17841400000000000",
  "facebook_page_name": null,
  "profile_picture_url": "https://scontent.cdninstagram.com/...",
  "login_kind": "instagram_login",
  "status": "connected",
  "scopes": [
    "instagram_business_basic",
    "instagram_business_manage_messages",
    "instagram_business_manage_comments"
  ],
  "last_error_message": null,
  "automations": [
    { "key": "respond_to_dms", "status": "live", "config": { "mode": "all" } }
  ],
  "conversation_starters": {
    "status": "draft",
    "draft_prompts": [],
    "live_prompts": null,
    "synced_at": null,
    "last_sync_error": null,
    "last_sync_error_code": null
  }
}

Refresh connection permissions

POST /api/v2/agents/{agentId}/channels/instagram/permissions/refresh

The response includes a machine-readable outcome, optional code, lastVerifiedAt, and loginKind. grant_readback_unavailable is the normal steady-state result for Instagram Login: the token identity check passed and webhook repair was submitted, but Meta does not expose grant readback for that login method. lastVerifiedAt records only a previous full grant and subscription readback, not the time of this partial check.

Automations

GET   /api/v2/agents/{agentId}/channels/instagram/automations/{key}
PATCH /api/v2/agents/{agentId}/channels/instagram/automations/{key}

key is one of respond_to_dms, comment_to_dm, story_leads, story_mentions. Any other value returns 404 RESOURCE_NOT_FOUND.

Both methods return { "key", "status", "config" }. An automation the agent has never configured reports its default: respond_to_dms is live (replying to every DM with the agent's AI is the baseline behaviour), the other three are draft.

StatusMeaning
liveRunning on the connected account.
draftSaved but not running; incomplete configurations are allowed.
offExplicitly disabled.

The PATCH body takes config, status, or both — at least one is required.

curl -X PATCH 'https://your-domain.com/api/v2/agents/955f28f1-8515-40bb-802c-f3f730bf0343/channels/instagram/automations/respond_to_dms' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"status": "off"}'

config is the automation's full configuration object, not a patch: send the object you read back from GET, with your edits applied. Each key has its own shape (respond_to_dms takes mode, keywords, message, links, intent and email-capture fields; comment_to_dm takes post scope, keyword filter, public replies, and DM copy — the opening message with its buttonLabel, then the link message and links sent when the button is tapped; and so on). Read the current object first rather than composing one by hand.

Behaviour worth knowing before you automate against this endpoint:

  • A status alone never rewrites your configuration. It flips the stored config on or off.
  • A config alone saves a draft, unless the automation is already live and the new configuration still satisfies the go-live rules — then it stays live. An incomplete edit to a live automation drops it to draft instead of silently changing what the connected account is running.
  • Going live validates. status: "live" with an incomplete configuration returns 400 VALIDATION_INVALID_BODY; details names the missing fields. The one exception is respond_to_dms, which falls back to replying to every DM rather than refusing to turn back on.
  • Comment automations need the comments permission. Setting comment_to_dm live on a connection granted before that permission existed returns 409 INSTAGRAM_RECONNECT_REQUIRED; reconnect the account in the dashboard. Turning it off always works.
  • Email capture is plan-gated. On a plan without the Instagram add-ons, askEmailEnabled is saved as false rather than rejected.
  • comment_to_dm and story_leads can have several rows. This route always addresses the oldest one (the row that existed before instances were introduced) and still returns a single object; the channel response still lists exactly four automations. Use the instance routes below to work with the others. The deprecated postScope: "next" is still accepted on input and stored as "any".

Automation instances

GET   /api/v2/agents/{agentId}/channels/instagram/automations/{key}/instances
POST  /api/v2/agents/{agentId}/channels/instagram/automations/{key}/instances
GET   /api/v2/agents/{agentId}/channels/instagram/automations/{key}/instances/{id}
PATCH /api/v2/agents/{agentId}/channels/instagram/automations/{key}/instances/{id}

key is comment_to_dm or story_leads — the two automations whose trigger names a target (a post or reel id, a story id). Any other key returns 404 RESOURCE_NOT_FOUND. Every instance is { "id", "key", "status", "config" }; the list is ordered oldest first.

An agent may run one live instance per target plus one live catch-all per kind (postScope: "any" / storyScope: "any"). Drafts and off rows are unlimited. When a comment or story reply arrives, the live instance that targets that exact post or story handles it — its keyword filter is the only one evaluated — and the catch-all runs only when no instance targets it.

curl -X POST 'https://your-domain.com/api/v2/agents/955f28f1-8515-40bb-802c-f3f730bf0343/channels/instagram/automations/comment_to_dm/instances' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"config": { "postScope": "specific", "mediaId": "17900000000000001", ... }, "status": "live"}'

POST requires a complete config (read one back from GET and edit it); without status the instance is saved as a draft. PATCH takes config, status, or both, with the same rules as the key route. There is no delete: set status to "off" to retire an instance.

Going live is where cardinality is enforced:

  • 409 INSTAGRAM_AUTOMATION_TARGET_TAKEN — another live instance already targets the same post or story. Turn that one off first, or target a different post.
  • 409 INSTAGRAM_AUTOMATION_CATCH_ALL_EXISTS — a live catch-all for this kind already exists.

Regression guarantees for existing clients: GET /automations/comment_to_dm keeps returning one object (the oldest instance), and GET /channels/instagram keeps returning exactly four automations entries, one per key.

Conversation Starters

GET   /api/v2/agents/{agentId}/channels/instagram/conversation-starters
PATCH /api/v2/agents/{agentId}/channels/instagram/conversation-starters

Conversation starters are the tappable questions Instagram shows the first time someone opens a DM with the account (Meta calls them ice breakers). Each starter carries the scripted answer your agent sends when it is tapped.

FieldTypeNotes
statusstringdraft, live, or off.
draft_promptsarrayUp to 4 objects: question (≤80 characters), message (the scripted reply), links (up to 3 { label, url } buttons).
live_promptsarray or nullThe prompts Meta confirmed are set. null unless the status is live.
synced_atstring or nullISO 8601 timestamp of the last confirmed sync with Meta.
last_sync_errorstring or nullFixed copy for the last sync failure. Meta's message text is never returned.
last_sync_error_codestring or nullAllow-listed machine code for the last sync failure.

Internal synchronization states are not exposed. While a publish or clear is in flight, status continues to report the last confirmed state. After a failed sync it still reports that confirmed state. last_sync_error carries fixed copy derived from last_sync_error_code and may include numeric HTTP or Meta codes.

The PATCH body takes prompts, status, or both — at least one is required.

curl -X PATCH 'https://your-domain.com/api/v2/agents/955f28f1-8515-40bb-802c-f3f730bf0343/channels/instagram/conversation-starters' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
        "prompts": [
          {
            "question": "What does it cost?",
            "message": "Plans start at $19/month — here is the full breakdown.",
            "links": [{ "label": "Pricing", "url": "https://agentkit.ai/pricing" }]
          }
        ],
        "status": "live"
      }'

status: "live" publishes to Instagram, so it is the one call here with an external side effect:

  • Every starter needs a non-empty question and message, and every link URL must be absolute http(s). Otherwise the call returns 400 VALIDATION_INVALID_BODY and nothing is published.
  • Omitting prompts publishes the saved draft.
  • The draft is always saved before Meta is called, so a failed publish never loses your edits.
  • status becomes live only after Meta confirms the prompts and the server reads them back. If the sync fails, the call returns 502 INSTAGRAM_SYNC_FAILED, and the stored status and live_prompts keep describing what is actually on Instagram.
  • A connection that is not connected returns 409 INSTAGRAM_RECONNECT_REQUIRED before any Meta call.

status: "off" clears the starters on Instagram first and only then records off locally, with the same 502 behaviour if the clear fails. Because live means "confirmed on Meta", you cannot move a live set straight back to draft — set it off first, which returns 400 VALIDATION_INVALID_BODY with that instruction if you try.

Sync failures use allow-listed codes and numeric metadata. Meta's message text is never persisted or returned.

{
  "error": {
    "code": "INSTAGRAM_SYNC_FAILED",
    "message": "Instagram rejected the update. (HTTP 400, Meta code 100)",
    "details": {
      "code": "meta_set_failed",
      "http_status": 400,
      "meta_code": 100
    }
  }
}

details.code is one of meta_set_failed, meta_set_unconfirmed, meta_read_failed, meta_clear_failed, meta_clear_unconfirmed, meta_readback_mismatch, meta_subscription_unverified, meta_compensation_unconfirmed, reconnect_required, local_state_write_failed, or unknown_error. The numeric fields are null when unavailable. meta_set_unconfirmed and meta_clear_unconfirmed mean the request never produced a response (timeout or dropped connection); meta_compensation_unconfirmed means the rollback after a failed update could not be verified. In all three the change may or may not have been applied — retry the sync.