herascrape
Getting started

Errors

The error envelope, every code, and which ones are worth retrying.

Failures return a consistent JSON envelope. Branch on error_code, not on detail — the wording can change, the code will not.

{
  "status": "client_error",
  "error_code": "profile_not_found",
  "detail": "Profile not found or unavailable"
}

status is derived from the HTTP status class: client_error for 4xx, server_error for 5xx.

Only errors carry this envelope. A successful call returns the object, or the list envelope, directly — so branch on the HTTP status code first.

Codes

400 · invalid_cursor

The cursor you sent is not a cursor we issued. Cursors are opaque and must be passed back byte-for-byte from a previous next_cursor. Not retryable — restart the list from the first page.

400 · invalid_parameter

A parameter is present but unusable: a malformed media id, a pk/code pair where exactly one was required, an unknown value. The detail names the problem. Not retryable.

401 · unauthorized

Missing or invalid X-Api-Key. Also returned briefly if key verification is offline — the API fails closed rather than serving unauthenticated requests. Check the key, then retry once.

403 · private_account

The account exists but its content is private. Nothing to retry: this is Instagram's answer, and no amount of retrying changes it.

404 · profile_not_found

No such username or user id — deleted, banned, renamed, or never existed. Not retryable.

404 · not_found

The requested object is gone: a deleted post, an expired story, a hashtag or location with no such id. Not retryable.

422 · unprocessable_entity

Request validation failed before the call ran — a required query parameter is missing, or a value has the wrong type. detail lists the offending fields. Not retryable.

429 · too_many_requests

You exceeded your per-key rate limit of 10 requests per second. detail carries the retry delay. Back off and retry.

502 · upstream_error

The call reached Instagram and could not be completed. Retry — it usually succeeds on the next attempt.

503 · upstream_unavailable

No capacity right now, or a transient failure that needs backing off. Retryable after a short wait.

Retry policy

CodeRetry?How
upstream_errorYesImmediately, up to ~3 times.
upstream_unavailableYesBack off — 1s, 2s, 4s.
too_many_requestsYesWait the delay in detail.
unauthorizedOnceAfter checking the key.
everything elseNoFix the request.
import time
import httpx

RETRYABLE = {502, 503}

def call(http: httpx.Client, path: str, **params) -> dict:
    for attempt in range(3):
        response = http.get(path, params=params)
        if response.status_code not in RETRYABLE:
            response.raise_for_status()
            return response.json()
        time.sleep(2**attempt)
    response.raise_for_status()
    return response.json()

Failed calls are free

Only 2xx responses are billed, so a retry loop cannot drain your balance. See Pricing.

Reporting a problem

Every response carries x-request-id. Include it — with the endpoint and roughly when it happened — and we can find the exact call in the logs.

On this page