herascrape
Getting started

Quickstart

Get a key, make a call, page through a list.

1. Get a key

Create one in the dashboard. The plaintext is shown once at creation and never again — we store a hash, so a lost key means a new key.

New accounts start with free balance, so you can run through this page before paying anything.

2. Make a call

curl https://api.herascrape.com/users/username/instagram \
  -H "X-Api-Key: $HERASCRAPE_KEY"

You get Instagram's profile object back, in full. Take pk from it — that numeric id addresses every sub-resource:

curl https://api.herascrape.com/users/25025320/followers \
  -H "X-Api-Key: $HERASCRAPE_KEY"

Looking up by username costs two Instagram requests (a resolve, then the profile), so it is billed at double. Cache the numeric id and the second call onward is single-price. See Pricing.

3. Page through a list

One call returns one Instagram page. Loop on next_cursor until has_more goes false:

import httpx

BASE = "https://api.herascrape.com"
headers = {"X-Api-Key": KEY}

def paginate(path: str, **params):
    cursor = None
    with httpx.Client(base_url=BASE, headers=headers, timeout=60) as http:
        while True:
            if cursor:
                params["cursor"] = cursor
            page = http.get(path, params=params).raise_for_status().json()
            yield from page["items"]
            if not page["has_more"]:
                return
            cursor = page["next_cursor"]

for follower in paginate("/users/25025320/followers"):
    print(follower["username"])
const BASE = 'https://api.herascrape.com'

async function* paginate(path, params = {}) {
  let cursor = null
  for (;;) {
    const query = new URLSearchParams({ ...params, ...(cursor ? { cursor } : {}) })
    const response = await fetch(`${BASE}${path}?${query}`, {
      headers: { 'X-Api-Key': process.env.HERASCRAPE_KEY },
    })
    if (!response.ok) throw new Error(`${response.status} ${await response.text()}`)
    const page = await response.json()
    yield* page.items
    if (!page.has_more) return
    cursor = page.next_cursor
  }
}

Treat cursor as opaque. It encodes whichever token the underlying Instagram endpoint uses, and that differs per endpoint — parsing or constructing one will break.

4. Handle the errors that matter

Three you should branch on, and one you should retry:

response = http.get(f"/users/{user_id}")
if response.status_code == 404:
    ...  # gone, deleted, or never existed
elif response.status_code == 403:
    ...  # private account
elif response.status_code == 429:
    ...  # your rate limit; back off
elif response.status_code in (502, 503):
    ...  # transient on our side; retry

Non-2xx responses are never billed. Full list in Errors.

Where to go next

On this page