NativePort
← How-to

How to Scrape Public Instagram Data with Apify on NativePort

Pull a public Instagram profile's data through NativePort's Apify proxy — exact actor, exact input, one call, no login and no private content.

Apify is a marketplace of 7,000+ pre-built scrapers (“Actors”), each with its own input and output schema, run on Apify’s infrastructure rather than yours. NativePort proxies one narrow, deliberate slice of Apify’s API: running an Actor synchronously and getting its output back inline, in the same response — no separate job to poll, no dataset to fetch afterward. This guide uses that route for one purpose: pulling a public Instagram profile’s public data through an official Apify Actor. Nothing here touches a private account, requires a login, or works around Instagram’s own access controls — the Actor’s input schema has no field for credentials or session cookies, only a username.

What you’ll need

  • A NativePort API key. Sign up to get a key and $5 in credits.
  • Python 3, standard library only — urllib.request, no pip install.
  • Your key exported as an environment variable, never hardcoded:
export NATIVEPORT_API_KEY="np_..."

The route

NativePort admits exactly two shapes of Apify call, both POST, both synchronous: run an Actor (or a saved Actor Task) and get its dataset back directly in the response.

POST https://api.nativeport.ai/apify/v2/acts/{actorId}/run-sync-get-dataset-items
POST https://api.nativeport.ai/apify/v2/acts/{actorId}/run-sync
POST https://api.nativeport.ai/apify/v2/actor-tasks/{taskId}/run-sync-get-dataset-items
POST https://api.nativeport.ai/apify/v2/actor-tasks/{taskId}/run-sync

Note the path segment: it’s /v2/acts/, not /v2/actors/ — Apify’s newer docs lead with /v2/actors/ as the current spelling, but that path isn’t one NativePort admits. Use /v2/acts/ exactly as shown above; anything else 404s at the gateway before it reaches Apify. {actorId} is the tilde-separated publisher and Actor name — apify~instagram-profile-scraper for the Actor this guide uses — or Apify’s own opaque Actor ID. Apify’s account-management surface (usage, proxy password, dataset listing outside an inline run, webhooks, schedules) isn’t exposed here at all; the run’s own dataset, returned inline, is the only way to get data out.

Pull one public profile

apify/instagram-profile-scraper is Apify’s own official Actor for exactly this: given one or more Instagram usernames, it returns each account’s public profile data — no posts, no comments, the narrowest surface the Actor offers.

import json
import os
import urllib.request

GATEWAY = "https://api.nativeport.ai"
API_KEY = os.environ["NATIVEPORT_API_KEY"]
ACTOR = "apify~instagram-profile-scraper"


def run_actor(actor_id, actor_input, timeout_secs=60):
    body = json.dumps(actor_input).encode()
    url = f"{GATEWAY}/apify/v2/acts/{actor_id}/run-sync-get-dataset-items?timeout={timeout_secs}"
    req = urllib.request.Request(url, data=body, method="POST")
    req.add_header("Authorization", f"Bearer {API_KEY}")
    req.add_header("Content-Type", "application/json")
    with urllib.request.urlopen(req, timeout=timeout_secs + 10) as resp:
        return json.loads(resp.read())


items = run_actor(ACTOR, {"usernames": ["natgeo"]})
profile = items[0]
print(profile["username"], profile["followersCount"], profile["private"])

usernames is the only required field — an array, even for one account. The Actor also takes an optional includeAboutSection boolean (default false), a paid add-on on Apify’s side that adds join date and verification info; leave it off for the minimal, cheapest call. The timeout query parameter caps how long the run is allowed to take before NativePort gives up waiting — keep it well under the hard 300-second ceiling this synchronous endpoint enforces (see Errors, below); 60 seconds is generous for a single-username profile lookup.

The response is a JSON array — one item per requested username — each item shaped like Apify’s own documented dataset schema for this Actor:

[
  {
    "id": "528817151",
    "username": "natgeo",
    "url": "https://www.instagram.com/natgeo/",
    "fullName": "National Geographic",
    "biography": "...",
    "followersCount": 280000000,
    "followsCount": 150,
    "postsCount": 26000,
    "private": false,
    "verified": true,
    "profilePicUrl": "https://...",
    "latestPosts": [ { "...": "up to 12 most recent posts, each with its own stats" } ]
  }
]

(Values illustrative — the actual counts depend on the account’s state when you run it.) A nonexistent or now-private username comes back as a dataset item carrying error and errorDescription fields instead of profile data, rather than failing the whole call — check for those on each item before trusting the rest of its fields.

A handful of public posts instead of a profile

For post-level data rather than profile summary, swap in apify/instagram-scraper with directUrls pointed at the profile and a small resultsLimit:

items = run_actor("apify~instagram-scraper", {
    "directUrls": ["https://www.instagram.com/natgeo/"],
    "resultsType": "posts",
    "resultsLimit": 5,
})

Keep resultsLimit small (single digits) for anything run through this synchronous route — this Actor is built for large async crawls by default (its own configured timeout is measured in days, not seconds), and the 300-second sync ceiling will cut off a large request with a 408 before Apify finishes. Each returned item is one post: shortCode, caption, hashtags, likesCount (-1 if the creator hid it — not an error), commentsCount, timestamp, ownerUsername, and more.

Public data only — by design, not by promise

Both Actors used here only reach what Instagram already serves without a login — there is no username/password or session-cookie field anywhere in either Actor’s input schema, so there’s no way to point this route at a private account even if you wanted to. From Apify’s own published guidance on these Actors: they “do not extract any private user data” and only surface “what the user has chosen to share publicly.” That data can still be personal data under regulations like GDPR even when it’s public, so scrape with a legitimate reason, and see Apify’s own legality-of-scraping guidance if you’re unsure whether yours qualifies. Neither Instagram nor Apify publishes a fixed numeric rate limit for these Actors — keep request volume and resultsLimit deliberately small, run scrapes on your own schedule rather than in a tight loop, and treat a 429 (below) as a signal to slow down, not retry immediately.

Errors you’ll actually hit

  • 402 Payment Required (from NativePort): the NativePort balance is at $0. Distinct from Apify’s own 402 below — this one is your balance, checked before the request ever leaves the gateway.
  • 401 Unauthorized: the Authorization: Bearer <NATIVEPORT_API_KEY> header is missing, malformed, or doesn’t resolve to an account.
  • 403 Forbidden: the key is valid but the account isn’t active.
  • 404 on any path outside the four run-sync shapes listed above, including /apify/v2/actors/... (the newer path spelling Apify’s own docs now lead with) or any GET/PUT/DELETE call: not a rate limit, just not a mounted route.
  • 400 invalid-input (from Apify): the JSON body doesn’t match the Actor’s input schema — most often a missing or malformed usernames array.
  • 404 record-not-found (from Apify): {actorId} doesn’t resolve to a real Actor — check the tilde-separated spelling.
  • 408 run-timeout-exceeded (from Apify): the run didn’t finish within the synchronous endpoint’s hard 300-second ceiling. The run itself may still be executing on Apify’s side even though you got a timeout back — for anything that risks running long, use a smaller resultsLimit rather than assuming a retry will land differently.
  • 429 rate-limit-exceeded (from Apify): back off before retrying, rather than looping immediately.
  • A 200/201 with an error field inside a dataset item: that one username failed (private, deleted, or never existed) without failing the whole batch — check each item, don’t assume array length means success count.

Security

Treat NATIVEPORT_API_KEY like any other credential: environment variable or secret store, never a literal string in source, never logged. Apify’s own account token never reaches your process — the gateway injects it server-side into every admitted call, and a client-supplied token query parameter (Apify’s alternate auth channel) is stripped rather than honored, so there’s no way to smuggle a different Apify credential through this route.

What this costs

Apify’s own compute-unit billing has no clean per-request signal, so NativePort meters this route at a flat rate per accepted call, independent of how large the returned dataset is — current pricing is on the Apify provider page. That’s separate from whatever Apify itself charges the Actor’s publisher per result (the profile-scraper and post-scraper Actors used here are both pay-per-result on Apify’s own platform) — NativePort’s flat per-call rate is what debits your balance; it isn’t a pass-through of the Actor’s own per-result price. A zero balance answers every request with 402 instead of degrading. Adding credit carries a flat 5.5% fee on top-ups between $10 and $5,000 — never on the calls themselves. Full breakdown: pricing.

Where to go next