Repository navigation
fix: logged-in posts() via GraphQL timeline; current browser headers; scoped cookies; loud errors - #128
Merged
Conversation
…ndpoint Instagram now login-walls the /api/v1/feed/user/ endpoint and rejects the browser identity ensta sent (Chrome 119 UA with sec-ch-ua set to the full UA string, a random x-ig-www-claim, and 2023-era x-asbd-id). Even a valid logged-in session was redirected to the login page. - New ensta/lib/WebHeaders.py: single source for UA, matching sec-ch-ua* client hints, x-ig-app-id and x-asbd-id, plus an api_headers() builder. - Guest/WebSession/Authentication use it instead of hand-copied headers. - x-ig-www-claim starts at "0" and is updated from the x-ig-set-www-claim response header (WebSession installs a response hook), instead of a random "hmac.<garbage>" value. - x-csrftoken is read from the cookie jar at request time so it matches the csrftoken cookie Instagram set. - posts(): optional user_id= selects the numeric feed endpoint the web app itself calls; redirects are no longer followed and are reported as a login wall; 401/require_login, non-JSON and unexpected bodies now include HTTP status, URL and a body snippet in the exception message. - Tests for the feed request shape and error reporting. Claude-Session: https://claude.ai/code/session_01AbNFrVSELUP8MhgbS8iQXe
After the first real Instagram response the jar holds two "csrftoken" cookies (ensta's domainless placeholder and the .instagram.com one), and RequestsCookieJar.get() raises CookieConflictError. csrf_token_from() prefers the instagram.com-scoped value. profile() now sends the real csrftoken and the remembered www-claim and reports HTTP status + body snippet on non-JSON responses. Claude-Session: https://claude.ai/code/session_01AbNFrVSELUP8MhgbS8iQXe
…ookies Instagram now answers the /api/v1/feed/user/ REST endpoint with a redirect to the home page even for valid logged-in sessions (both the /username/ and numeric variants). The web app fetches the profile timeline through POST /graphql/query with the persisted PolarisProfilePostsQuery doc_id, and that still works for logged-in sessions, so WebSession.posts() now does the same. The request is deliberately minimal (variables, doc_id, server_timestamps; x-ig-app-id, x-csrftoken, referer): adding the web app's own request markers without its per-page LSD/DTSG tokens makes Instagram answer with the HTML app shell instead of JSON. The response nodes have the v1 media shape, so the existing post parser is reused. Pagination follows page_info.end_cursor. Session cookies are now set with domain=.instagram.com. Set without a domain they lingered next to the fresh domain-scoped cookies Instagram sends back, and every request carried duplicate sessionid/csrftoken/rur/ ds_user_id/ig_did cookies. Claude-Session: https://claude.ai/code/session_01AbNFrVSELUP8MhgbS8iQXe
Instagram answers the timeline query for a deleted/renamed account with "execution error" and description "User lookup returned null". That was being reported as "doc_id may be retired", which points operators at the wrong fix. Error descriptions are now included in the message. Claude-Session: https://claude.ai/code/session_01AbNFrVSELUP8MhgbS8iQXe
| for cookie in session.cookies: | ||
| if cookie.name != "csrftoken": | ||
| continue | ||
| if "instagram.com" in (cookie.domain or ""): |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Since early September 2026
Guest.posts()/Web.posts()fail for everyone:/api/v1/feed/user/<name>/username/answers401 {"require_login": true, "igweb_rollout": true}from any IP. Instagram has login-walled the guest feed./api/v1/feed/user/<id>/variant) answers302 -> https://www.instagram.com/, so ensta reported the misleadingHTTP response doesn't include 'status' or 'items' node.Two things in ensta made the logged-in path fail even where Instagram would have served it:
sec-ch-ua/sec-ch-ua-full-version-listset to the full UA string (not the brand list), a randomx-ig-www-claim(hmac.<garbage>), a randomx-csrftokenthat never matched the cookie Instagram set, and 2023-erax-asbd-idvalues..instagram.comcopy and every request carried duplicatesessionid/csrftoken/rur/ds_user_id/ig_didcookies (RequestsCookieJar.get("csrftoken")even raisesCookieConflictError).Changes
ensta/lib/WebHeaders.py: single source for the UA, matchingsec-ch-ua*client hints,x-ig-app-id,x-asbd-id;api_headers()builder;csrf_token_from()(prefers the.instagram.comcookie);describe_response()for error messages.Guest,WebSession,Authenticationuse it instead of hand-copied headers.x-ig-www-claimstarts at"0"and is updated from thex-ig-set-www-claimresponse header (WebSessioninstalls a response hook)..instagram.com.WebSession.posts()now fetches the profile timeline the way the web app does:POST /graphql/querywith the persistedPolarisProfilePostsQuerydoc_id (34579740524958711;7898261790222653is retired and answers "execution error"). The request is deliberately minimal (variables,doc_id,server_timestamps;x-ig-app-id,x-csrftoken,referer) — adding the web app's own markers without its per-page LSD/DTSG tokens makes Instagram answer with the HTML app shell. Response nodes have the v1 media shape, so__process_post_datais reused; pagination followspage_info.end_cursor.Guest.posts(): optionaluser_id=for the numeric endpoint; redirects are no longer followed; 401/require_login, non-JSON and unexpected bodies now include HTTP status, URL and a body snippet in the exception.test_ensta/test_guest_posts.py,test_ensta/test_web_posts.py(request shape, pagination, cookie scoping, error reporting).Verification
Live, with a real logged-in session on 2026-09-12:
Web(...).posts("<public venue>", 3)returns 3 posts (codes,taken_at, captions, share URLs) withhas_next_page=True. The unauthenticatedGuest.posts()still cannot work — Instagram requires login now — but it fails with an actionable message instead of a misleading one.https://claude.ai/code/session_01AbNFrVSELUP8MhgbS8iQXe