Skip to main content
Socialift
Documentation sections
SDK reference

Instagram client: Graph API methods

Instagram is the Instagram Graph API client in the Socialift Python SDK. It supports two connection types: Instagram(app_id, app_secret) uses Instagram Login without a Facebook Page via graph.instagram.com, and Instagram.via_facebook(app_secret=...) uses a Page token from Facebook Login via graph.facebook.com. It reads profiles and media, publishes photos, Reels, carousels and stories, moderates comments, handles Direct conversations and fetches insights.

from socialift import Instagram25 methodsas of socialift 1.3.0Updated:

How do I set up the Instagram client?

Without a Page (Instagram Login): Instagram(app_id=..., app_secret=...). Instagram.from_env() reads INSTAGRAM_APP_ID and INSTAGRAM_APP_SECRET, plus the Graph API version from META_GRAPH_VERSION. Requests go to graph.instagram.com with an Instagram user token; when ig_user_id is omitted, me is used. The keys are in the Instagram settings of your Meta app — see the "Instagram Login setup" guide.

Through a Page: Instagram.via_facebook(app_secret=...). No app_id is needed because OAuth happens in the Facebook client. Use the Page token from Facebook.pages() as the token and Page.instagram_id as the account ID; in this mode ig_user_id is required, otherwise ConfigurationError is raised. Meta app configuration is covered in the "Meta app setup" guide.

In both modes the account must be an Instagram professional account (Business or Creator). When app_secret is set, appsecret_proof is added to every request. You can store keys in the dashboard's Keys section, connect the account under Connections and try the methods in the Playground.

python
import asyncio
import os

from socialift import Instagram

async def main():
    # 1) Instagram Login (no Page), from the environment:
    #    INSTAGRAM_APP_ID, INSTAGRAM_APP_SECRET, META_GRAPH_VERSION
    ig = Instagram.from_env()
    # or: Instagram(app_id=os.environ["INSTAGRAM_APP_ID"],
    #               app_secret=os.environ["INSTAGRAM_APP_SECRET"])

    # 2) Through a Page: Page token plus Page.instagram_id
    # ig = Instagram.via_facebook(app_secret=os.environ["FACEBOOK_APP_SECRET"])

    async with ig:
        profile = await ig.profile(os.environ["IG_TOKEN"])
        print(profile.username, profile.followers_count)

asyncio.run(main())

Connection and tokens

The client in both modes, the Instagram Login URL, token exchange and refresh.

exchange_codefrom_envlogin_urlrefresh_tokenvia_facebook

Exchanges code for a short-lived token at api.instagram.com (POST form data), then for a long-lived one via ig_exchange_token, and returns a TokenBundle. Call it in your Instagram Login callback.

await ig.exchange_code(code: str, *, redirect_uri: str) -> socialift.models.TokenBundle
  • Per Meta, the short-lived token lasts 1 hour and the long-lived token 60 days.
  • Pass exactly the same redirect_uri as in the login URL.
  • Raises ApiError if no short-lived token is returned.

from_env

classmethod

Creates an Instagram Login client from the environment: INSTAGRAM_APP_ID, INSTAGRAM_APP_SECRET and META_GRAPH_VERSION. For the Page-based mode use via_facebook.

Instagram.from_env(**kwargs) -> Instagram
  • The constructor does not validate empty keys: a missing app_id surfaces as ConfigurationError when you call login_url.

Builds the Instagram Login dialog URL (https://www.instagram.com/oauth/authorize). It is synchronous and sends no request.

ig.login_url(
    *,
    redirect_uri: str,
    state: Optional[str] = None,
    scopes: Optional[list[str]] = None,
) -> str
  • Default scopes: instagram_business_basic, instagram_business_manage_messages, instagram_business_manage_comments, instagram_business_content_publish, instagram_business_manage_insights; narrow them with scopes=.
  • Raises ConfigurationError without app_id.
  • The SDK does not store state: generate a random value and compare it in the callback.
  • Per Meta, the callback code is valid for 1 hour and can be used only once.
Example — python
url = ig.login_url(redirect_uri="https://siz.uz/cb", state=state)

Refreshes a long-lived Instagram user token via ig_refresh_token and returns a new TokenBundle. Run it on a schedule (cron) so accounts do not silently disconnect.

await ig.refresh_token(token: str) -> socialift.models.TokenBundle
  • Meta rule: the token must be at least 24 hours old and not expired; refreshed tokens are valid for 60 days from the refresh date.
  • Instagram Login tokens only — the Page-based mode uses Page tokens.
  • Raises ApiError if no token is returned.
Example — python
bundle = await ig.refresh_token(token)

via_facebook

classmethod

Creates a client for the Facebook Login flow: requests go to graph.facebook.com and a Page token is expected. No app_id is needed — OAuth happens in the Facebook client and the secret is used for appsecret_proof.

Instagram.via_facebook(*, app_secret: str, **kwargs) -> Instagram
  • Profile, media, insights and publishing methods require ig_user_id (me means the Page here); take it from Page.instagram_id.
  • conversations and send_message also require page_id.
  • login_url, exchange_code and refresh_token are for Instagram Login and are not used in this mode.

Profile and media

The account profile, the media list and a single media object.

mediamedia_detailsprofile

media

async

Returns the account's media as raw dicts. limit is the total number of items; the SDK follows pagination.

await ig.media(
    token: str,
    *,
    ig_user_id: Optional[str] = None,
    limit: int = 25,
) -> list[dict]
  • In the Page-based mode ig_user_id is required.
  • For a single item use media_details.

Required permissions:instagram_basic

Example — python
posts = await ig.media(page_token, ig_user_id=ig_user_id, limit=25)

Returns a single media object by media_id. publish only returns IDs, so fetch permalink and other fields with this method after publishing.

await ig.media_details(media_id: str, token: str) -> dict

Required permissions:instagram_basic

Example — python
post = await ig.media_details(media_id, page_token)

profile

async

Returns the professional account's profile as a Profile: username, name, picture, bio, website, followers, follows and media count. Use it to display or sync the account after connecting.

await ig.profile(
    token: str,
    *,
    ig_user_id: Optional[str] = None,
) -> socialift.models.Profile
  • In the Page-based mode ig_user_id is required; the SDK does not request account_type there, so it is None.
  • The full response is in Profile.raw.

Required permissions:instagram_basic

Example — python
profile = await ig.profile(page_token, ig_user_id=ig_user_id)

Publishing

Publishing photos, Reels and carousels from public HTTPS URLs.

publishpublish_carouselpublish_container

publish

async

Publishes one photo or video: creates a container, waits for it to be ready if it is a video, then calls media_publish. Returns {container_id, media_id}.

await ig.publish(
    token: str,
    *,
    ig_user_id: Optional[str] = None,
    caption: Optional[str] = None,
    image_url: Optional[str] = None,
    video_url: Optional[str] = None,
    is_reel: bool = False,
    share_to_feed: Optional[bool] = None,
    wait_timeout: float = 300.0,
) -> dict
  • image_url/video_url must be public HTTPS URLs — Meta downloads the file itself; images must be JPEG.
  • Video always goes out as REELS — Meta stopped accepting media_type=VIDEO on November 9, 2023. is_reel only picks the share_to_feed default.
  • Raises ContainerNotReadyError if a video container is not ready within wait_timeout (300 seconds by default) or reports ERROR — pass its container_id to publish_container().
  • Meta limit: up to 100 API-published posts per account within a moving 24-hour period (some Meta pages state 50) — content_publishing_limit returns the exact remaining quota.

Required permissions:instagram_content_publish

Example — python
post = await ig.publish(page_token, ig_user_id=ig_user_id, caption="Yangi mahsulot",
                        image_url="https://cdn.siz.uz/photo.jpg")

Publishes a container created earlier: use it to continue after publish(), publish_carousel() or publish_story() raised ContainerNotReadyError, without re-uploading the file.

await ig.publish_container(
    token: str,
    container_id: str,
    *,
    ig_user_id: Optional[str] = None,
    wait_timeout: float = 300.0,
) -> dict
  • Take container_id from the exception (ContainerNotReadyError.container_id); the method waits for the container to be ready and then calls media_publish.
  • A container lives 24 hours: if it is not published by then it becomes EXPIRED and the media must be uploaded again.
  • Raise wait_timeout for long videos — Meta recommends polling the container status once a minute for no more than 5 minutes.

Stories

Publishing a story and listing the currently active ones. A story lives 24 hours and has no caption.

publish_storystories

Publishes a photo or video as a story. The flow is the same as publish() (container → wait → media_publish) with one difference: the container carries media_type=STORIES. Returns {container_id, media_id}.

await ig.publish_story(
    token: str,
    *,
    ig_user_id: Optional[str] = None,
    image_url: Optional[str] = None,
    video_url: Optional[str] = None,
    wait_timeout: float = 300.0,
) -> dict
  • There is no caption, by design: a story is not a feed post and caption text is not shown on it. Any text has to be burned into the image or video itself.
  • Pass exactly one of image_url and video_url — a public HTTPS URL, as in publish() (Meta downloads the file). Passing both raises ConfigurationError.
  • Meta's media rules (the SDK cannot check them): images — JPEG up to 8 MB, 9:16 recommended; video — MOV or MP4, 3–60 seconds, up to 100 MB, H264 or HEVC, AAC audio at 48 kHz.
  • If a video container is not ready within wait_timeout (300 seconds by default), ContainerNotReadyError is raised; continue with publish_container() and its container_id.
  • Stories go out through media_publish too, so they count against the daily publishing limit. Meta's own docs state that number differently on different pages — read the remaining quota from GET /{ig-user-id}/content_publishing_limit.
  • A published story disappears after 24 hours (the API exposes no archive and no highlights). Within those 24 hours media_details() and media_insights() work; media_product_type == "STORY" identifies it as a story.
  • Permissions: Instagram Login needs instagram_business_basic and instagram_business_content_publish; Facebook Login needs instagram_basic, instagram_content_publish and pages_read_engagement.

Required permissions:instagram_content_publishpages_read_engagement

Example — python
story = await ig.publish_story(page_token, ig_user_id=ig_user_id,
                                image_url="https://cdn.siz.uz/story.jpg")

stories

async

Returns the account's currently active stories. Stories never appear in the media edge — Meta exposes a separate stories edge for them, which is why this is its own method.

await ig.stories(
    token: str,
    *,
    ig_user_id: Optional[str] = None,
    limit: int = 25,
) -> list[dict]
  • Facebook Login only: Meta marks this edge "Instagram API with Facebook Login only". On an Instagram Login client the method raises ConfigurationError without sending a request — there is no official way to list active stories in that flow.
  • Only the last 24 hours are visible: the API exposes neither the archive nor highlights.
  • Every entry is an IG Media object; media_type is IMAGE/VIDEO for stories too, so media_product_type == "STORY" is what identifies them — the SDK always requests that field.
  • Meta limits: Live Video stories are not returned; a new story created by resharing someone else's is not included; and if a story has several captions, only one comes back.
  • Permissions (Facebook Login): instagram_basic and pages_read_engagement.

Required permissions:instagram_basicpages_read_engagement

Example — python
stories = await ig.stories(page_token, ig_user_id=ig_user_id, limit=25)

Comments

Reading, replying to, hiding and deleting comments.

commentsdelete_commenthide_commentreply_comment

Returns a media item's comments as a list of raw dicts. limit is the total number of comments; pagination is automatic.

await ig.comments(media_id: str, token: str, *, limit: int = 50) -> list[dict]
  • Meta recommends comment webhooks instead of frequent polling to avoid rate limiting.
  • With Facebook Login Meta requires instagram_basic, instagram_manage_comments and pages_read_engagement; with Instagram Login, instagram_business_basic and instagram_business_manage_comments.

Required permissions:instagram_manage_comments

Example — python
comments = await ig.comments(media_id, page_token, limit=50)

Deletes a comment with DELETE /{comment_id}. This cannot be undone — hide_comment is often enough.

await ig.delete_comment(comment_id: str, token: str) -> dict

Required permissions:instagram_manage_comments

Example — python
await ig.delete_comment(comment_id, page_token)

Hides a comment (hide=true) or unhides it with hidden=False. A reversible alternative to deleting.

await ig.hide_comment(comment_id: str, token: str, hidden: bool = True) -> dict

Required permissions:instagram_manage_comments

Example — python
await ig.hide_comment(comment_id, page_token, hidden=True)

Replies to a comment as the account (POST /{comment_id}/replies) and returns the new comment id.

await ig.reply_comment(comment_id: str, token: str, message: str) -> dict

Required permissions:instagram_manage_comments

Example — python
await ig.reply_comment(comment_id, page_token, "Rahmat!")

Direct messages

Conversations, message history, contact profiles and sending messages.

contactconversationsmessagessend_message

contact

async

Returns a participant's profile (User Profile API) by IGSID. It never raises: on error it returns {id, available: False, reason}, on success it adds available: True.

await ig.contact(igsid: str, token: str) -> dict
  • Meta rule: the profile is available only after user consent — the user messaged the business, tapped an icebreaker or a persistent menu item. Someone who only commented cannot be looked up.
  • Per Meta, the profile_pic URL expires in a few days — do not cache it for long.

Required permissions:instagram_manage_messages

Example — python
contact = await ig.contact(igsid, page_token)

Returns Direct conversations as {data, paging}: each has contact (the customer), contacts, last_message and updated_time. With enrich=True every participant's profile is fetched in parallel from the User Profile API.

await ig.conversations(
    token: str,
    *,
    ig_user_id: Optional[str] = None,
    page_id: Optional[str] = None,
    limit: int = 25,
    after: Optional[str] = None,
    enrich: bool = True,
) -> dict
  • Returns one page: pass the cursor from paging as after= for the next one.
  • In the Page-based mode page_id is required and platform=instagram is added; with Instagram Login it calls /me/conversations.
  • ig_user_id and page_id are used to tell the business apart from the customer — pass them, or contact may be the business itself.
  • Meta limits: only the 20 most recent messages in a conversation can be read in detail; conversations in the Requests folder inactive for 30 days are not returned.

Required permissions:instagram_manage_messages

Example — python
chats = await ig.conversations(page_token, page_id=page_id, ig_user_id=ig_user_id)
for chat in chats["data"]:
    print(chat["contact"]["name"])

Reads one conversation's messages from the messages edge and returns the Graph API response (data, paging). Pass the after cursor for the next page.

await ig.messages(
    conversation_id: str,
    token: str,
    *,
    limit: int = 50,
    after: Optional[str] = None,
) -> dict
  • Meta limit: only the 20 most recent messages can be read in detail; older ones return an error saying the message has been deleted.

Required permissions:instagram_manage_messages

Example — python
history = await ig.messages(conversation_id, page_token, limit=50)

Sends a text message in Direct (POST /{id}/messages, recipient.id is the IGSID). Use it to reply to a customer who arrived via a webhook or conversations.

await ig.send_message(
    recipient_id: str,
    token: str,
    text: str,
    *,
    ig_user_id: Optional[str] = None,
    page_id: Optional[str] = None,
) -> dict
  • 24-hour window: Meta allows replies within 24 hours of the customer's message and rejects them afterwards (PermissionError in the SDK).
  • Meta requirement: text must be UTF-8 and at most 1000 bytes.
  • In the Page-based mode page_id is required; with Instagram Login ig_user_id or me is used.
  • Text only: no images, templates or message tags in this method.

Required permissions:instagram_manage_messages

Example — python
await ig.send_message(recipient_id, page_token, "Salom!", page_id=page_id)

Webhook subscription

Subscribing an account to your app's webhooks in the Instagram Login flow.

subscribe_account

Subscribes the account to your app's webhooks in the Instagram Login flow (subscribed_apps) and returns a bool. Even with webhooks configured in the App Dashboard, comment and Direct events do not arrive until the account is subscribed.

await ig.subscribe_account(
    token: str,
    *,
    ig_user_id: Optional[str] = None,
    fields: Optional[Sequence[str]] = None,
) -> bool
  • Call it right after exchange_code(), otherwise the first events are lost.
  • Without fields the SDK sends the default list: comments, messages, messaging_postbacks, message_reactions.
  • Not used for Page-connected accounts: there the subscription lives on the Page (Facebook.subscribe_page) and IG events arrive through it. In that mode this method raises ConfigurationError.

Insights

Account metrics, follower demographics and media insights.

demographicsinsightsmedia_insights

Returns follower demographics broken down by age,gender, city and country (follower_demographics, timeframe=this_month). Each breakdown is a separate request, so one failure does not block the others.

await ig.demographics(token: str, *, ig_user_id: Optional[str] = None) -> dict
  • Result: {breakdown: {available, values}}; values is a flat dict keyed like 25-34 / F, and failures carry available: False and reason.
  • Meta rule: demographics are not returned for accounts with fewer than 100 followers.

Required permissions:instagram_manage_insights

Example — python
demo = await ig.demographics(page_token, ig_user_id=ig_user_id)

Returns the account's total metrics for a period as {since, until, metrics}. The SDK requests reach, profile_views, website_clicks, accounts_engaged and total_interactions with period=day and metric_type=total_value.

await ig.insights(
    token: str,
    *,
    ig_user_id: Optional[str] = None,
    since: Optional[datetime.date] = None,
    until: Optional[datetime.date] = None,
) -> dict[str, Any]
  • The SDK clamps the range: a future until becomes today, a missing since means the last 30 days, and longer ranges are cut to 30 days.
  • Meta removed profile_views and website_clicks from account insights for all versions on January 8, 2025. If Graph API rejects the request, ApiError is raised.
  • Per Meta, data can be delayed by up to 48 hours.

Required permissions:instagram_manage_insights

Example — python
stats = await ig.insights(page_token, ig_user_id=ig_user_id)

Returns one media item's metrics as {metric: value}. The set depends on the type: reach, likes, comments, saved, shares, total_interactions, plus profile_visits for images and views for videos and Reels; Stories get reach, replies, navigation, total_interactions.

await ig.media_insights(media_id: str, token: str) -> dict[str, Any]
  • Never raises on API errors — returns {} instead.
  • Meta rules: Story metrics are only available for 24 hours; there are no insights for individual items inside an album (carousel).

Required permissions:instagram_manage_insights

Example — python
stats = await ig.media_insights(media_id, page_token)

Methods shared by all clients

Every HTTP client (Telegram, Facebook, Instagram, LinkedIn, TikTok, Ads) has these low-level methods — for calling an endpoint the SDK doesn't wrap yet or for reading pages of results. The connection is closed automatically when the async with block ends.

request

async

Sends an arbitrary HTTP request and returns the JSON response — for endpoints the SDK doesn't wrap with a dedicated method yet. You pass the token and signing parameters yourself in params or headers (access_token, and for Meta appsecret_proof via socialift.crypto.appsecret_proof).

await ig.request(
    method: str,
    url: str,
    *,
    params: Optional[Mapping[str, Any]] = None,
    data: Optional[Any] = None,
    json: Optional[Any] = None,
    headers: Optional[Mapping[str, str]] = None,
) -> Any
  • None values in params are not sent.
  • Rate limits (RateLimitError) and transient server errors (HTTP 429, 500, 502, 503, 504) are retried according to RetryPolicy; a Retry-After header is honoured. Network failures are raised as TransportError.

get

async

Shorthand for request("GET", ...).

await ig.get(url: str, **kw) -> Any

post

async

Shorthand for request("POST", ...).

await ig.post(url: str, **kw) -> Any

delete

async

Shorthand for request("DELETE", ...).

await ig.delete(url: str, **kw) -> Any

Follows Meta's cursor pagination (paging.next) and returns every data item as a single list.

await ig.paginate(
    url: str,
    *,
    params: Optional[Mapping[str, Any]] = None,
    max_items: int = 500,
    page_size: int = 100,
) -> list[dict]
  • max_items (default 500) is a safety cap — a large account can have tens of thousands of records. page_size sets the limit of each request.

close

async

Closes the HTTP session the SDK created itself (a session you passed in is left open). Called automatically when you use async with Client(...).

await ig.close() -> None

Frequently asked questions

Do I need a Facebook Page to use the Instagram API?

No. With Instagram Login (Instagram(app_id, app_secret)) a professional account connects without a Page. The Facebook Login path (Instagram.via_facebook) requires an account linked to a Page and a Page token. Either way, the account must be a Business or Creator account.

How long does an Instagram access token last and how do I refresh it?

Per Meta, with Instagram Login a short-lived token lasts 1 hour and a long-lived token 60 days; exchange_code returns the long-lived one directly. refresh_token refreshes a token that is at least 24 hours old and not expired, and the new token is valid for another 60 days. Run it on a regular schedule.

Why can't I send an Instagram Direct message to a customer?

Meta's rule: you can reply within 24 hours of the user's message; after that the request is rejected. The text must also be UTF-8 and at most 1000 bytes. When connected through a Page, pass page_id to send_message as well.

How do I publish a video or Reel to Instagram via the API?

Call publish(token, video_url=...). The video must be at a public HTTPS URL — Meta downloads the file itself — and the SDK waits for the container up to wait_timeout (300 seconds by default). Meta stopped accepting media_type=VIDEO on November 9, 2023, so the SDK always sends video as REELS. is_reel now only picks the share_to_feed default: False puts the Reel in the feed too, True matches Meta's default. Set share_to_feed yourself for exact control.

Can I publish an Instagram story through the API?

Yes. publish_story(token, ig_user_id=..., image_url=...) creates a container with media_type=STORIES and publishes it, and it works in both connection flows. Stories have no caption — any text must be burned into the image or video. Meta's rules: JPEG up to 8 MB for images, 3–60 seconds for video. The story disappears after 24 hours, and the API exposes no archive or highlights. Listing active stories with stories(), however, is Facebook Login only: Meta marks that edge "Instagram API with Facebook Login only".

How many posts per day can I publish through the Instagram API?

Per Meta, an account can publish up to 100 API posts within a moving 24-hour period, although other Meta pages state 50 — so read the remaining quota from GET /{ig-user-id}/content_publishing_limit. Stories count against the same limit. A carousel holds up to 10 items; the SDK's publish_carousel accepts 2 to 10 images.

Sources

  1. Meta: Business Login for Instagram
  2. Meta: Instagram content publishing
  3. Meta: Instagram stories edge (IG User)
  4. Meta: Instagram Conversations API
  5. Meta: Instagram Messaging API
  6. Meta: Instagram account insights
  7. Meta: Instagram Platform changelog

Related pages