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.
Instagram API / Code samples / curl
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
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 jqQuickstart
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
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
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 ;;
esacGotchas
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.
-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.
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.
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
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.
A new account starts with 100 free credits. Failures before resource use are free; requests that use scraping resources are billed.
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.
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
Every endpoint these samples call is documented at /docs/instagram.