instagrapi

challenge_required: how to fix the Instagram challenge in instagrapi (Python)

Maintained by the instagrapi contributors · Library on GitHub

Updated

You wrote a small script that calls cl.login(), ran it for the third time today, and Python printed instagrapi.exceptions.ChallengeRequired: challenge_required. The script aborts before any of your actual scraping or posting code runs. Open the Instagram app on your phone and there is a banner: “Help us confirm it’s you.” Reading that banner is the first useful piece of information — Instagram does not consider this an error in the bug-report sense. It is a fraud-prevention checkpoint, fired by a server-side risk model that decided the request looked like an attacker rather than the legitimate account owner. instagrapi surfaces the signal as a ChallengeRequired exception so your code can decide what to do; it cannot decide for you, because completing the challenge requires reading a code from SMS or email and feeding it back through the verification flow.

This page walks through what Instagram is actually doing, why fresh Client() instances trip the model so reliably, and the four-step fix that stops the loop: handler, persist, reuse, pin. The heart of the fix is session reuse — most developers hit challenge_required not because their account is genuinely flagged but because every script run looks like a brand-new device on a brand-new IP, and Instagram cannot tell the difference between that and a stolen credential. Once your fingerprint stops resetting, the challenge rate drops to near-zero. If you are arriving here in the middle of debugging a production worker, the Fix in instagrapi section is what you want; otherwise read top-to-bottom.

Symptoms

ChallengeRequired is raised inside pre_login_flow() — that is, before login() returns. The script does not get a Client you can call methods on; the exception fires during the handshake itself. You will see a Python traceback that bottoms out in instagrapi/mixins/challenge.py and the message will be the literal string challenge_required.

  • The exception fires on the first call to cl.login(), not later in the session.
  • Opening the Instagram mobile app on the same account shows a “Help us confirm it’s you” prompt that must be cleared manually unless you have wired a handler.
  • cl.last_response.json() (if you catch the exception and inspect the response) contains a challenge key with api_path, challenge_context, and either SMS or EMAIL choices.
  • Sometimes the challenge fires mid-session on a previously-good Client after IP rotation or after a long idle gap — but the more common pattern is at startup.
  • Retrying with the same script and no code changes will keep raising the same exception until either the challenge is resolved or Instagram escalates to checkpoint_required.

Real-world traceback

Traceback (most recent call last):
  File "automation.py", line 14, in <module>
    cl.login("USERNAME", "PASSWORD")
  File ".../instagrapi/mixins/account.py", line 142, in login
    self.pre_login_flow()
  File ".../instagrapi/mixins/challenge.py", line 87, in pre_login_flow
    raise ChallengeRequired(message=last["message"])
instagrapi.exceptions.ChallengeRequired: challenge_required

The frame to look at is instagrapi.mixins.challenge.pre_login_flow. That function runs as part of the bootstrap sequence the first time login() is called on a fresh Client. It pings a low-privilege endpoint to ask Instagram whether the device is trusted; if Instagram returns a challenge envelope instead of the expected payload, pre_login_flow raises immediately. Because this happens before the actual credential POST, the exception is not telling you your password is wrong — it is telling you Instagram refused to run the login at all.

Why it happens

The risk model that decides whether to issue a challenge weighs many signals, but for instagrapi users three patterns dominate. Understanding which one you are hitting is the difference between a five-minute fix and a multi-day debugging session.

1. Fresh device fingerprint every run

This is the cause most instagrapi users hit and the one that is unique to library usage rather than browser usage. When you write cl = Client() without first calling cl.load_settings(), the constructor generates a brand-new set of Android device IDs: device_id, phone_id, uuid, client_session_id, plus a randomly-chosen device profile from a built-in list of phone models. From Instagram’s perspective, every script run looks like the user just unboxed a new phone, signed into Wi-Fi at a new location, and is logging in for the first time. The risk model treats that combination as overwhelmingly suspicious. The mobile app does not have this problem because the device IDs are baked into the OS install and never change. Your script can have the same property — but only if you persist them.

2. New IP on a residential or datacenter ASN Instagram has never seen for this account

The risk model layers IP reputation on top of fingerprint. A datacenter ASN — AWS, GCP, DigitalOcean, Hetzner — is flagged immediately because no real Instagram user logs in from one. Residential IPs are not flagged on their own, but if the account has been logging in from a particular city for months and suddenly appears on a residential IP from a different country, the impossible-travel signal fires. Pinning a single residential proxy per account gives Instagram a stable view; rotating proxies on every request, somewhat counterintuitively, makes the problem worse.

3. Account-level risk signals: account age, recent password change, sudden volume spike

Accounts younger than about a month are flagged hard for any automation-shaped behavior. So are accounts that just changed their password, just enabled two-factor auth, or just logged in from a new country in the mobile app. Sudden volume spikes also trigger the model: an account that posted twice a week for two years and then made fifty API calls in an hour will earn a challenge regardless of fingerprint quality.

4. Geo-anomaly and impossible-travel patterns

A subtler version of the IP problem: even if both endpoints are residential, if the timestamps imply the user moved 5000 km in 30 minutes, the model treats it as a hijack signal. This shows up most often when a developer tests locally in one country and then deploys the same session.json to a server in another. Even though the cookie jar is valid, the geographic discontinuity is enough to fire challenge_required on the next privileged call.

Fix in instagrapi

The fix is a four-step sequence: wire a handler, run login interactively once, persist the post-challenge session, and reuse the persisted session forever. Skipping the persistence step is the single most common mistake — without it, you solve the challenge once and then trigger it again on the next run because the device fingerprint resets.

  1. Wire a challenge_code_handler so Instagram has somewhere to deliver the verification code. This is the callback instagrapi invokes when the API decides the user must verify. The signature is (username: str, choice: ChallengeChoice) -> str and it must return the 6-digit code as a string. For development, an input() prompt is fine; in production, you wire this to a webhook or an inbox poller.

    from instagrapi import Client
    from instagrapi.mixins.challenge import ChallengeChoice
    
    def handler(username: str, choice: ChallengeChoice) -> str:
        return input(f"Enter the {choice.name} code sent for {username}: ")
    
    cl = Client()
    cl.challenge_code_handler = handler
    cl.login("YOUR_USERNAME", "YOUR_PASSWORD")
  2. Persist the session immediately after login() returns successfully. The post-challenge cookie jar contains a fresh authentication token and — equally important — the device fingerprint that Instagram now associates with this account. Dump it with cl.dump_settings("session.json"). If you forget this step, you discard the proof-of-trust the moment the script exits.

  3. On every subsequent run, load the session before logging in. Calling load_settings() before login() reuses the same fingerprint and cookie jar. Instagram sees a returning client, not a new device, and challenge_required does not fire. If the cookie has expired, instagrapi re-runs login() against the saved fingerprint, which is much less likely to challenge than a fresh Client.

    from pathlib import Path
    from instagrapi import Client
    
    cl = Client()
    session_file = Path("session.json")
    if session_file.exists():
        cl.load_settings(session_file)
    cl.login("YOUR_USERNAME", "YOUR_PASSWORD")
    cl.dump_settings(session_file)  # refresh after every login
  4. Pin a residential proxy per account. See the proxy setup guide for the full setup. The short version: one residential IP per account, set with cl.set_proxy() before login(), kept stable for the lifetime of the account. Datacenter IPs will not work for accounts that already exist; they will not pass the first challenge.

Deep dive

Instagram offers two challenge channels, SMS and EMAIL, and the choice is made server-side based on which contact methods the account has verified. You cannot force one over the other from the client — the ChallengeChoice enum your handler receives tells you which one Instagram picked, and you have to deliver a code on that channel. In practice this means production workers need both an SMS provider integration and an email inbox poller, because you do not get to pick which the API will demand on any given run. Throwaway-number SMS services frequently fail here because Instagram maintains a blocklist of known short-code reseller gateways; using a real number from a real provider like Twilio is the only reliable option.

There is also a closely-related exception, CheckpointRequired, which surfaces when Instagram has escalated past a soft challenge to a hard checkpoint that must be cleared by the account owner manually. The two are easy to confuse because both can be raised from pre_login_flow. To distinguish them at runtime, catch ChallengeRequired and inspect cl.last_response.json()['message']: a value of challenge_required is the soft form this page covers, while checkpoint_required indicates Instagram has already decided the account needs human intervention. The fix path is different — checkpoint_required generally cannot be resolved by your worker and should page an operator instead.

Related errors

Related guides

Frequently asked

What does challenge_required mean?

challenge_required is Instagram's anti-fraud signal — your request looked suspicious (new IP, new device, unusual pattern) and the account must verify via SMS or email before continuing. instagrapi raises ChallengeRequired when this happens.

Why does instagrapi keep raising challenge_required on every login?

Almost always: the device fingerprint changes between runs because you're not reusing the saved session. dump_settings()/load_settings() preserves the device fingerprint and cookie jar so Instagram sees the same client every time, which prevents challenge_required from re-firing.

Can I resolve challenge_required without typing the code by hand?

Yes — wire challenge_code_handler to a Twilio webhook or your email inbox poller, and return the 6-digit code from the callback.

Why did challenge_required come back even after I solved it once?

Instagram re-challenges when it sees a new fingerprint. If you logged in fresh after solving the challenge, you discarded the post-challenge cookies. Always dump_settings() right after the resolved login.

Skip the infra?

Managed Instagram API — same endpoints, sessions and proxies handled.

Try HikerAPI → Full comparison
More from the team