Instagram API / Code samples / curl

Instagram API With curl — One Header, Plain REST, No SDK

The fastest way to confirm the API does what you need before writing any code, and the form to paste into a bug report when it does not.

First call

Calling the Instagram API from curl.

Copy this, set your key, and you have a working integration. Everything below is the same request with more of the edge cases handled.

Install

brew install jq        # macOS. Debian and Ubuntu: apt-get install jq

Quickstart

export INSCRAPE_KEY="sk_your_key_here"

curl -sS "https://api.socialscrape.dev/v1/instagram/profile?handle=natgeo" \
  -H "x-api-key: $INSCRAPE_KEY" | jq

# {
#   "success": true,
#   "credits_charged": 1,
#   "credits_remaining": 99,
#   "processing_time_ms": 1842,
#   "requested_at": "2026-09-13T14:32:18Z",
#   "query": { "username": "natgeo" },
#   "data": { "handle": "natgeo", "follower_count": 279000000, ... }
# }

# Just the fields you care about:
curl -sS "https://api.socialscrape.dev/v1/instagram/profile?handle=natgeo" \
  -H "x-api-key: $INSCRAPE_KEY" \
  | jq '{handle: .data.handle, status: (.status // "found"), followers: .data.profile.follower_count, charged: .credits_charged}'

Pagination

curl cursor pagination for Instagram API lists.

Every list endpoint returns a cursor. Stop when it is absent, not after a fixed number of pages — the page size is not a contract.

curl — cursor loop

#!/usr/bin/env bash
set -euo pipefail

handle="natgeo"
cursor=""
spent=0
: > posts.ndjson

for page in $(seq 1 10); do
  url="https://api.socialscrape.dev/v1/instagram/posts?handle=$handle&limit=50"

  if [ -n "$cursor" ]; then
    # The cursor is opaque and may contain characters the URL cannot carry raw.
    url="$url&cursor=$(jq -rn --arg c "$cursor" '$c | @uri')"
  fi

  body=$(curl -sS -H "x-api-key: $INSCRAPE_KEY" "$url")

  if [ "$(jq -r '.success' <<< "$body")" != "true" ]; then
    jq -r '"\(.error.code): \(.error.message)"' <<< "$body" >&2
    exit 1
  fi

  jq -c '.data.posts[]' <<< "$body" >> posts.ndjson
  spent=$(( spent + $(jq -r '.credits_charged' <<< "$body") ))

  cursor=$(jq -r '.data.next_cursor // empty' <<< "$body")
  [ -n "$cursor" ] || break
done

echo "$(wc -l < posts.ndjson) posts across $spent credits"

Errors

curl error handling for the Instagram API.

Only HTTP 200 is charged, so a retry after a 500 costs you nothing except time. A retry after a 400 costs you nothing and fixes nothing.

curl — error handling

#!/usr/bin/env bash
set -uo pipefail

# -w appends the status code on its own line, so one call gives you both
# the body and the status without a second request.
response=$(curl -sS -w $'\n%{http_code}' \
  -H "x-api-key: $INSCRAPE_KEY" \
  "https://api.socialscrape.dev/v1/instagram/profile?handle=natgeo")

status=$(tail -n1 <<< "$response")
body=$(sed '$d' <<< "$response")

case "$status" in
  200)
    jq '{status: (.status // "found"), followers: .data.profile.follower_count, charged: .credits_charged, remaining: .credits_remaining}' <<< "$body"
    ;;
  400) echo "bad parameters, 0 credits charged: $(jq -r '.error.message' <<< "$body")" >&2 ;;
  401) echo "invalid or revoked key, 0 credits charged" >&2 ;;
  402) echo "insufficient credits, 0 credits charged" >&2 ;;
  404) echo "not found, 0 credits charged" >&2 ;;
  *)   echo "retryable $status $(jq -r '.error.code' <<< "$body"), 0 credits charged" >&2 ;;
esac

Gotchas

Things that only bite in curl.

Quote the URL

An unquoted & sends curl to the background and silently drops every parameter after the first. Every multi-parameter example on this site is quoted for that reason.

-sS, not -s

-s alone hides transport errors as well as the progress meter, so a DNS failure looks like an empty response. -sS keeps the errors and drops the noise.

Keep the key out of shell history

export it from a file that is not in version control, or read it from a password manager. A key pasted inline lives in .bash_history and carries a credit balance.

Keep response metadata

requested_at, processing_time_ms and query make a pasted terminal response useful later when you need to compare when and why a lookup ran.

FAQ

Instagram API in curl FAQ.

Is there a Postman collection?

Import https://socialscrape.dev/openapi.json directly. Postman generates a request per endpoint from it, and the same file works in Insomnia, Bruno and any OpenAPI client generator. /docs/postman covers the import step and where to keep the key once it is in.

How do I test without spending credits?

A new account starts with 100 free credits. Failures before resource use are free; requests that use scraping resources are billed.

Why does my second page return 400?

The cursor was not URL-encoded. Encode it, as the pagination script above does with jq's @uri, or use a client library that builds the query string for you.

Can I use this to like, follow or comment?

No. Every endpoint is a GET and nothing writes back to Instagram. The API reads public data and returns JSON; it cannot act on an account.

Other languages

Instagram API examples beyond curl.

Every endpoint these samples call is documented at /docs/instagram.