instagram

package module
v1.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 4, 2026 License: MIT Imports: 22 Imported by: 0

README

instagram-go

A Go client for Instagram's private web/mobile API (api/v1/*), plus a separate official Meta Graph/Marketing API client for professional insights and advertising. Authenticated, stdlib-only, zero production dependencies. Mirrors the conventions of x-go, linkedin-go, and the rest of the teslashibe scraper family.

import "github.com/teslashibe/instagram-go"

Status

Surface Read Write Tested live
Profiles & search
Account administration (offline; burner opt-in)
Posts / reels / feed ✅ (read)
Comments / likers ✅ (read)
Followers / friendship ✅ (read)
Stories / highlights ✅ (read)
Hashtags ✅ (read)
Locations
Keyword discovery
Direct messages ✅ (text only) gated burner/self
Photo/Reel/video-Story publishing gated live burner capture required
Topical explore (offline)
Home timeline (offline)

Write endpoints are implemented and shape-checked, but their integration tests are disabled by default — Instagram is aggressive about silent soft-blocks on write actions from server-side IPs. See Rate limiting below. The typed publishing draft is fail-closed: it cannot perform a mutation until a reviewed, date-stamped live burner capture is committed with the matching code. See Publishing.

Account-administration writes have an additional fail-closed contract: no raw request escape hatch, approved fields only, and an expected account ID plus explicit before/after values and confirmation on every call.

Install

go get github.com/teslashibe/instagram-go

Requires Go 1.25 or newer (uses generics for Iterator[T]).

Quick start

package main

import (
    "context"
    "fmt"

    instagram "github.com/teslashibe/instagram-go"
)

func main() {
    c, err := instagram.New(instagram.Cookies{
        SessionID: "...",  // sessionid cookie
        CSRFToken: "...",  // csrftoken cookie
        DSUserID:  "...",  // ds_user_id cookie (numeric)
        Datr:      "...",  // datr cookie
        Mid:       "...",  // mid cookie
        IgDid:     "...",  // ig_did cookie
    })
    if err != nil {
        panic(err)
    }

    ctx := context.Background()
    user, err := c.GetProfile(ctx, "natgeo")
    if err != nil {
        panic(err)
    }
    fmt.Printf("@%s — %d followers\n", user.Username, user.FollowerCount)

    it := c.GetPosts(user.ID).WithMaxPages(3)
    for it.Next(ctx) {
        p := it.Item()
        fmt.Printf("%s  %s  likes=%d comments=%d\n", p.Code, p.PermalinkURL, p.LikeCount, p.CommentCount)
    }
    if err := it.Err(); err != nil {
        panic(err)
    }
}

Authentication

This repository has two deliberately separate clients and credential types:

Client Package Credential Intended capabilities
Private Instagram API instagram (module root) Browser session cookies Consumer profiles, feeds, search, social actions
Official Meta Graph API instagram/meta Facebook Login OAuth bearer token Professional account/media insights and read-only advertising

Never put a Meta OAuth token in Cookies, and never provide Instagram cookies to meta.New. The transports, models, errors, and MCP providers are independent. See Official Meta Graph and Marketing API for OAuth scopes, token storage, Page linkage, account selection, insight validation, and ad safety.

Required cookies (export from a logged-in browser session):

Field Cookie name Required Notes
SessionID sessionid yes Primary credential
CSRFToken csrftoken yes Also sent as X-CSRFToken header
DSUserID ds_user_id yes Numeric user ID; used for session validation
Datr datr recommended Device auth token; reduces challenge prompts
Mid mid recommended Machine ID
IgDid ig_did recommended Device ID
Rur rur optional Region routing
IgNrcb ig_nrcb optional Notification opt-in flag
PsL/PsN ps_l/ps_n optional Persistent session telemetry
Wd wd optional Viewport size (<width>x<height>)

New() validates the session on construction by fetching /api/v1/users/<DSUserID>/info/. Pass WithSkipSessionValidation() to defer validation (useful in tests).

Burner credential probes

cmd/instagram-login-probe verifies the complete credential-to-media path. It asks the social-login sidecar to mint cookies, validates the authenticated user, runs a blended keyword search, resolves one of the returned hashtags, and fails unless that hashtag returns at least one media post.

INSTAGRAM_USERNAME='burner@example.com' \
INSTAGRAM_PASSWORD='...' \
SOCIAL_LOGIN_SIDECAR_URL='http://localhost:8190' \
INSTAGRAM_PROXY_URL='http://residential-proxy.example:8080' \
INSTAGRAM_SEARCH_QUERY='nature' \
go run ./cmd/instagram-login-probe

INSTAGRAM_SEARCH_QUERY is optional and defaults to nature. A residential proxy is recommended because Instagram commonly challenges browser logins from datacenter addresses. A successful run prints sanitized evidence in this form:

PASS: authenticated as @burner_account (id=123456789)
PASS: keyword search query="nature" hashtag=#nature posts=12 first_post=ABC123 permalink=https://www.instagram.com/p/ABC123/

The same path is available as an explicit live acceptance test:

INSTAGRAM_LIVE_TEST=1 \
INSTAGRAM_USERNAME='burner@example.com' \
INSTAGRAM_PASSWORD='...' \
SOCIAL_LOGIN_SIDECAR_URL='http://localhost:8190' \
INSTAGRAM_SEARCH_QUERY='nature' \
go test -v -run TestLiveInventoryProbe ./cmd/instagram-login-probe

When INSTAGRAM_LIVE_TEST=1, missing live configuration is a test failure rather than a skip. Never commit passwords, proxy credentials, session cookies, CSRF tokens, or raw sidecar responses. See the redacted live validation record for the latest committed run.

For the auditable keyword-to-media acceptance capture, use cmd/instagram-search-inventory. Unlike blended web typeahead, this command queries Instagram's mobile keyword SERP directly, fails closed unless both Top and Reels contain media nodes, and writes only secret-scrubbed response-shape evidence:

INSTAGRAM_COOKIES_FILE=/secure/burner-cookies.json \
go run ./cmd/instagram-search-inventory \
  -query coffee \
  -output docs/inventory/captures/YYYY-MM-DD-coffee-rest.md

The submitted probe, its capture contract, and the full live artifact are committed at cmd/instagram-search-inventory, docs/inventory/search-graphql.md, and docs/inventory/captures/2026-07-31-coffee-rest.md.

User-Agent

The default User-Agent is the Instagram Android app's UA string (Instagram 103.1.0.15.119 Android …). Desktop browser UAs are rejected with {"message": "useragent mismatch"} — override only if you have a known-good alternative.

Web and mobile request routing

One Client carries the same explicit cookie header and http.Client across two Instagram origins. Existing profile, feed, hashtag, entity-search, and web GraphQL calls use https://www.instagram.com. Mobile keyword SERP calls use https://i.instagram.com only when the endpoint wrapper explicitly selects the mobile request profile; switching hosts does not create or maintain a second session.

The profiles intentionally have different defaults:

Surface Host App/header profile
Existing web API and GraphQL www.instagram.com Web app ID 936619743392459, X-IG-WWW-Claim: 0, browser fetch/origin headers
Mobile fbsearch SERP i.instagram.com Mobile app ID 567067343352427, current Android UA, X-IG-Capabilities: 3brTv10=, X-IG-Connection-Type: WIFI

The live inventory succeeded with browser session cookies and did not require a synthesized Authorization bearer value or a web WWW-Claim on the mobile host, so the client does not invent either. Web GraphQL remains on the WWW host and retains its existing claim/header behavior. Override origins with WithWWWHost and WithAPIHost; override only the mobile identity with WithAPIUserAgent and WithAPIAppID. WithUserAgent and WithAppID retain their existing web behavior.

Endpoint catalogue

Read endpoints with enabled integration coverage have been end-to-end verified with a live session; surfaces marked (offline) above remain fixture-verified only. Write endpoints are implemented but not exercised in the integration suite.

Method Endpoint
Me(ctx) (cached from New())
GetProfile(ctx, username) GET /api/v1/users/web_profile_info/?username=
GetProfileByID(ctx, userID) GET /api/v1/users/{id}/info/
SearchUsers(ctx, query, count) GET /api/v1/users/search/?q=&count=
Search(ctx, query) GET /api/v1/web/search/topsearch/
GetSuggestedUsers(ctx, targetID) GET /api/v1/discover/chaining/?target_id=
SearchPosts(query) (iterator) GET i.instagram.com/api/v1/fbsearch/top_serp/
SearchKeywordPosts(query) (iterator) POST /graphql/query (initial + pagination documents)
SearchReels(query) (single-page iterator) GET i.instagram.com/api/v1/fbsearch/reels_serp/
SearchAccounts(ctx, query) GET i.instagram.com/api/v1/fbsearch/account_serp/
SearchTypeaheadUsers(ctx, query, count) GET i.instagram.com/api/v1/fbsearch/typeahead_stream/
KeywordTypeahead(ctx, query) GET i.instagram.com/api/v1/fbsearch/typeahead_stream/

Search and SearchUsers remain the compatible REST entity searches. SearchPosts uses the mobile Top SERP and preserves its complete pagination state in a versioned opaque cursor bound to the trimmed query. Version-1 SearchPosts cursors are rejected because they contain no query binding; malformed, unsupported, or query-mismatched cursors fail before an HTTP request is made. SearchKeywordPosts uses the web keyword-to-media connection, transparently switches from the captured initial GraphQL document to the distinct pagination document, and preserves the Relay cursor plus both GraphQL search session IDs in its versioned opaque cursor. Keyword cursors are bound to the trimmed query; malformed, unsupported, or query-mismatched cursors fail before an HTTP request is made. SearchReels returns Reel media as ordinary Post values, including media PKs needed by commenting helpers. It intentionally fetches only the first page: the live inventory proved response cursor fields but not their continuation request parameters. The iterator shape allows pagination to be added compatibly after a continuation request is captured; until then, passing a cursor to the iterator fails before an HTTP request. The instagram_search_reels MCP tool follows the same terminal contract: limit can truncate that first page, but the tool does not return a continuation cursor, and it rejects any supplied cursor before making an HTTP request. SearchAccounts returns the richer account SERP context (including friendship and social-context fields), while SearchTypeaheadUsers returns the lighter account suggestions shown during keyword entry. KeywordTypeahead is a compact string helper over those entities, returning usernames (or display-name fallbacks). An empty slice is a valid response when Instagram has no suggestion for a partial query. The private contracts, status checklist, rotating doc_id values, and scrubbed evidence are documented in the search inventory.

it := client.SearchPosts("specialty coffee").WithMaxPages(2)
for it.Next(ctx) {
    post := it.Item()
    fmt.Printf("%s %s\n", post.Code, post.PermalinkURL)
}
if err := it.Err(); err != nil {
    // Includes the existing auth/rate-limit/challenge sentinels.
    return err
}

// Persist after a page, then resume the same trimmed query later without
// losing Top SERP state.
cursor := it.Cursor()
if cursor != "" {
    resumed := client.SearchPosts("specialty coffee").WithCursor(cursor)
    _ = resumed
}

// Keyword GraphQL cursors also resume faithfully on a fresh iterator.
keyword := client.SearchKeywordPosts("specialty coffee").WithMaxPages(1)
_, err := keyword.Collect(ctx)
if err == nil && keyword.Cursor() != "" {
    resumed := client.SearchKeywordPosts("specialty coffee").WithCursor(keyword.Cursor())
    _ = resumed
}

accounts, err := client.SearchAccounts(ctx, "specialty coffee")

reels, err := client.SearchReels("specialty coffee").Collect(ctx)
suggestions, err := client.KeywordTypeahead(ctx, "specialty cof")

Top SERP ranking is personalized and can change between runs. Durable watches should deduplicate results by Post.PK (falling back to Post.Code).

Safe account administration

The authenticated account read is projected into three safe models. Raw current account responses are not exposed because Instagram may include contact or security-adjacent fields alongside editable settings.

Method Endpoint
GetCurrentAccount(ctx) GET i.instagram.com/api/v1/accounts/current_user/?edit=true
GetAccountSettings(ctx) same captured read, reversible-settings projection
GetProfessionalAccountState(ctx) same captured read, professional-state projection
UpdateProfileFields(ctx, params) POST i.instagram.com/api/v1/accounts/edit_profile/
SetPrivacy(ctx, params) POST i.instagram.com/api/v1/accounts/set_private/ or set_public/
UpdateProfessionalSettings(ctx, params) POST i.instagram.com/api/v1/business/account/edit/

Each mutation checks ExpectedAccountID against both the authenticated ds_user_id and a fresh read, requires Confirm: true, rejects a no-op or stale Before, sends exactly one write attempt, and re-reads to verify After before returning success. Profile mutation is limited to full name, biography, and external URL. Professional mutation is limited to category ID and category visibility on an account that is already professional.

Password, username/email/phone, public contact details, 2FA, deletion/deactivation, account conversion, ownership, and security changes are not implemented. The same fields are absent from MCP input schemas. The captured contract and burner-only verification protocol are documented in docs/inventory/account-administration.md.

Posts & feeds
Method Endpoint
GetPosts(userID) (iterator) GET /api/v1/feed/user/{id}/?count=&max_id=
GetReels(userID) (iterator) POST /api/v1/clips/user/
GetTaggedPosts(userID) (iterator) GET /api/v1/usertags/{id}/feed/
GetPost(ctx, shortcode) shortcode → media_id, then GET /api/v1/media/{id}/info/
GetPostByID(ctx, mediaID) GET /api/v1/media/{id}/info/
GetTimeline() (iterator) POST /api/v1/feed/timeline/
GetExplore() (iterator) GET /api/v1/discover/topical_explore/
Comments & likers
Method Endpoint
GetComments(mediaPK) (iterator) GET /api/v1/media/{pk}/comments/
GetCommentReplies(mediaPK, parentID) (iterator) GET /api/v1/media/{pk}/comments/{parent}/child_comments/
GetLikers(ctx, mediaPK) GET /api/v1/media/{pk}/likers/
GetCommentLikers(ctx, mediaPK, commentID) GET /api/v1/media/{pk}/comment_likers/?comment_id=
Followers, following, friendship
Method Endpoint
GetFollowers(userID) (iterator) GET /api/v1/friendships/{id}/followers/
GetFollowing(userID) (iterator) GET /api/v1/friendships/{id}/following/
GetFriendship(ctx, userID) GET /api/v1/friendships/show/{id}/
GetFriendships(ctx, userIDs) POST /api/v1/friendships/show_many/
Stories & highlights
Method Endpoint
GetStoryTray(ctx) GET /api/v1/feed/reels_tray/
GetUserStories(ctx, userID) GET /api/v1/feed/user/{id}/story/
GetHighlights(ctx, userID) GET /api/v1/highlights/{id}/highlights_tray/
GetReelsMedia(ctx, reelIDs) POST /api/v1/feed/reels_media/
Hashtags
Method Endpoint
GetHashtag(ctx, name) GET /api/v1/tags/web_info/?tag_name=
GetHashtagPosts(name) (iterator) POST /api/v1/tags/{name}/sections/ tab=recent
GetHashtagTopPosts(name) (iterator) POST /api/v1/tags/{name}/sections/ tab=top
GetHashtagClips(name) (iterator) POST /api/v1/tags/{name}/sections/ tab=clips
Locations
Method Endpoint
GetLocation(ctx, id) GET /api/v1/locations/{id}/info/
SearchLocations(ctx, query) GET /api/v1/location_search/?search_query=
GetLocationPosts(id) (iterator) POST /api/v1/locations/{id}/sections/ tab=recent
GetLocationTopPosts(id) (iterator) POST /api/v1/locations/{id}/sections/ tab=ranked
Instagram Direct

Direct uses the captured mobile i.instagram.com request profile. Inbox and thread cursors are versioned opaque values; thread-item cursors are bound to the selected thread ID and cross-thread replay fails before HTTP.

Method Endpoint
GetDirectInbox() (iterator) GET /api/v1/direct_v2/inbox/
GetDirectThread(threadID) (iterator) GET /api/v1/direct_v2/threads/{thread_id}/
SendDirectText(ctx, request) POST /api/v1/direct_v2/create_group_thread/, then POST /api/v1/direct_v2/threads/broadcast/text/

SendDirectText accepts exactly one explicit numeric RecipientID and non-whitespace text. It creates a cryptographically random ClientContext when one is not supplied, uses the same value for the mutation token and every broadcast retry, and returns it for reconciliation after an uncertain outcome. The whole write is bounded to 30 seconds. Thread creation is not automatically retried because its idempotency contract has not been proven. If broadcast returns an uncertain outcome, retry with ThreadID, ClientContext, and RetryToken from DirectSendError; this skips thread creation and repeats only the idempotent broadcast. The authenticated token binds the resolved thread to the explicit recipient and original text, so substituted targets fail before HTTP.

result, err := c.SendDirectText(ctx, instagram.DirectTextRequest{
    RecipientID: "123456789", // explicitly approved burner/self ID
    Text:        "hello from the burner acceptance test",
})

var sendErr *instagram.DirectSendError
if errors.As(err, &sendErr) && sendErr.ThreadID != "" {
    result, err = c.SendDirectText(ctx, instagram.DirectTextRequest{
        RecipientID:   "123456789",
        Text:          "hello from the burner acceptance test",
        ThreadID:      sendErr.ThreadID,
        ClientContext: sendErr.ClientContext,
        RetryToken:    sendErr.RetryToken,
    })
}

Only plain text is supported. Attachments, reactions, vanish mode, and group administration remain unsupported until their request and safety contracts are captured separately. See the Direct contract inventory.

Write actions

All writes are subject to a stricter rate-limit budget than reads. They share a 12 s minimum gap and a 15 m circuit-breaker cooldown when Instagram returns a 302→login soft-block. See Rate limiting.

Action category Methods
Posts LikePost, UnlikePost, SavePost, UnsavePost
Comments PostComment, LikeComment, UnlikeComment, DeleteComment
Friendship Follow, Unfollow, Block, Unblock, MutePosts, UnmutePosts
Hashtags FollowHashtag, UnfollowHashtag
Stories MarkStorySeen
Direct SendDirectText (one explicit recipient; plain text only)

Publishing

PublishPhoto, PublishReel, and PublishStory define a typed UploadSource: an io.Reader plus exact filename, MIME type, byte length, dimensions, and video duration. Each call also requires an idempotency key, which is combined with the bounded content hash and metadata to derive stable upload/client IDs.

No reviewed live publishing capture is currently committed, so PublishingCaptureVersion is empty and every SDK method returns ErrPublishingCaptureRequired before reading the stream or making an HTTP request. The MCP tools similarly return publishing_capture_required. This is the capture-before-publishing gate required for Instagram's rotating private protocol.

file, _ := os.Open("disposable.jpg")
info, _ := file.Stat()
result, err := client.PublishPhoto(ctx, instagram.PublishPhotoInput{
    Media: instagram.UploadSource{
        Reader: file, Filename: info.Name(), MIMEType: "image/jpeg",
        Size: info.Size(), Width: 1080, Height: 1350,
    },
    Caption: "disposable burner photo",
    IdempotencyKey: "my-operation-123",
})

After the capture gate is satisfied, photos default to a 25 MiB decoded limit and videos to 100 MiB. Raw upload requests have a two-minute deadline, video processing has a separate two-minute deadline, and upload bodies are never blindly replayed. Override conservative SDK bounds with WithPublishingLimits and WithPublishingTimeouts.

The draft failure model includes ErrFeedbackRequired, ErrProcessingFailed, ErrProcessingTimeout, ErrPartialUpload, or ErrUploadTooLarge in addition to the existing challenge/rate/write sentinels. DeleteMedia accepts the exact caller-supplied media ID plus the returned PublishedMediaKind, reproduces the per-kind deletion discriminator, and is intended only for deliberate burner cleanup. It does not enumerate account data and is excluded from MCP.

Instagram's private publishing protocol can rotate. The redaction command, current contract status, capture procedure, and safety boundary are documented in docs/inventory/publishing.md. Live publishing verification is intentionally opt-in and requires disposable assets:

IG_PUBLISH_LIVE_TEST=1 \
IG_PUBLISH_BURNER_ACK=DISPOSABLE_BURNER_CONTENT \
IG_PUBLISH_PHOTO_PATH=/secure/disposable.jpg \
IG_PUBLISH_REEL_PATH=/secure/disposable.mp4 \
IG_PUBLISH_REEL_THUMBNAIL_PATH=/secure/reel-thumb.jpg \
IG_PUBLISH_REEL_DURATION_MS=3000 \
IG_PUBLISH_STORY_PATH=/secure/story.mp4 \
IG_PUBLISH_STORY_THUMBNAIL_PATH=/secure/story-thumb.jpg \
IG_PUBLISH_STORY_DURATION_MS=3000 \
go test -v -run '^TestIntegration_PublishDisposableBurnerMedia$' .

Once a capture is compiled in, the test registers each returned ID for exact-ID cleanup before verifying it, then reads that exact ID back after deletion until Instagram confirms it is unavailable. It never lists or removes unrelated account media.

Unsupported until separately captured and burner-verified: photo Stories, carousels, licensed music selection, stickers, structured mentions/tags, locations, collaboration/paid-partnership invitations, and scheduling. Plain caption text will publish immediately once the capture gate is enabled; embedded Reel audio remains original upload audio.

Pagination

All list endpoints return an Iterator[T]:

it := c.GetFollowers(userID).WithMaxPages(5)
for it.Next(ctx) {
    u := it.Item()
    // ...
}
if err := it.Err(); err != nil { … }

Next advances one item; the iterator transparently fetches the next page on exhaustion using Instagram's next_max_id (or, for comments, next_min_id). Use WithMaxPages(n) to cap how many pages are fetched. WithLimit(n) caps total items returned.

Rate limiting

Instagram does not publish standard X-RateLimit-* / Retry-After headers. Instead it rate-limits behaviourally and signals pressure via three channels:

  1. Body messages{"message": "Please wait a few minutes before you try again.", "status": "fail"}
  2. 302→/accounts/login/ soft-block — Instagram returns a 302 redirect to the login page even with a healthy sessionid. This pattern indicates a rate limit, not session expiry, once the session has been validated at least once.
  3. Soft signal headersx-ig-capacity-level (0 = degraded, 3 = healthy), x-ig-peak-time, x-ig-peak-v2, x-fb-connection-quality.

The client handles all three:

  • A leaky-bucket pacer enforces a minimum gap between requests (default 4 s for reads = ~15 reads/min, 12 s for writes).
  • On any of the three signals above, a global circuit-breaker trips a cooldown (default 5 m for reads, 15 m for writes). Subsequent calls block until the cooldown clears (or the context is cancelled).
  • RateLimit() exposes the most recent observation; WaitForCooldown(ctx) blocks until all cooldowns are clear.
  • Retries are skipped while a cooldown is active (so we never burn attempts).
state := c.RateLimit()
fmt.Printf("capacity=%d peak=%v conn=%q\n", state.CapacityLevel, state.PeakTime, state.ConnectionQuality)

if err := c.WaitForCooldown(ctx); err != nil {
    return err
}

Tune via:

c, _ := instagram.New(cookies,
    instagram.WithMinRequestGap(6*time.Second),
    instagram.WithMinWriteGap(20*time.Second),
    instagram.WithRateLimitCooldown(10*time.Minute, 30*time.Minute),
)

Empirically observed safe ceilings on a single residential session (your mileage will vary):

Action Conservative Aggressive Notes
Reads (per min) 8 15 Above 15/min triggers wait a few minutes.
Reads (per hour) 400 800 Capacity drops to 1–2 above this.
Writes (per hour) 30 60 Above this risks a 24 h soft-block.

Error handling

All errors wrap one of the package sentinels — match with errors.Is:

Sentinel Meaning
ErrInvalidAuth Missing/malformed cookies, or session validation failed
ErrSessionExpired 302→login on an unvalidated session, or sessionid="" server-set
ErrRateLimited 429, wait a few minutes, or 302→login on a validated session
ErrWriteSoftBlock 302→login on a write action; read session still works
ErrChallengeRequired Account flagged for security checkpoint
ErrFeedbackRequired feedback_required; also trips the write cooldown
ErrProcessingFailed Uploaded media reached a captured terminal processing failure
ErrProcessingTimeout Media did not become ready before the bounded processing deadline
ErrPartialUpload A prior stage may have accepted bytes; do not change idempotency key
ErrUploadTooLarge Declared or streamed bytes exceeded a local upload limit
ErrInvalidPublishInput Publishing metadata or stream length failed local validation
ErrPublishingCaptureRequired No reviewed live burner protocol is compiled in; no mutation occurred
ErrNotFound 404 or user_not_found response
ErrPrivateAccount Resource belongs to a private account viewer doesn't follow
ErrMediaUnavailable Post deleted or hidden
ErrCSRF CSRF token rejected on a write
ErrAccountMismatch Authenticated account differs from the requested target
ErrMutationPrecondition Confirmation, before-state, no-op, or verification failed
ErrUnexpectedResponse Well-formed JSON missing the expected fields

For non-2xx HTTP responses, the wrapped error is also an *APIError with StatusCode, Body, etc. — useful for logging:

var apiErr *instagram.APIError
if errors.As(err, &apiErr) {
    log.Printf("instagram %d: %s", apiErr.StatusCode, apiErr.Body)
}

Concurrency

Client is safe for concurrent use by multiple goroutines. The pacer and circuit-breaker are global to the client, so concurrent goroutines share a single rate-limit budget.

Testing

# Offline unit tests (no cookies required)
go test ./...

# Integration suite — needs cookies in env
source .env.test.local
go test -count=1 -run '^TestIntegration_' .

.env.test.local shape (gitignored):

export IG_SESSIONID='...'
export IG_CSRFTOKEN='...'
export IG_DS_USER_ID='...'
export IG_DATR='...'
export IG_MID='...'
export IG_DID='...'
# Optional but recommended:
export IG_RUR='...'
export IG_NRCB='1'
export IG_WD='948x1384'

The integration suite uses a single shared client across all tests so the rate-limit circuit-breaker is honoured globally. To stay under Instagram's ~15 reads/min ceiling, run individual tests with explicit pauses rather than the full suite as a burst:

go test -v -count=1 -run '^TestIntegration_GetProfile$' .
sleep 30
go test -v -count=1 -run '^TestIntegration_GetPosts$' .
# ...

Account-administration smoke tests have stronger guards and must use a dedicated burner. Each test registers cleanup before writing and verifies restoration:

export INSTAGRAM_ACCOUNT_ADMIN_LIVE_TEST=1
export INSTAGRAM_ACCOUNT_ADMIN_BURNER_ID="$IG_DS_USER_ID"
export INSTAGRAM_ACCOUNT_ADMIN_CONFIRM='RESTORE_BURNER_SETTINGS'
go test -v -count=1 -run '^TestIntegration_AccountAdmin_ProfileRestoresBurner$' .

Run one administration smoke test at a time. Privacy and professional-display test names are listed in the account-administration inventory document.

MCP support

This package ships an MCP tool surface in ./mcp for use with teslashibe/mcptool-compatible hosts (e.g. teslashibe/agent-setup). 64 tools cover the full client API: profile lookup and search, safe account administration, post/reel/timeline/explore feeds, comments and likes, followers/following and friendship reads + writes (follow/unfollow/block/mute), hashtag and location reads + follow/unfollow, stories and highlights, blended top-search, keyword post/reel search, and Direct inbox/thread reads plus separately tagged confirmed Direct and fail-closed photo/Reel/video-Story publishing writes.

The Direct MCP tools are instagram_get_direct_inbox, instagram_get_direct_thread, and instagram_send_direct_text. The send tool alone is tagged write and requires recipient_id, non-empty text, and confirm_send=true; hosts can therefore put mutation confirmation around it without classifying the read tools as writes. Direct auth, challenge, rate limit, CSRF, timeout, and incomplete-send failures are returned as structured tool errors. An uncertain broadcast returns thread_id, client_context, and an authenticated retry_token; supplying all three on the next confirmed call retries only the broadcast after verifying the recipient and text binding.

import (
    "github.com/teslashibe/mcptool"
    instagram "github.com/teslashibe/instagram-go"
    igmcp "github.com/teslashibe/instagram-go/mcp"
)

client, _ := instagram.New(instagram.Cookies{...})
provider := igmcp.Provider{}
for _, tool := range provider.Tools() {
    // register tool with your MCP server, passing client as the
    // opaque client argument when invoking
}

A coverage test in mcp/mcp_test.go fails if a new exported method is added to *Client without either being wrapped by an MCP tool or being added to mcp.Excluded with a reason — keeping the MCP surface in lockstep with the package API is enforced by CI rather than convention.

Publishing tools are separately tagged write, publishing, and mutation. They require confirm_mutation: true, accept decoded media only through bounded base64 inputs (8 MiB photo/thumbnail, 64 MiB video), and impose a two-minute per-call timeout. Missing confirmation, oversized input, or a missing reviewed capture fails before any Instagram request.

Conventions

  • Stdlib only in the SDK. instagram package itself has zero third-party deps. The ./mcp subpackage pulls in teslashibe/mcptool (and its transitive deps) for the MCP tool surface — opt in by importing ./mcp, otherwise unaffected.
  • Errors as values. Sentinel errors with errors.Is; *APIError for HTTP context.
  • Iterators for lists. Anything paginated returns *Iterator[T]; one-shot results return []T directly.
  • Numeric IDs as strings. Instagram mixes numeric and string IDs in the same payloads (pk / pk_id). All IDs are normalised to string on the way out.
  • Raw field on every model. Each User, Post, etc. carries Raw json.RawMessage so callers can fish out fields the typed view doesn't expose.

License

MIT — see LICENSE.

Documentation

Overview

Package instagram provides a Go client for Instagram's private web/mobile API.

It supports authenticated profile lookup, post and reel feeds, comments, followers/following, stories, hashtags, locations, search, narrowly scoped account administration, and explicitly confirmed plain-text Instagram Direct messaging — giving programmatic access to Instagram's content graph and safe reversible settings from a logged-in browser session. It also defines a bounded, fail-closed publishing draft that remains disabled until a reviewed live burner capture is compiled in.

Zero production dependencies — stdlib only.

Authentication

Required cookies (obtained from a browser export of an authenticated session):

  • sessionid primary session credential
  • csrftoken CSRF token (also sent as X-CSRFToken header)
  • ds_user_id numeric user ID of the logged-in account
  • datr device auth token
  • mid machine ID
  • ig_did device ID (recommended)

User-Agent

Instagram's API rejects desktop browser user-agents with {"message": "useragent mismatch"}. The default UA is the Instagram Android app's UA string. Override via WithUserAgent only if you have a known-good alternative.

Rate limiting

Instagram does not return X-RateLimit headers. The client paces requests with a leaky-bucket minimum gap (default 4s) and exponential backoff on HTTP 429 / "Please wait a few minutes" responses.

Write actions (Follow, Unfollow, Like, Comment, etc.) are subject to a stricter, separate rate limiter than reads. The client enforces a longer minimum gap (default 12s) between writes and applies aggressive backoff on any 302-to-login response, which Instagram uses to indicate a write soft block. Account-administration writes additionally require an expected account ID, explicit before/after values, and confirmation; ambiguous writes are not retried.

Index

Constants

View Source
const PublishingCaptureVersion = ""

PublishingCaptureVersion identifies the reviewed live burner capture compiled into the SDK. It intentionally remains empty while the repository contains only draft/offline fixtures: all publishing methods fail closed before consuming a stream or making an HTTP request. A future implementation may set this only in the same change that commits the reviewed, date-stamped capture.

Variables

View Source
var (
	// ErrInvalidAuth indicates missing or malformed cookies, or that
	// validateSession could not fetch the current user.
	ErrInvalidAuth = errors.New("instagram: invalid auth")

	// ErrSessionExpired is returned when Instagram redirects to /accounts/login/
	// or wipes the sessionid cookie, indicating the session is no longer valid.
	ErrSessionExpired = errors.New("instagram: session expired or invalidated")

	// ErrRateLimited is returned when Instagram throttles the request.
	// On reads this is a 429 or "Please wait a few minutes" body.
	// On writes it is most often a 302 redirect to the login page.
	ErrRateLimited = errors.New("instagram: rate limited")

	// ErrWriteSoftBlock is returned when a write action is rejected with a
	// 302-to-login that does not invalidate the read session. Try again later
	// or from a different IP / device.
	ErrWriteSoftBlock = errors.New("instagram: write soft-blocked")

	// ErrChallengeRequired is returned when Instagram requires the account to
	// complete a security challenge (checkpoint) before continuing.
	ErrChallengeRequired = errors.New("instagram: checkpoint / challenge required")

	// ErrFeedbackRequired is returned when Instagram rejects publishing with
	// feedback_required. It is also classified as ErrWriteSoftBlock so existing
	// callers that only understand the broader write classification keep working.
	ErrFeedbackRequired = errors.New("instagram: feedback required")

	// ErrProcessingFailed is returned when Instagram accepts an upload but its
	// asynchronous media processor reaches a terminal failure state.
	ErrProcessingFailed = errors.New("instagram: media processing failed")

	// ErrProcessingTimeout is returned when an uploaded media item does not
	// reach a captured terminal processing state before the processing deadline.
	ErrProcessingTimeout = errors.New("instagram: media processing timed out")

	// ErrPartialUpload is returned after Instagram may have accepted upload
	// bytes but a later upload/configure/status stage failed. Callers must not
	// blindly retry with a different idempotency key.
	ErrPartialUpload = errors.New("instagram: partial upload")

	// ErrUploadTooLarge is returned locally, before any request, when declared
	// or streamed media exceeds the configured upload limit.
	ErrUploadTooLarge = errors.New("instagram: upload too large")

	// ErrInvalidPublishInput is returned locally, before any request, when
	// publishing metadata, MIME type, dimensions, duration, or stream length is
	// invalid.
	ErrInvalidPublishInput = errors.New("instagram: invalid publishing input")

	// ErrPublishingCaptureRequired is returned before consuming media or making
	// an HTTP request while no reviewed, current burner capture is compiled into
	// the SDK. Publishing deliberately fails closed until that evidence exists.
	ErrPublishingCaptureRequired = errors.New("instagram: verified publishing capture required")

	// ErrNotFound is returned for 404s and for usernames/IDs that resolve to
	// a user_not_found response from Instagram.
	ErrNotFound = errors.New("instagram: not found")

	// ErrPrivateAccount is returned when the requested resource belongs to a
	// private account that the authenticated user does not follow.
	ErrPrivateAccount = errors.New("instagram: private account")

	// ErrMediaUnavailable is returned when a post has been deleted or hidden.
	ErrMediaUnavailable = errors.New("instagram: media unavailable")

	// ErrCSRF is returned when Instagram rejects a write with a CSRF error.
	ErrCSRF = errors.New("instagram: csrf token rejected")

	// ErrAccountMismatch is returned when an authenticated account response or
	// mutation target does not match the ds_user_id bound to this client.
	ErrAccountMismatch = errors.New("instagram: authenticated account mismatch")

	// ErrMutationPrecondition is returned when an account administration write
	// is unconfirmed, is a no-op, or its explicit Before value is stale.
	ErrMutationPrecondition = errors.New("instagram: mutation precondition failed")

	// ErrUnexpectedResponse is returned when the response is well-formed but
	// does not contain the expected fields. The wrapped error gives detail.
	ErrUnexpectedResponse = errors.New("instagram: unexpected response")
)

Sentinel errors. Use errors.Is for matching.

Functions

This section is empty.

Types

type APIError

type APIError struct {
	StatusCode int
	Status     string
	Method     string
	URL        string
	Body       string
}

APIError carries the raw status code and body from a non-2xx response.

func (*APIError) Error

func (e *APIError) Error() string

type AccountMismatchError added in v1.4.0

type AccountMismatchError struct {
	ExpectedAccountID string
	ActualAccountID   string
}

AccountMismatchError identifies the expected and observed account IDs without exposing credentials or response bodies.

func (*AccountMismatchError) Error added in v1.4.0

func (e *AccountMismatchError) Error() string

func (*AccountMismatchError) Unwrap added in v1.4.0

func (e *AccountMismatchError) Unwrap() error

type AccountMutationResult added in v1.4.0

type AccountMutationResult[T any] struct {
	AccountID string `json:"account_id"`
	Before    T      `json:"before"`
	After     T      `json:"after"`
	Verified  bool   `json:"verified"`
}

AccountMutationResult records the verified state transition.

type AccountSearchResult added in v1.4.0

type AccountSearchResult struct {
	Users      []*User `json:"users"`
	NumResults int     `json:"num_results"`
	HasMore    bool    `json:"has_more"`
	PageToken  string  `json:"page_token,omitempty"`
	RankToken  string  `json:"rank_token,omitempty"`
}

AccountSearchResult is one typed page from the mobile account SERP. PageToken and RankToken are returned for observability; continuation request parameters were not proven by the inventory and are intentionally not sent.

type AccountSettings added in v1.4.0

type AccountSettings struct {
	AccountID string        `json:"account_id"`
	Profile   ProfileFields `json:"profile"`
	IsPrivate bool          `json:"is_private"`
}

AccountSettings contains the reversible settings approved for mutation.

type AudioInfo

type AudioInfo struct {
	AudioAssetID   string `json:"audio_asset_id,omitempty"`
	OriginalAudio  bool   `json:"original_audio,omitempty"`
	ArtistName     string `json:"display_artist,omitempty"`
	Title          string `json:"title,omitempty"`
	DurationMs     int64  `json:"duration_in_ms,omitempty"`
	OwnerID        string `json:"original_media_id,omitempty"`
	ProgressiveURL string `json:"progressive_download_url,omitempty"`
	IPADURL        string `json:"dash_manifest,omitempty"`
}

AudioInfo describes the audio track used in a reel.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is an Instagram API client. It is safe for concurrent use.

func New

func New(cookies Cookies, opts ...Option) (*Client, error)

New creates a Client and validates the session by fetching the current user. Returns ErrInvalidAuth if SessionID, CSRFToken, or DSUserID are empty.

func (*Client) Block

func (c *Client) Block(ctx context.Context, userID string) (*FriendshipStatus, error)

Block blocks a user.

Endpoint: POST /api/v1/friendships/block/<user_id>/

Note: this and the other tier-2 friendship writes (Mute, SetBesties) are often soft-blocked on web sessions. See ErrWriteSoftBlock.

func (*Client) DeleteComment

func (c *Client) DeleteComment(ctx context.Context, mediaPK, commentID string) error

DeleteComment deletes a comment authored by the viewer (or on the viewer's post).

Endpoint: POST /api/v1/media/<media_pk>/comment/<comment_id>/delete/

func (*Client) DeleteMedia added in v1.4.0

func (c *Client) DeleteMedia(ctx context.Context, mediaID string, kind PublishedMediaKind) error

DeleteMedia deletes exactly one caller-supplied media ID using the per-kind deletion discriminator established by the reviewed capture. It exists for burner verification cleanup and deliberately does not enumerate account data.

func (*Client) Follow

func (c *Client) Follow(ctx context.Context, userID string) (*FriendshipStatus, error)

Follow follows a user.

Endpoint: POST /api/v1/friendships/create/<user_id>/

func (*Client) FollowHashtag

func (c *Client) FollowHashtag(ctx context.Context, name string) error

FollowHashtag starts following a hashtag.

Endpoint: POST /api/v1/web/tags/follow/<name>/

Note: subject to write rate-limiting; see ErrWriteSoftBlock.

func (*Client) GetAccountSettings added in v1.4.0

func (c *Client) GetAccountSettings(ctx context.Context) (*AccountSettings, error)

GetAccountSettings fetches only the reversible profile and privacy fields.

func (*Client) GetCommentLikers

func (c *Client) GetCommentLikers(ctx context.Context, mediaPK, commentID string) ([]*User, error)

GetCommentLikers fetches the users who liked a specific comment on a post.

Endpoint: GET /api/v1/media/<media_pk>/comment_likers/?comment_id=<id>

func (*Client) GetCommentReplies

func (c *Client) GetCommentReplies(mediaPK, parentID string) *Iterator[*Comment]

GetCommentReplies iterates over the child replies under a parent comment.

Endpoint: GET /api/v1/media/<media_pk>/comments/<parent_id>/child_comments/

func (*Client) GetComments

func (c *Client) GetComments(mediaPK string) *Iterator[*Comment]

GetComments iterates over the top-level comments on a post.

mediaPK is the numeric pk of the post (Post.PK or Post.ID up to '_').

Endpoint: GET /api/v1/media/<media_pk>/comments/

func (*Client) GetCurrentAccount added in v1.4.0

func (c *Client) GetCurrentAccount(ctx context.Context) (*CurrentAccount, error)

GetCurrentAccount fetches the authenticated account's safe identity and editable profile projection.

func (*Client) GetDirectInbox added in v1.4.0

func (c *Client) GetDirectInbox() *Iterator[*DirectThread]

GetDirectInbox iterates over threads in the authenticated viewer's primary Direct inbox. Cursor values are versioned and opaque.

Endpoint: GET i.instagram.com/api/v1/direct_v2/inbox/

func (*Client) GetDirectThread added in v1.4.0

func (c *Client) GetDirectThread(threadID string) *Iterator[*DirectItem]

GetDirectThread iterates over items in a selected Direct thread. Cursors are bound to threadID so a cursor cannot be replayed against another conversation.

Endpoint: GET i.instagram.com/api/v1/direct_v2/threads/<thread_id>/

func (*Client) GetExplore

func (c *Client) GetExplore() *Iterator[*Post]

GetExplore fetches the Explore feed.

Endpoint: GET /api/v1/discover/topical_explore/

func (*Client) GetFollowers

func (c *Client) GetFollowers(userID string) *Iterator[*User]

GetFollowers iterates over a user's followers.

Endpoint: GET /api/v1/friendships/<user_id>/followers/?count=12&max_id=<cursor>

Note: pagination is best-effort. Instagram caps deep follower lists for large accounts, and very high page indices are silently truncated.

func (*Client) GetFollowing

func (c *Client) GetFollowing(userID string) *Iterator[*User]

GetFollowing iterates over the accounts a user is following.

Endpoint: GET /api/v1/friendships/<user_id>/following/

func (*Client) GetFriendship

func (c *Client) GetFriendship(ctx context.Context, userID string) (*FriendshipStatus, error)

GetFriendship returns the viewer's relationship with one user.

Endpoint: GET /api/v1/friendships/show/<user_id>/

func (*Client) GetFriendships

func (c *Client) GetFriendships(ctx context.Context, userIDs []string) (map[string]*FriendshipStatus, error)

GetFriendships returns relationship status with many users in one call.

Endpoint: POST /api/v1/friendships/show_many/ with form: user_ids=1,2,3

func (*Client) GetHashtag

func (c *Client) GetHashtag(ctx context.Context, name string) (*Hashtag, error)

GetHashtag fetches the metadata for a hashtag.

Endpoint: GET /api/v1/tags/web_info/?tag_name=<name>

func (*Client) GetHashtagClips

func (c *Client) GetHashtagClips(name string) *Iterator[*Post]

GetHashtagClips iterates over reels (clips) under a hashtag.

Endpoint: GET /api/v1/tags/<tag>/sections/?tab=clips

func (*Client) GetHashtagPosts

func (c *Client) GetHashtagPosts(name string) *Iterator[*Post]

GetHashtagPosts iterates over the recent posts under a hashtag.

Endpoint: GET /api/v1/tags/<tag>/sections/?tab=recent

func (*Client) GetHashtagTopPosts

func (c *Client) GetHashtagTopPosts(name string) *Iterator[*Post]

GetHashtagTopPosts iterates over the top (algorithmically ranked) posts under a hashtag.

Endpoint: GET /api/v1/tags/<tag>/sections/?tab=top

func (*Client) GetHighlights

func (c *Client) GetHighlights(ctx context.Context, userID string) ([]*StoryReel, error)

GetHighlights fetches a user's saved story highlight reels.

Endpoint: GET /api/v1/highlights/<user_id>/highlights_tray/

func (*Client) GetLikers

func (c *Client) GetLikers(ctx context.Context, mediaPK string) ([]*User, error)

GetLikers fetches the users who liked a post.

Endpoint: GET /api/v1/media/<media_pk>/likers/

Note: this is a one-shot endpoint — Instagram does not paginate likers. For posts with very many likes, only the first ~1000 are returned.

func (*Client) GetLocation

func (c *Client) GetLocation(ctx context.Context, id string) (*Location, error)

GetLocation fetches the metadata for a location by its numeric ID.

Endpoint: GET /api/v1/locations/<id>/info/

func (*Client) GetLocationPosts

func (c *Client) GetLocationPosts(id string) *Iterator[*Post]

GetLocationPosts iterates over the recent posts at a location.

Endpoint: GET /api/v1/locations/<id>/sections/?tab=recent

func (*Client) GetLocationTopPosts

func (c *Client) GetLocationTopPosts(id string) *Iterator[*Post]

GetLocationTopPosts iterates over the top posts at a location.

Endpoint: GET /api/v1/locations/<id>/sections/?tab=ranked

func (*Client) GetPost

func (c *Client) GetPost(ctx context.Context, shortcode string) (*Post, error)

GetPost fetches a single post by its shortcode (the bit between /p/ and / in the public URL, e.g. "DXHKcyvEWfr").

Internally the shortcode is decoded to a numeric media ID via the standard Instagram base64 alphabet, then GET /api/v1/media/{media_id}/info/ is hit.

To fetch by numeric ID directly, use GetPostByID.

func (*Client) GetPostByID

func (c *Client) GetPostByID(ctx context.Context, mediaID string) (*Post, error)

GetPostByID fetches a single post by its numeric media ID (Post.PK).

Endpoint: GET /api/v1/media/<media_id>/info/

func (*Client) GetPosts

func (c *Client) GetPosts(userID string) *Iterator[*Post]

GetPosts iterates over the timeline posts of a user (most recent first).

Endpoint: GET /api/v1/feed/user/<user_id>/?count=12&max_id=<cursor>

The user_id can be obtained from GetProfile(username).ID.

func (*Client) GetProfessionalAccountState added in v1.4.0

func (c *Client) GetProfessionalAccountState(ctx context.Context) (*ProfessionalAccountState, error)

GetProfessionalAccountState fetches professional type and display settings.

func (*Client) GetProfile

func (c *Client) GetProfile(ctx context.Context, username string) (*User, error)

GetProfile fetches a user's full profile by username.

Endpoint: GET /api/v1/users/web_profile_info/?username=<username>

func (*Client) GetProfileByID

func (c *Client) GetProfileByID(ctx context.Context, userID string) (*User, error)

GetProfileByID fetches a user's full profile by their numeric ID.

Endpoint: GET /api/v1/users/<user_id>/info/

func (*Client) GetReels

func (c *Client) GetReels(userID string) *Iterator[*Post]

GetReels iterates over the reels (clips) authored by a user.

Endpoint: POST /api/v1/clips/user/ with form body {target_user_id, max_id, page_size}

func (*Client) GetReelsMedia

func (c *Client) GetReelsMedia(ctx context.Context, reelIDs []string) (map[string]*StoryReel, error)

GetReelsMedia fetches the media items for one or more story reels.

reelIDs are user IDs for live reels, or "highlight:<id>" for highlights.

Endpoint: POST /api/v1/feed/reels_media/

func (*Client) GetStoryTray

func (c *Client) GetStoryTray(ctx context.Context) ([]*StoryReel, error)

GetStoryTray fetches the viewer's story tray (the row of profile circles at the top of the home feed).

Endpoint: GET /api/v1/feed/reels_tray/

func (*Client) GetSuggestedUsers

func (c *Client) GetSuggestedUsers(ctx context.Context, targetID string) ([]*User, error)

GetSuggestedUsers returns up to ~80 accounts Instagram suggests based on the given seed user (typically the logged-in user's ID for "Suggested for you", but any user ID works to get accounts related to that user).

Endpoint: GET /api/v1/discover/chaining/?target_id=<user_id>

func (*Client) GetTaggedPosts

func (c *Client) GetTaggedPosts(userID string) *Iterator[*Post]

GetTaggedPosts iterates over posts the user has been tagged in.

Endpoint: GET /api/v1/usertags/<user_id>/feed/

func (*Client) GetTimeline

func (c *Client) GetTimeline() *Iterator[*Post]

GetTimeline fetches the home timeline feed (Following + recommendations).

Endpoint: POST /api/v1/feed/timeline/

func (*Client) GetUserStories

func (c *Client) GetUserStories(ctx context.Context, userID string) (*StoryReel, error)

GetUserStories fetches a single user's current story.

Endpoint: GET /api/v1/feed/user/<user_id>/story/

Returns nil if the user has no story.

func (*Client) KeywordTypeahead added in v1.4.0

func (c *Client) KeywordTypeahead(ctx context.Context, query string) ([]string, error)

KeywordTypeahead returns lightweight suggestion strings for a partial keyword without loading a full search-results page. The inventory-proven typeahead stream currently returns account entities, so suggestions are their usernames (falling back to display names when necessary). Instagram may validly return an empty slice when it has no suggestions.

func (*Client) LikeComment

func (c *Client) LikeComment(ctx context.Context, commentID string) error

LikeComment likes a comment.

Endpoint: POST /api/v1/media/<comment_id>/comment_like/

func (*Client) LikePost

func (c *Client) LikePost(ctx context.Context, mediaPK string) error

LikePost likes a post.

Endpoint: POST /api/v1/media/<media_pk>/like/

func (*Client) MarkStorySeen

func (c *Client) MarkStorySeen(ctx context.Context, reelID, mediaPK string, takenAt int64) error

MarkStorySeen marks a story media item as seen by the viewer.

Endpoint: POST /api/v2/media/seen/ with form: reels[<reel_id>][]=<media_pk>_<reel_id>_<taken_at>

Note: subject to write rate-limiting; see ErrWriteSoftBlock.

func (*Client) Me

func (c *Client) Me(ctx context.Context) (*User, error)

Me returns the authenticated user's profile. The result is cached at New() time; subsequent calls return the cached value.

func (*Client) MutePosts

func (c *Client) MutePosts(ctx context.Context, userID string) error

MutePosts mutes the posts of a user without unfollowing.

Endpoint: POST /api/v1/friendships/mute_posts_or_story_from_follow/

func (*Client) PostComment

func (c *Client) PostComment(ctx context.Context, mediaPK, text string) (*Comment, error)

PostComment leaves a top-level comment on a post.

Endpoint: POST /api/v1/media/<media_pk>/comment/

Subject to write rate-limiting; Instagram is aggressive about silent soft-blocks here. See ErrWriteSoftBlock.

func (*Client) PublishPhoto added in v1.4.0

func (c *Client) PublishPhoto(ctx context.Context, in PublishPhotoInput) (*PublishResult, error)

PublishPhoto uploads and configures one feed photo.

func (*Client) PublishReel added in v1.4.0

func (c *Client) PublishReel(ctx context.Context, in PublishReelInput) (*PublishResult, error)

PublishReel uploads a video and its thumbnail, waits for captured media processing states, and configures one Reel.

func (*Client) PublishStory added in v1.4.0

func (c *Client) PublishStory(ctx context.Context, in PublishStoryInput) (*PublishResult, error)

PublishStory publishes one video Story using the captured thumbnail and processing/status contracts before configure.

func (*Client) RateLimit

func (c *Client) RateLimit() RateLimitState

RateLimit returns the most recent rate-limit observation.

func (*Client) SavePost

func (c *Client) SavePost(ctx context.Context, mediaPK string) error

SavePost saves a post to the user's collection.

Endpoint: POST /api/v1/media/<media_pk>/save/

func (*Client) Search

func (c *Client) Search(ctx context.Context, query string) (*SearchResult, error)

Search runs a topsearch across users, hashtags, and places.

Endpoint: GET /api/v1/web/search/topsearch/?context=blended&query=<q>

func (*Client) SearchAccounts added in v1.4.0

func (c *Client) SearchAccounts(ctx context.Context, query string) (*AccountSearchResult, error)

SearchAccounts searches the inventory-proven mobile account SERP and returns richer account card context than SearchUsers.

func (*Client) SearchKeywordPosts added in v1.4.0

func (c *Client) SearchKeywordPosts(query string) *Iterator[*Post]

SearchKeywordPosts iterates over posts from the authenticated web keyword search GraphQL connection. It uses the inventory-proven initial persisted operation for the first request and the distinct pagination operation for subsequent pages. Cursor returns an opaque, query-bound continuation value containing the Relay cursor and both search session IDs, so it can be passed to WithCursor on a fresh iterator without changing the search session.

Persisted document IDs are private, dated contracts and can rotate. A stale or malformed GraphQL response returns ErrUnexpectedResponse.

func (*Client) SearchLocations

func (c *Client) SearchLocations(ctx context.Context, query string) ([]*Location, error)

SearchLocations searches Instagram's location index by free-text query.

Endpoint: GET /api/v1/location_search/?search_query=<q>

func (*Client) SearchPosts added in v1.4.0

func (c *Client) SearchPosts(query string) *Iterator[*Post]

SearchPosts iterates over posts matching a free-text keyword from Instagram's mobile Top SERP. Results are ranked and personalized by Instagram; callers should deduplicate durable watches by Post.PK.

Endpoint: GET /api/v1/fbsearch/top_serp/

Cursor returns an opaque, query-bound continuation value containing all of the Top SERP pagination state. Pass it to WithCursor on a fresh iterator to resume the same normalized query.

func (*Client) SearchReels added in v1.4.0

func (c *Client) SearchReels(query string) *Iterator[*Post]

SearchReels returns the first inventory-proven mobile Reels SERP as an iterator of Post values. The captured endpoint exposes a continuation token, but the corresponding request parameter has not been proven, so the iterator deliberately makes at most one upstream request and rejects cursors locally.

func (*Client) SearchTypeaheadUsers added in v1.4.0

func (c *Client) SearchTypeaheadUsers(ctx context.Context, query string, count int) (*TypeaheadSearchResult, error)

SearchTypeaheadUsers fetches account suggestions from the inventory-proven mobile keyword typeahead stream. Pass count <= 0 to use the captured value of 30.

func (*Client) SearchUsers

func (c *Client) SearchUsers(ctx context.Context, query string, count int) ([]*User, error)

SearchUsers searches Instagram for users matching a query.

Endpoint: GET /api/v1/users/search/?q=<query>&count=<n>

count clamps to 50 server-side; pass 0 to use the default (~12).

func (*Client) SendDirectText added in v1.4.0

func (c *Client) SendDirectText(ctx context.Context, in DirectTextRequest) (*DirectSendResult, error)

SendDirectText creates/resolves a one-recipient thread and broadcasts a plain-text item. The entire mutation is capped at 30 seconds. Thread creation is never automatically retried; broadcast retries reuse the same client context, mutation token, and offline threading ID. To retry an uncertain broadcast without repeating non-idempotent thread creation, pass the ThreadID, ClientContext, and RetryToken from DirectSendError. The authenticated token binds those values to the original recipient and text.

func (*Client) SetPrivacy added in v1.4.0

SetPrivacy changes only the account's public/private state.

func (*Client) Unblock

func (c *Client) Unblock(ctx context.Context, userID string) (*FriendshipStatus, error)

Unblock unblocks a user.

Endpoint: POST /api/v1/friendships/unblock/<user_id>/

func (*Client) Unfollow

func (c *Client) Unfollow(ctx context.Context, userID string) (*FriendshipStatus, error)

Unfollow unfollows a user.

Endpoint: POST /api/v1/friendships/destroy/<user_id>/

func (*Client) UnfollowHashtag

func (c *Client) UnfollowHashtag(ctx context.Context, name string) error

UnfollowHashtag stops following a hashtag.

Endpoint: POST /api/v1/web/tags/unfollow/<name>/

func (*Client) UnlikeComment

func (c *Client) UnlikeComment(ctx context.Context, commentID string) error

UnlikeComment removes a like from a comment.

Endpoint: POST /api/v1/media/<comment_id>/comment_unlike/

func (*Client) UnlikePost

func (c *Client) UnlikePost(ctx context.Context, mediaPK string) error

UnlikePost removes a like from a post.

Endpoint: POST /api/v1/media/<media_pk>/unlike/

func (*Client) UnmutePosts

func (c *Client) UnmutePosts(ctx context.Context, userID string) error

UnmutePosts undoes a previous MutePosts.

Endpoint: POST /api/v1/friendships/unmute_posts_or_story_from_follow/

func (*Client) UnsavePost

func (c *Client) UnsavePost(ctx context.Context, mediaPK string) error

UnsavePost removes a post from the user's saved collection.

Endpoint: POST /api/v1/media/<media_pk>/unsave/

func (*Client) UpdateProfessionalSettings added in v1.4.0

UpdateProfessionalSettings changes only category ID and category visibility on an existing professional account. It cannot convert account type.

func (*Client) UpdateProfileFields added in v1.4.0

UpdateProfileFields changes only full name, biography, and external URL.

func (*Client) WaitForCooldown

func (c *Client) WaitForCooldown(ctx context.Context) error

WaitForCooldown blocks until any active read/write cooldown has expired, or the context is cancelled. Returns ctx.Err() on cancellation.

type ClipsMetadata

type ClipsMetadata struct {
	OriginalSoundInfo  *AudioInfo `json:"original_sound_info,omitempty"`
	MusicInfo          *AudioInfo `json:"music_info,omitempty"`
	AudioRankingInfo   *AudioInfo `json:"audio_ranking_info,omitempty"`
	OriginalAudioTitle string     `json:"original_audio_title,omitempty"`
}

ClipsMetadata is the reels-specific metadata attached to a Post when product_type == "clips".

type Comment

type Comment struct {
	ID                string `json:"pk,omitempty"`
	UserID            string `json:"user_id,omitempty"`
	User              *User  `json:"user,omitempty"`
	Text              string `json:"text,omitempty"`
	CreatedAt         int64  `json:"created_at,omitempty"`
	LikeCount         int    `json:"comment_like_count"`
	HasLikedComment   bool   `json:"has_liked_comment"`
	ChildCommentCount int    `json:"child_comment_count"`
	ParentCommentID   string `json:"parent_comment_id,omitempty"`

	Replies []*Comment `json:"child_comments,omitempty"`

	Raw json.RawMessage `json:"-"`
}

Comment is a top-level or threaded comment on a post.

type Cookies

type Cookies struct {
	SessionID string `json:"sessionid"`
	CSRFToken string `json:"csrftoken"`
	DSUserID  string `json:"ds_user_id"`
	Datr      string `json:"datr"`
	Mid       string `json:"mid"`
	IgDid     string `json:"ig_did"`
	Rur       string `json:"rur"`
	IgNrcb    string `json:"ig_nrcb"`
	PsL       string `json:"ps_l"`
	PsN       string `json:"ps_n"`
	Wd        string `json:"wd"`
}

Cookies holds the Instagram session cookies obtained from a browser export. SessionID, CSRFToken, and DSUserID are required; the rest help mimic a real browser session and reduce the chance of the request being blocked.

type CurrentAccount added in v1.4.0

type CurrentAccount struct {
	AccountID      string        `json:"account_id"`
	Username       string        `json:"username"`
	Profile        ProfileFields `json:"profile"`
	IsPrivate      bool          `json:"is_private"`
	IsProfessional bool          `json:"is_professional"`
	AccountType    int           `json:"account_type"`
}

CurrentAccount is the safe identity/profile projection of the authenticated account response. Contact and security data are never retained.

type DirectItem added in v1.4.0

type DirectItem struct {
	ID             string `json:"item_id"`
	ThreadID       string `json:"thread_id,omitempty"`
	UserID         string `json:"user_id,omitempty"`
	ItemType       string `json:"item_type,omitempty"`
	Text           string `json:"text,omitempty"`
	Timestamp      int64  `json:"timestamp,omitempty"`
	ClientContext  string `json:"client_context,omitempty"`
	IsSentByViewer bool   `json:"is_sent_by_viewer,omitempty"`

	Raw json.RawMessage `json:"-"`
}

DirectItem is one item in an Instagram Direct thread. Text is populated only for captured text items; attachments, reactions, vanish mode, and administrative events are intentionally left unsupported.

type DirectSendError added in v1.4.0

type DirectSendError struct {
	ClientContext string
	ThreadID      string
	RetryToken    string
	Err           error
}

DirectSendError preserves the idempotency context for a failed or uncertain send so callers can safely reconcile or retry the same logical broadcast.

func (*DirectSendError) Error added in v1.4.0

func (e *DirectSendError) Error() string

func (*DirectSendError) Unwrap added in v1.4.0

func (e *DirectSendError) Unwrap() error

type DirectSendResult added in v1.4.0

type DirectSendResult struct {
	RecipientID   string `json:"recipient_id"`
	ThreadID      string `json:"thread_id"`
	ItemID        string `json:"item_id,omitempty"`
	ClientContext string `json:"client_context"`
	Status        string `json:"status,omitempty"`
}

DirectSendResult identifies the created/resolved thread and broadcast item.

type DirectTextRequest added in v1.4.0

type DirectTextRequest struct {
	RecipientID   string `json:"recipient_id"`
	Text          string `json:"text"`
	ThreadID      string `json:"thread_id,omitempty"`
	ClientContext string `json:"client_context,omitempty"`
	RetryToken    string `json:"retry_token,omitempty"`
}

DirectTextRequest identifies the sole recipient and text for a Direct message. ThreadID, ClientContext, and the authenticated RetryToken may be supplied together to safely retry an uncertain broadcast without repeating thread creation. For a new send, leave all three empty and the SDK generates a cryptographically random context.

type DirectThread added in v1.4.0

type DirectThread struct {
	ID             string        `json:"thread_id"`
	Title          string        `json:"thread_title,omitempty"`
	Users          []*User       `json:"users,omitempty"`
	Items          []*DirectItem `json:"items,omitempty"`
	LastActivityAt int64         `json:"last_activity_at,omitempty"`
	IsGroup        bool          `json:"is_group,omitempty"`
	IsPending      bool          `json:"is_pending,omitempty"`
	Muted          bool          `json:"muted,omitempty"`
	ReadState      int           `json:"read_state,omitempty"`

	Raw json.RawMessage `json:"-"`
}

DirectThread is a conversation returned by the authenticated viewer's Instagram Direct inbox. Direct payloads are private; Raw must never be logged or persisted without explicit redaction.

type FriendshipStatus

type FriendshipStatus struct {
	Following       bool `json:"following"`
	FollowedBy      bool `json:"followed_by"`
	Blocking        bool `json:"blocking"`
	Muting          bool `json:"muting"`
	IsPrivate       bool `json:"is_private"`
	IncomingRequest bool `json:"incoming_request"`
	OutgoingRequest bool `json:"outgoing_request"`
	IsBestie        bool `json:"is_bestie"`
	IsRestricted    bool `json:"is_restricted"`
	IsFeedFavorite  bool `json:"is_feed_favorite"`
}

FriendshipStatus describes the relationship between the viewer and a user.

type Hashtag

type Hashtag struct {
	ID             string `json:"id,omitempty"`
	Name           string `json:"name"`
	MediaCount     int    `json:"media_count"`
	ProfilePicURL  string `json:"profile_pic_url,omitempty"`
	Following      bool   `json:"following"`
	FollowingCount int    `json:"following_count,omitempty"`

	Raw json.RawMessage `json:"-"`
}

Hashtag describes a hashtag with profile metadata.

type ImageVersion

type ImageVersion struct {
	URL    string `json:"url"`
	Width  int    `json:"width"`
	Height int    `json:"height"`
}

ImageVersion describes one resolution of a post's image.

type Iterator

type Iterator[T any] struct {
	// contains filtered or unexported fields
}

Iterator is the generic paginating iterator returned by all list endpoints.

Usage:

it := client.GetPosts(username)
for it.Next(ctx) {
    post := it.Item()
    fmt.Println(post.Code, post.LikeCount)
}
if err := it.Err(); err != nil {
    log.Fatal(err)
}

func (*Iterator[T]) Collect

func (it *Iterator[T]) Collect(ctx context.Context) ([]T, error)

Collect drains the iterator into a slice. Stops at maxPages if set.

func (*Iterator[T]) Cursor

func (it *Iterator[T]) Cursor() string

Cursor returns the endpoint-specific next-page cursor. It is opaque and can be passed to WithCursor on a fresh iterator to resume in a later process.

func (*Iterator[T]) Err

func (it *Iterator[T]) Err() error

Err returns the error that caused iteration to stop, if any.

func (*Iterator[T]) Item

func (it *Iterator[T]) Item() T

Item returns the current item. Only valid after Next returns true.

func (*Iterator[T]) Next

func (it *Iterator[T]) Next(ctx context.Context) bool

Next advances to the next item, fetching a new page if necessary. Returns false when there are no more items or an error occurred. Inspect Err.

func (*Iterator[T]) WithCursor added in v1.4.0

func (it *Iterator[T]) WithCursor(cursor string) *Iterator[T]

WithCursor starts the iterator from a cursor returned by Cursor. It must be called before Next or Collect. Cursor values are endpoint-specific and should be treated as opaque.

func (*Iterator[T]) WithMaxPages

func (it *Iterator[T]) WithMaxPages(n int) *Iterator[T]

WithMaxPages caps the number of upstream requests the iterator will make. Returns the iterator for chaining. 0 means unlimited.

type Location

type Location struct {
	ID               string  `json:"pk,omitempty"`
	ShortName        string  `json:"short_name,omitempty"`
	Name             string  `json:"name,omitempty"`
	Address          string  `json:"address,omitempty"`
	City             string  `json:"city,omitempty"`
	Lng              float64 `json:"lng,omitempty"`
	Lat              float64 `json:"lat,omitempty"`
	ExternalSource   string  `json:"external_source,omitempty"`
	FacebookPlacesID string  `json:"facebook_places_id,omitempty"`
	MediaCount       int     `json:"media_count,omitempty"`

	Raw json.RawMessage `json:"-"`
}

Location is a geo-tag attached to a post.

type LoginParams added in v1.3.0

type LoginParams struct {
	Username string
	Password string

	// SidecarURL is the base URL of the social-login sidecar (e.g.
	// "http://social-login:8090"). Required.
	SidecarURL string

	// ProxyURL, when set, is forwarded to the sidecar so the browser logs in
	// from a residential egress (Instagram challenges datacenter IPs).
	ProxyURL string

	// VerificationCode is the email/SMS code for Instagram's login challenge
	// (interposed from unfamiliar IPs). When empty, VerificationProvider is
	// consulted after the challenge is detected.
	VerificationCode string

	// VerificationProvider, when set, is called to fetch the login challenge
	// code on demand (e.g. read from the user's connected Gmail). It is only
	// invoked if the sidecar reports a verification challenge and no
	// VerificationCode was pre-supplied.
	VerificationProvider func(ctx context.Context) (string, error)

	// HTTPClient overrides the client used to talk to the sidecar. Optional.
	HTTPClient *http.Client
}

LoginParams configures a credential login via the social-login sidecar.

Instagram gates login behind Bloks-encrypted, browser-only JavaScript that is impractical to reproduce in a pure-Go client. Rather than reimplement it, we delegate the interactive login to the headless-browser social-login sidecar (see sidecars/social-login), which drives the real web login and returns the session cookies. Those cookies are then used by the normal Client for all API calls.

type LoginResult added in v1.3.0

type LoginResult struct {
	Cookies  Cookies
	FinalURL string
}

LoginResult holds the session minted by a credential login.

func Login added in v1.3.0

func Login(ctx context.Context, p LoginParams) (LoginResult, error)

Login performs a credential login through the social-login sidecar and returns the resulting session cookies. The caller passes the cookies to New to build an authenticated Client.

type MediaType

type MediaType int

MediaType is the Instagram media_type enum.

const (
	MediaTypeUnknown  MediaType = 0
	MediaTypePhoto    MediaType = 1
	MediaTypeVideo    MediaType = 2
	MediaTypeCarousel MediaType = 8
)

type MutationPreconditionError added in v1.4.0

type MutationPreconditionError struct {
	Field  string
	Reason string
}

MutationPreconditionError describes a locally rejected administration mutation. Field is an allowlisted field name and never contains a secret.

func (*MutationPreconditionError) Error added in v1.4.0

func (e *MutationPreconditionError) Error() string

func (*MutationPreconditionError) Unwrap added in v1.4.0

func (e *MutationPreconditionError) Unwrap() error

type Option

type Option func(*Client)

Option configures a Client.

func WithAPIAppID added in v1.4.0

func WithAPIAppID(id string) Option

WithAPIAppID overrides X-IG-App-ID on mobile API requests. It does not change the web app ID configured by WithAppID.

func WithAPIHost added in v1.4.0

func WithAPIHost(host string) Option

WithAPIHost overrides the origin used by explicitly mobile API requests, including the fbsearch SERP endpoints. The default is https://i.instagram.com. Existing web endpoints continue to use the WWW host and the same Client, HTTP transport, and cookie header.

func WithAPIUserAgent added in v1.4.0

func WithAPIUserAgent(ua string) Option

WithAPIUserAgent overrides the Instagram Android User-Agent used on mobile API requests. It does not change the web request profile configured by WithUserAgent.

func WithAppID

func WithAppID(id string) Option

WithAppID overrides X-IG-App-ID for existing web requests. The default (936619743392459) is Instagram Web's registered app ID. Use WithAPIAppID to override the separate mobile API request profile.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient replaces the default http.Client. Nil is ignored.

IMPORTANT: When supplying a custom client, leave the cookie jar nil. Instagram's 302-to-login responses include Set-Cookie: sessionid="" directives that will wipe the session if you use a CookieJar. All cookies are sent via the explicit Cookie header instead.

func WithMinRequestGap

func WithMinRequestGap(d time.Duration) Option

WithMinRequestGap sets the minimum time between consecutive read requests. Default: 4s. Lower values risk triggering Instagram's behavioural limiter.

func WithMinWriteGap

func WithMinWriteGap(d time.Duration) Option

WithMinWriteGap sets the minimum time between consecutive write requests (Follow, Like, Comment, Save, etc.). Default: 12s. Writes share a separate rate-limit budget from reads on Instagram's backend.

func WithProxy

func WithProxy(proxyURL string) Option

WithProxy routes all HTTP traffic through the given proxy URL.

func WithPublishingLimits added in v1.4.0

func WithPublishingLimits(photoBytes, videoBytes int64) Option

WithPublishingLimits configures maximum decoded upload sizes. Values must be positive to replace the conservative defaults (25 MiB photos, 100 MiB video). Limits are enforced while reading and before any Instagram request is made.

func WithPublishingTimeouts added in v1.4.0

func WithPublishingTimeouts(upload, processing time.Duration) Option

WithPublishingTimeouts configures the deadline for each upload request and for the complete processing/status wait. Positive values replace defaults.

func WithRateLimitCooldown

func WithRateLimitCooldown(read, write time.Duration) Option

WithRateLimitCooldown sets how long the client refuses requests after observing a rate-limit signal from Instagram (a "wait a few minutes" body or a 302-to-login redirect). Reads and writes have independent cooldown budgets.

Pass 0 to use the defaults (5m read, 15m write). Pass any positive value to override; pass a tiny value (e.g. 1ms) to effectively disable the circuit-breaker (not recommended). Write cooldowns are capped at 30 minutes so callers always retain a bounded recovery window.

func WithRetry

func WithRetry(maxAttempts int, base time.Duration) Option

WithRetry configures retry behaviour. Set maxAttempts to 0 to disable retries. Default: 3 attempts, 750ms exponential base.

func WithSkipSessionValidation

func WithSkipSessionValidation() Option

WithSkipSessionValidation disables the initial session check inside New. Useful for offline tests or when the caller wants to defer validation.

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent overrides the User-Agent string for existing web requests. Most desktop browser UAs are rejected with "useragent mismatch". Use WithAPIUserAgent to override the separate mobile API request profile.

func WithWWWHost added in v1.4.0

func WithWWWHost(host string) Option

WithWWWHost overrides the origin used by existing web endpoints and web GraphQL requests. The default is https://www.instagram.com.

This option is primarily useful for private proxies and tests. The value must be an absolute HTTP(S) origin; a trailing slash is ignored.

type Page

type Page[T any] struct {
	Items      []T
	NextCursor string
	HasMore    bool
}

Page is one page of a paginated response. NextCursor is empty when there are no more results. Iterator-backed methods expose it through Cursor; pass it to WithCursor on a fresh iterator to fetch the following page.

type PageOptions

type PageOptions struct {
	// Cursor is the next_max_id (or equivalent) returned from a previous page.
	// Leave empty to fetch the first page.
	Cursor string
	// Limit caps the number of items per request. Instagram clamps this server-side
	// (typically 12-50 depending on the endpoint); 0 uses the endpoint default.
	Limit int
}

PageOptions configures a paginated request.

type Place

type Place struct {
	Title    string    `json:"title,omitempty"`
	Subtitle string    `json:"subtitle,omitempty"`
	Location *Location `json:"location,omitempty"`
}

Place is a search result wrapping a Location with extra subtitle text.

type Post

type Post struct {
	ID            string    `json:"id,omitempty"`
	PK            string    `json:"pk,omitempty"`
	Code          string    `json:"code,omitempty"`
	MediaType     MediaType `json:"media_type"`
	ProductType   string    `json:"product_type,omitempty"`
	TakenAt       int64     `json:"taken_at,omitempty"`
	Caption       string    `json:"caption_text,omitempty"`
	CaptionUserID string    `json:"caption_user_id,omitempty"`
	Owner         *User     `json:"user,omitempty"`

	LikeCount      int     `json:"like_count"`
	CommentCount   int     `json:"comment_count"`
	ViewCount      int     `json:"view_count,omitempty"`
	PlayCount      int     `json:"play_count,omitempty"`
	IGTVViewCount  int     `json:"igtv_view_count,omitempty"`
	ReshareCount   int     `json:"reshare_count,omitempty"`
	SaveCount      int     `json:"save_count,omitempty"`
	OriginalWidth  int     `json:"original_width,omitempty"`
	OriginalHeight int     `json:"original_height,omitempty"`
	VideoDurationS float64 `json:"video_duration,omitempty"`

	HasLiked      bool `json:"has_liked,omitempty"`
	IsPinned      bool `json:"is_pinned,omitempty"`
	IsPaidPartner bool `json:"is_paid_partnership,omitempty"`

	ImageVersions []ImageVersion `json:"image_versions,omitempty"`
	VideoVersions []VideoVersion `json:"video_versions,omitempty"`
	CarouselMedia []*Post        `json:"carousel_media,omitempty"`

	Hashtags []string `json:"hashtags,omitempty"`
	Mentions []string `json:"mentions,omitempty"`

	Location *Location `json:"location,omitempty"`

	ClipsMetadata *ClipsMetadata `json:"clips_metadata,omitempty"`

	// PermalinkURL is constructed from the shortcode as
	// https://www.instagram.com/p/<code>/ for posts and /reel/<code>/ for
	// product_type==clips.
	PermalinkURL string `json:"-"`

	// Raw is the complete media payload from the source endpoint.
	Raw json.RawMessage `json:"-"`
}

Post is a media item — photo, video, reel, carousel, or IGTV.

type ProfessionalAccountState added in v1.4.0

type ProfessionalAccountState struct {
	AccountID      string               `json:"account_id"`
	IsProfessional bool                 `json:"is_professional"`
	IsBusiness     bool                 `json:"is_business"`
	AccountType    int                  `json:"account_type"`
	CategoryName   string               `json:"category_name,omitempty"`
	Settings       ProfessionalSettings `json:"settings"`
}

ProfessionalAccountState describes professional status and its reversible display settings. IsProfessional and AccountType are read-only.

type ProfessionalSettings added in v1.4.0

type ProfessionalSettings struct {
	CategoryID      string `json:"category_id" jsonschema:"required"`
	DisplayCategory bool   `json:"display_category" jsonschema:"required"`
}

ProfessionalSettings is the narrow professional-display allowlist. It does not permit account conversion, contact changes, ownership, or security work.

type ProfileFields added in v1.4.0

type ProfileFields struct {
	FullName    string `json:"full_name" jsonschema:"required"`
	Biography   string `json:"biography" jsonschema:"required"`
	ExternalURL string `json:"external_url" jsonschema:"required"`
}

ProfileFields is the complete allowlist for profile administration. It intentionally excludes username, email, phone, and every security field.

type PublishError added in v1.4.0

type PublishError struct {
	Stage      string
	UploadID   string
	ClientID   string
	Partial    bool
	Underlying error
}

PublishError reports the safe protocol stage and deterministic identifiers involved in a failure. It never retains media bytes, captions, or credentials.

func (*PublishError) Error added in v1.4.0

func (e *PublishError) Error() string

func (*PublishError) Unwrap added in v1.4.0

func (e *PublishError) Unwrap() []error

type PublishPhotoInput added in v1.4.0

type PublishPhotoInput struct {
	Media          UploadSource
	Caption        string
	IdempotencyKey string
}

PublishPhotoInput is the typed input for a single-image feed publication.

type PublishReelInput added in v1.4.0

type PublishReelInput struct {
	Media          UploadSource
	Thumbnail      UploadSource
	Caption        string
	IdempotencyKey string
}

PublishReelInput is the typed input for a Reel. Thumbnail is required because the captured Reel contract contains a separate thumbnail upload.

type PublishResult added in v1.4.0

type PublishResult struct {
	MediaID  string             `json:"media_id"`
	Code     string             `json:"code,omitempty"`
	UploadID string             `json:"upload_id"`
	ClientID string             `json:"client_id"`
	Kind     PublishedMediaKind `json:"kind"`
}

PublishResult identifies exactly one created media item and the deterministic identifiers used across upload, processing, and configure stages.

type PublishStoryInput added in v1.4.0

type PublishStoryInput struct {
	Media          UploadSource
	Thumbnail      *UploadSource
	Caption        string
	IdempotencyKey string
}

PublishStoryInput is the typed input for a video Story. Thumbnail is required. Photo Story publishing is deliberately absent until separately captured.

type PublishedMediaKind added in v1.4.0

type PublishedMediaKind string

PublishedMediaKind identifies the captured configure/delete contract for a created media item.

const (
	PublishedMediaPhoto PublishedMediaKind = "photo"
	PublishedMediaReel  PublishedMediaKind = "reel"
	PublishedMediaStory PublishedMediaKind = "story"
)

type RateLimitState

type RateLimitState struct {
	LastBlockedAt       time.Time
	LastReadAt          time.Time
	LastWriteAt         time.Time
	CooldownReadUntil   time.Time
	CooldownWriteUntil  time.Time
	WriteBlocked        bool
	BlockedReason       string
	CapacityLevel       int    // 0 = degraded, 3 = healthy (-1 = unknown)
	PeakTime            bool   // x-ig-peak-time
	PeakV2              bool   // x-ig-peak-v2
	ConnectionQuality   string // x-fb-connection-quality verbatim
	OriginRegion        string // x-ig-origin-region
	ServerRegion        string // x-ig-server-region
	LastServerElapsedMs int    // x-ig-request-elapsed-time-ms
}

RateLimitState is the most recent rate-limit observation, parsed from both Instagram's response body cues ("wait a few minutes") and the soft signals Instagram exposes via response headers (x-ig-*, x-fb-connection-quality).

Instagram does NOT publish standard rate-limit headers. The following are the closest server-side hints we can act on:

  • x-ig-capacity-level: 0–3, where 3 = healthy, 0 = degraded
  • x-ig-peak-time: "1" if Instagram considers traffic at peak
  • x-ig-peak-v2: secondary peak hint
  • x-fb-connection-quality: e.g. "EXCELLENT; q=0.9, rtt=18, ..."

CooldownReadUntil / CooldownWriteUntil are set when Instagram returns the "Please wait a few minutes" body or a 302-to-login (the soft-block pattern). All subsequent requests of the same kind block until the cooldown elapses.

Use Client.RateLimit() to read, Client.WaitForCooldown() to block until clear.

type SearchResult

type SearchResult struct {
	Users    []*User    `json:"users,omitempty"`
	Hashtags []*Hashtag `json:"hashtags,omitempty"`
	Places   []*Place   `json:"places,omitempty"`
}

SearchResult bundles users, hashtags, and places returned by /web/search/topsearch/.

type SetPrivacyParams added in v1.4.0

type SetPrivacyParams struct {
	ExpectedAccountID string `json:"expected_account_id" jsonschema:"required"`
	Before            *bool  `json:"before" jsonschema:"required"`
	After             *bool  `json:"after" jsonschema:"required"`
	Confirm           bool   `json:"confirm" jsonschema:"required"`
}

SetPrivacyParams guards one public/private transition.

type Story

type Story struct {
	ID            string         `json:"pk,omitempty"`
	MediaType     MediaType      `json:"media_type"`
	TakenAt       int64          `json:"taken_at,omitempty"`
	ExpiringAt    int64          `json:"expiring_at,omitempty"`
	User          *User          `json:"user,omitempty"`
	ImageVersions []ImageVersion `json:"image_versions,omitempty"`
	VideoVersions []VideoVersion `json:"video_versions,omitempty"`
	Audience      string         `json:"audience,omitempty"`

	Raw json.RawMessage `json:"-"`
}

Story is one item from a user's story tray.

type StoryReel

type StoryReel struct {
	ID              string   `json:"id,omitempty"`
	User            *User    `json:"user,omitempty"`
	Title           string   `json:"title,omitempty"`
	Items           []*Story `json:"items,omitempty"`
	HasMore         bool     `json:"-"`
	LatestReelMedia int64    `json:"-"`

	Raw json.RawMessage `json:"-"`
}

StoryReel is a story tray item — one user's set of stories or a highlight.

type TypeaheadSearchResult added in v1.4.0

type TypeaheadSearchResult struct {
	Users     []*User `json:"users"`
	RankToken string  `json:"rank_token,omitempty"`
}

TypeaheadSearchResult is the typed account context returned by the mobile keyword typeahead stream.

type UpdateProfessionalSettingsParams added in v1.4.0

type UpdateProfessionalSettingsParams struct {
	ExpectedAccountID string                `json:"expected_account_id" jsonschema:"required"`
	Before            *ProfessionalSettings `json:"before" jsonschema:"required"`
	After             *ProfessionalSettings `json:"after" jsonschema:"required"`
	Confirm           bool                  `json:"confirm" jsonschema:"required"`
}

UpdateProfessionalSettingsParams guards reversible display-only settings.

type UpdateProfileFieldsParams added in v1.4.0

type UpdateProfileFieldsParams struct {
	ExpectedAccountID string         `json:"expected_account_id" jsonschema:"required"`
	Before            *ProfileFields `json:"before" jsonschema:"required"`
	After             *ProfileFields `json:"after" jsonschema:"required"`
	Confirm           bool           `json:"confirm" jsonschema:"required"`
}

UpdateProfileFieldsParams requires a complete before/after pair and an explicit confirmation. ExpectedAccountID binds the operation to one account.

type UploadSource added in v1.4.0

type UploadSource struct {
	Reader   io.Reader
	Filename string
	MIMEType string
	Size     int64
	Width    int
	Height   int
	Duration time.Duration
}

UploadSource describes a bounded media stream. Size is the exact decoded byte length, not a base64 length. Reader is consumed once by a publish call.

type User

type User struct {
	ID                  string   `json:"pk_id,omitempty"`
	Username            string   `json:"username,omitempty"`
	FullName            string   `json:"full_name,omitempty"`
	Biography           string   `json:"biography,omitempty"`
	ExternalURL         string   `json:"external_url,omitempty"`
	ProfilePicURL       string   `json:"profile_pic_url,omitempty"`
	ProfilePicURLHD     string   `json:"profile_pic_url_hd,omitempty"`
	IsPrivate           bool     `json:"is_private,omitempty"`
	IsVerified          bool     `json:"is_verified,omitempty"`
	IsBusiness          bool     `json:"is_business,omitempty"`
	IsProfessional      bool     `json:"is_professional_account,omitempty"`
	BusinessCategory    string   `json:"business_category_name,omitempty"`
	Category            string   `json:"category_name,omitempty"`
	FollowerCount       int      `json:"follower_count,omitempty"`
	FollowingCount      int      `json:"following_count,omitempty"`
	MediaCount          int      `json:"media_count,omitempty"`
	TotalIGTVCount      int      `json:"total_igtv_videos,omitempty"`
	HasReels            bool     `json:"has_clips,omitempty"`
	HasGuides           bool     `json:"has_guides,omitempty"`
	HasChaining         bool     `json:"has_chaining,omitempty"`
	HasHighlightReels   bool     `json:"has_highlight_reels,omitempty"`
	HideLikeAndViewCnts bool     `json:"hide_like_and_view_counts,omitempty"`
	IsBusinessOwned     bool     `json:"is_business_owned_by_viewer,omitempty"`
	PublicEmail         string   `json:"public_email,omitempty"`
	PublicPhone         string   `json:"public_phone_number,omitempty"`
	ContactPhone        string   `json:"contact_phone_number,omitempty"`
	AddressStreet       string   `json:"address_street,omitempty"`
	City                string   `json:"city_name,omitempty"`
	Zip                 string   `json:"zip,omitempty"`
	AccountType         int      `json:"account_type,omitempty"`
	Pronouns            []string `json:"pronouns,omitempty"`
	SearchSERPType      string   `json:"search_serp_type,omitempty"`
	SearchSocialContext string   `json:"search_social_context,omitempty"`
	SocialContext       string   `json:"social_context,omitempty"`
	IsSearchBoosted     bool     `json:"is_verified_search_boosted,omitempty"`

	FriendshipStatus *FriendshipStatus `json:"friendship_status,omitempty"`

	// Raw is the complete user payload from the source endpoint.
	// It lets callers access fields that aren't yet typed.
	Raw json.RawMessage `json:"-"`
}

User represents an Instagram user/profile. Fields are populated based on which endpoint returned the data; not all fields are present everywhere.

IDs are strings because Instagram returns 64-bit numeric IDs that exceed JS Number safe range and are sometimes serialised as strings, sometimes as numbers. The client normalises to strings.

type VideoVersion

type VideoVersion struct {
	URL    string `json:"url"`
	Width  int    `json:"width"`
	Height int    `json:"height"`
	Type   int    `json:"type,omitempty"`
}

VideoVersion describes one resolution of a post's video.

Directories

Path Synopsis
cmd
instagram-account-inventory command
Command instagram-account-inventory captures a secret-scrubbed, read-only contract for the authenticated account administration surface.
Command instagram-account-inventory captures a secret-scrubbed, read-only contract for the authenticated account administration surface.
instagram-direct-inventory command
Command instagram-direct-inventory validates and redacts a burner-account HAR containing the four Instagram Direct contracts used by the SDK.
Command instagram-direct-inventory validates and redacts a burner-account HAR containing the four Instagram Direct contracts used by the SDK.
instagram-login-probe command
Command instagram-login-probe is an end-to-end inventory smoke test.
Command instagram-login-probe is an end-to-end inventory smoke test.
instagram-publish-inventory command
Command instagram-publish-inventory validates and redacts burner-only HAR captures for Instagram photo, Reel, and video Story publishing.
Command instagram-publish-inventory validates and redacts burner-only HAR captures for Instagram photo, Reel, and video Story publishing.
instagram-search-inventory command
Command instagram-search-inventory captures a secret-scrubbed inventory of Instagram's private keyword-search surfaces.
Command instagram-search-inventory captures a secret-scrubbed inventory of Instagram's private keyword-search surfaces.
Package mcp exposes the instagram-go instagram.Client surface as a set of MCP (Model Context Protocol) tools that any host application can mount on its own MCP server.
Package mcp exposes the instagram-go instagram.Client surface as a set of MCP (Model Context Protocol) tools that any host application can mount on its own MCP server.
Package meta implements the official Meta Graph and Marketing APIs for Instagram professional accounts.
Package meta implements the official Meta Graph and Marketing APIs for Instagram professional accounts.
mcp
Package mcp exposes the official Meta Graph client through an independent MCP provider.
Package mcp exposes the official Meta Graph client through an independent MCP provider.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL