The short answer: challenge_required and checkpoint_required are not scraping errors — they are account state errors. Instagram has flagged the account your client is using and is asking a human to complete a verification step in the official app or browser. No amount of retry logic, header tuning, or endpoint switching clears them, because the problem is not the request. It is the identity behind it.
feedback_required is the same family with a different meaning: the account is rate-limited or restricted for that action, and the restriction lifts on Instagram’s schedule, not yours. This is why the “blocked” bucket needs triage before code changes. Getting it wrong is how people lose accounts.
Do not attempt to automate past a challenge or checkpoint. Do not use third-party “challenge solver”, “checkpoint unlock”, or “bypass” services, and never hand your credentials, session cookies, or 2FA codes to one. Those services are credential-harvesting operations, and automated challenge-solving violates Instagram’s terms. Complete verification yourself, in the official app, as the account owner.
Which response do you actually have?
| Response body | What it means | Correct response |
|---|---|---|
{"message":"challenge_required","status":"fail"} (often with challenge_url) | The account must pass a verification challenge before further authenticated requests | Stop the scraper. Complete the challenge yourself in the official app. |
{"message":"checkpoint_required","status":"fail"} | Same gate, surfaced by different client libraries; the account is held at a security checkpoint | Same: manual verification, then a long cooldown. |
{"message":"feedback_required","status":"fail"} | The action is temporarily blocked — a restriction, not a verification gate | Stop the action. Wait hours, not seconds. Reduce volume permanently. |
login_required / 401 | No valid session at all | Different problem — see login required errors. |
429 Too Many Requests | Rate or anti-automation limit | Back off with jitter — see the Instaloader 429 guide. |
HTTP/1.1 400 Bad Request
{"message":"challenge_required","status":"fail","challenge_url":"/challenge/..."}
HTTP/1.1 401 Unauthorized
{"message":"checkpoint_required","status":"fail"}
HTTP/1.1 400 Bad Request
{"message":"feedback_required","status":"fail"}Why it happened
1. The account looks automated
Any session-driven automation produces a request pattern that differs from human usage: tight intervals, no reads before writes, many targets in a short window, datacenter IP ranges, a client fingerprint that does not match the declared client. Instagram’s risk systems score the account and act on the score. This is not a bug in your code — it is the expected outcome of pointing an automation rig at an endpoint that is designed to be driven by a human app.
2. The library and the platform have drifted apart
Challenge handling is where open-source clients break first, because the challenge flow itself is a moving target. Recent examples from public issue trackers:
- instagrapi — “[BUG] challenge_required / Can’t login”, and “ChallengeResolve fails with unknown step_name”. The API contract for the challenge flow changed underneath the resolver.
- Instaloader — “How to proceed on ‘Login: Checkpoint required’?” is the canonical maintainer-thread answer: sign in through the app or browser and clear the checkpoint manually first.
- Instaloader — “400 Bad Request — feedback_required” (open as of September 2026) shows a restriction appearing on an otherwise working setup.
- Osintgram — “ClientError checkpoint_required” on login attempt.
The common thread in these threads is that maintainers do not recommend automated bypass. They recommend manual verification and lower volume — or moving off session-based automation entirely.
3. Shared or reused sessions
If one session is used by several processes, machines, or people — a cron job plus a laptop plus a CI runner — the account suddenly “appears” in multiple places at once. That alone triggers challenges. This is one of the most common self-inflicted causes and the easiest to confirm: look for a second worker before you blame Instagram.
4. It is not your account’s fault at all
Sometimes the target is gone (deleted, renamed, or changed to private), the numeric ID you cached is stale, or you are querying an endpoint your client cannot reach at all. Check for a 404-shaped cause before assuming a block — and note that several Instagram paths return HTTP 200 with "status":"fail", which is why body inspection matters.
Safe recovery sequence
- Stop every process using the account. Kill the scraper, pause the cron job, stop the CI schedule. This is step one because continued traffic converts a temporary checkpoint into a suspension.
- Complete the challenge yourself, manually. Sign in through the official Instagram app (preferably on the device the account normally uses) and follow the on-screen verification. Do this once, patiently. Do not script it, do not loop it, and do not attempt it from a datacenter IP.
- Confirm the account is healthy. Once verified, use the app normally for a while. If
feedback_requiredis what you saw, there is nothing to “complete” — just wait. Restrictions commonly persist for hours to days. - Wait well past the apparent cooldown. A challenge that clears in the app does not mean the account is trusted again immediately. Restarting automation within minutes is a reliable way to trigger a second challenge.
- Change the architecture, not the headers. If the same workload will run again, the account will be flagged again. That is the finding this error is giving you.
- Never share session material. If you opened an issue or posted logs, assume any pasted
sessionid,csrftoken, ords_user_idis compromised. Verify the account, change the password, and sign out other sessions. See Instaloader #1702 for how account losses escalate.
Uninstalling and reinstalling the app, clearing stale sessions, and using a stable network are legitimate steps for your own account. Rotating accounts or proxy pools to keep a scraper alive is not troubleshooting — it is evasion, it breaks terms, and it gets accounts banned in batches. Comply with GramScraper’s Acceptable Use Policy and Instagram’s terms.
Code: detect the block and stop cleanly
The most valuable code change is the one that prevents the retry storm. Several of these responses arrive with a 200 or 400, so status-only checks miss them.
const BLOCK_MESSAGES = new Set([
"challenge_required",
"checkpoint_required",
"feedback_required",
]);
async function readJson(response) {
const body = await response.json().catch(() => ({}));
const message = String(body?.message || "");
if (BLOCK_MESSAGES.has(message)) {
// Account-level block: not retryable, requires human verification.
const error = new Error(`Account blocked upstream: ${message}`);
error.code = message;
error.retryable = false;
throw error;
}
return body;
}Then let the block terminate the job. A crashed job with a clear error is strictly better than a job that quietly hammers a checkpointed account for six hours.
The architectural fix: take the account out of the loop
Every response in this article depends on a signed-in identity. That identity is the thing being flag-scored, restricted, and checkpointed — and it is the thing you do not control. If the data you need is public, the account is an unnecessary dependency in your pipeline: it is the only component that can get your workload blocked, and it is also the only component that can put a real person’s account at risk.
GramScraper removes it. You call a documented REST endpoint with a GramScraper API key. There is no Instagram login, no session to checkpoint, no account to lose. Public profile lookups cost one credit per request.
curl --get \
"https://gramscraper.com/api/instagram/v1/user/by/username" \
--header "Authorization: Bearer $GRAMSCRAPER_API_KEY" \
--data-urlencode "username=instagram"const url = new URL(
"https://gramscraper.com/api/instagram/v1/user/by/username"
);
url.searchParams.set("username", "instagram");
const response = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.GRAMSCRAPER_API_KEY}` },
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const profile = await response.json();
console.log(profile.username, profile.follower_count); // instagram 686724581The response is flat public JSON — username, full_name, biography, follower_count, following_count, media_count, is_private, is_verified, profile_pic_url — plus a _meta object with credits consumed and remaining. All endpoint documentation is in the API docs; a fuller walkthrough of the fields is in the profile data API guide.
What changes in your error handling
Verified statuses on the live endpoint:
401— missing or invalid API key. Non-retryable; fix the credential. Rotating a GramScraper key is a config change, not an account-recovery procedure.402— no credits remaining.404—{"detail":"We're sorry, we couldn't find that.","exc_type":"UserNotFound"}— treat as not-found, not as a block.429— client-side budget exceeded; back off with jitter.5xx— transient; bounded retry is appropriate.
The useful part is what is absent: there is no challenge_required branch, because there is no account to challenge. Your incident surface drops to ordinary HTTP semantics, and a failed job can no longer cost someone their Instagram account.
Related failure modes
Blocks rarely arrive alone. If your client also returns an authentication error, start with Instagram scraping “login required”. If the failure is rate-shaped rather than account-shaped, read the rate limit and “too many requests” guide and the Instaloader 429 and first-request limit guide. For how these signals are compared at the request level, see useragent mismatch and web_profile_info requests. For the wider picture of why session-based scrapers decay, read why DIY scrapers break.
Get 100 free API credits
Remove the account layer entirely — no Instagram session, no checkpoint, no ban risk. Call documented endpoints instead.
Create a free account →