Using instagrapi-rest from Node, Go, PHP, Java and other non-Python languages
Updated
instagrapi-rest is instagrapi wrapped as a FastAPI service. You run it as a container; you call it over HTTP from any language. This guide walks through the setup, authentication, and a working call snippet for the six languages people most often ask about — Node.js, Go, PHP, Java, Ruby, and C# / .NET — followed by the OpenAPI-driven client-generation path for everything else.
TL;DR
Run instagrapi-rest next to your application as a Docker sidecar bound to a private port. POST to /auth/login to exchange username + password for a sessionid. Pass sessionid on every subsequent call. The endpoints are JSON in, JSON out, and the live OpenAPI / Swagger UI at /docs is the canonical reference. Two pages of YAML (compose), one HTTP client in your host language, and you have Instagram in any stack.
Architecture in one sentence
[ your-app (any language) ] ── HTTP ──▶ [ instagrapi-rest container ] ── HTTPS ──▶ Instagram private API
Your app keeps its native runtime; instagrapi-rest does the Python heavy lifting, including session persistence, retries, and the response shapes that the underlying instagrapi library produces.
Run instagrapi-rest as a Docker sidecar
The shortest path is the prebuilt image:
docker run -d -p 127.0.0.1:8000:8000 --name instagrapi-rest subzeroid/instagrapi-rest
Bind to 127.0.0.1 not 0.0.0.0 unless you genuinely want the service reachable on the network — see the FAQ on exposure.
For real use you almost always want compose, so that your app and instagrapi-rest share a network and instagrapi-rest can persist sessions to a volume:
# docker-compose.yml
services:
instagrapi-rest:
image: subzeroid/instagrapi-rest:latest
expose:
- "8000"
volumes:
- ./sessions:/app/sessions
restart: unless-stopped
your-app:
build: .
environment:
INSTAGRAM_API_URL: http://instagrapi-rest:8000
depends_on:
- instagrapi-rest
Open http://localhost:8000/docs while the container is running for the live Swagger UI. Every endpoint has a “Try it out” button — that is the easiest way to learn the surface before wiring it from code.
Authenticate once, store the session id
instagrapi-rest follows instagrapi’s auth model: you exchange username + password (and optionally a 2FA code) for a sessionid once, then pass that sessionid on every call.
curl -X POST http://localhost:8000/auth/login \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=<USERNAME>&password=<PASSWORD>"
# → {"sessionid": "5..."}
If the account has 2FA enabled, append &verification_code=<6-DIGIT-CODE>. If Instagram returns a challenge_required (which it will, eventually, for most accounts) the response surfaces it the same way the underlying library does — you handle it the same way you would in a Python script. The challenge_required guide covers the recovery flow.
Store the returned sessionid somewhere durable (your app’s database, Redis, an environment variable for single-account toy setups). Re-issuing it on every call works but every fresh login increases Instagram’s risk score against the account.
Node.js / TypeScript
Native fetch is enough; no SDK required.
const API = process.env.INSTAGRAM_API_URL ?? "http://localhost:8000";
const sid = process.env.INSTAGRAM_SESSION_ID!;
async function getProfile(username: string) {
const r = await fetch(`${API}/user/by/username?username=${username}&sessionid=${sid}`);
if (!r.ok) throw new Error(`instagrapi-rest ${r.status}: ${await r.text()}`);
return r.json();
}
const user = await getProfile("instagram");
console.log(user.full_name, user.follower_count);
For typed responses, generate a TypeScript client from instagrapi-rest’s OpenAPI spec — see the OpenAPI section below.
Go
The standard net/http package is fine. A working full client lives in ./golang/client.go in the repo; the minimum is roughly:
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
sid := os.Getenv("INSTAGRAM_SESSION_ID")
resp, err := http.Get("http://localhost:8000/user/by/username?username=instagram&sessionid=" + sid)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var user map[string]any
if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
panic(err)
}
fmt.Println(user["full_name"], user["follower_count"])
}
For typed structs, generate a Go client from /openapi.json.
PHP
Bare PHP works, no Composer dependency strictly needed:
<?php
$sid = getenv("INSTAGRAM_SESSION_ID");
$url = "http://localhost:8000/user/by/username?username=instagram&sessionid=$sid";
$user = json_decode(file_get_contents($url), true);
echo $user["full_name"] . " — " . $user["follower_count"] . "\n";
For posting, multipart uploads, or anything beyond GETs, switch to Guzzle or curl_multi_* — the endpoint shapes are documented in /docs.
Java
java.net.http.HttpClient (Java 11+) is enough for the simple case:
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
var client = HttpClient.newHttpClient();
var sid = System.getenv("INSTAGRAM_SESSION_ID");
var req = HttpRequest.newBuilder(URI.create(
"http://localhost:8000/user/by/username?username=instagram&sessionid=" + sid
)).build();
var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(resp.body());
Use Jackson or JsonReader to deserialize. For typed Java POJOs, generate them from /openapi.json with openapi-generator-cli.
Ruby
Standard library only:
require "net/http"
require "json"
sid = ENV.fetch("INSTAGRAM_SESSION_ID")
url = URI("http://localhost:8000/user/by/username?username=instagram&sessionid=#{sid}")
user = JSON.parse(Net::HTTP.get(url))
puts "#{user['full_name']} — #{user['follower_count']}"
For Rails, wrap this in a service object and inject the base URL from credentials.
C# / .NET
HttpClient — same shape, idiomatic for .NET:
using var client = new HttpClient { BaseAddress = new Uri("http://localhost:8000") };
var sid = Environment.GetEnvironmentVariable("INSTAGRAM_SESSION_ID");
var json = await client.GetStringAsync($"/user/by/username?username=instagram&sessionid={sid}");
Console.WriteLine(json);
For typed C# models, generate from OpenAPI via nswag or openapi-generator-cli -g csharp.
Generating typed clients
The service exposes its OpenAPI spec at /openapi.json. The most robust path for any non-trivial integration is to generate a typed client in your language once, then call those typed methods rather than hand-rolling URLs.
npm install -g @openapitools/openapi-generator-cli
openapi-generator-cli generate \
-g typescript-fetch \
-i http://localhost:8000/openapi.json \
-o ./generated/instagrapi-client \
--skip-validate-spec
Replace typescript-fetch with go, java, csharp, php, ruby, swift5, rust, kotlin, scala-sttp, dart-dio, elixir, crystal, r, or any of the 60+ generators the tool supports. The generated client has typed request and response models, retry hooks, and bearer-auth support if you put a reverse proxy with auth in front of instagrapi-rest.
--skip-validate-spec is sometimes needed because instagrapi-rest’s spec uses Pydantic-flavored fields the generator’s strict validator does not always like. The generated code works regardless.
What you still own
instagrapi-rest solves the language barrier. It does not solve the operational barrier. The realities you take on by self-hosting:
- Instagram accounts — accounts that perform private-API traffic at scale get flagged. You will need accounts you control, an account-warming workflow, and a way to swap them out when one dies.
- Proxies — datacenter IPs are flagged on first contact. Residential or mobile proxies are required for serious use, and they need to be rotated. Proxy budget is real money.
- Session storage and rotation — losing a session means re-logging in, which means walking the
challenge_requiredflow, which means somebody (or something) reading SMS or email codes. - Retry + backoff — Instagram returns rate-limit errors and transient failures regularly. Your code on the calling side has to handle that gracefully.
These are the same costs anyone running instagrapi in Python pays. Putting an HTTP boundary in front does not move the cost.
Wrapping up
If your stack is not Python and you’ve been writing your own Instagram scraper because the libraries in your language died years ago, instagrapi-rest is the lower-effort path: one container, one HTTP client in your host language, and you ride the same release cadence the Python ecosystem rides. The trade-off is that every operational concern that comes with Instagram automation — accounts, proxies, sessions, retries — is still yours. When the cost of those concerns crosses the cost of a managed service, the HikerAPI path is the natural next step; the call sites you wrote against instagrapi-rest map almost one-to-one.
Related guides
- Instagram API libraries by language: what's actually maintained in 2026 Instagram private-API libraries across Python, Node, PHP, Java, Go, C#, Swift, Ruby. What's alive, what's archived, what to use instead.
- Instagram Private API in Python: a practical guide with instagrapi How to use Instagram's private (mobile) API from Python with instagrapi. Login, session reuse, fetching media, posting, and avoiding common errors.
- instagrapi vs aiograpi: sync or async for the Instagram private API? instagrapi vs aiograpi compared: same maintainer team, same API shape, sync vs asyncio. When async actually pays off and how to migrate.
- Projects using instagrapi: 25 standout tools from 1,300+ public repos Top open-source projects built on instagrapi: CLI clients, OSINT tools, AI agents, cross-platform bridges. Sorted by GitHub stars.
Frequently asked
Why not just use my language's native Instagram library?
Because most of them have stopped tracking Instagram. As covered in the [language-by-language survey](/guides/instagram-api-libraries-by-language), the canonical Node, Go, Swift and Ruby Instagram libraries are either archived outright or have not seen a meaningful release in over a year. `instagrapi-rest` lets you ride `instagrapi`'s active release cadence in any language.
Is instagrapi-rest production-ready?
The HTTP wrapper is thin — it forwards to `instagrapi`, which is the actively-maintained Python library used by 1,300+ public dependent repositories. The pieces that decide production-readiness are the same as for `instagrapi` itself: account quality, proxy strategy, and session storage. None of that is solved for you by adding the HTTP boundary.
What are the latency implications of the HTTP hop?
Sub-millisecond when `instagrapi-rest` runs on localhost or the same Docker network. The dominant latency by a large factor is Instagram's own response time (typically 300-1500 ms per call). The HTTP hop you're adding is rounding error against that.
Should I expose instagrapi-rest publicly?
No. The default has no authentication on the endpoints; anyone who can reach the port can issue calls under whatever sessions you have stored. Bind to `127.0.0.1`, a private network, or a Docker compose internal network. Put a reverse proxy with auth in front if you need cross-host access.
How do I scale beyond a single instance?
Run multiple containers, route by Instagram account so a session always lands on the same instance, and persist session settings to a shared store (S3, Postgres, Redis). Beyond a few accounts and steady traffic, the operational complexity climbs fast — at that point the managed [HikerAPI](https://hikerapi.com/p/hsazcgym) path is usually the simpler answer.
Skip the infra?
Managed Instagram API — same endpoints, sessions and proxies handled.
Try HikerAPI → Full comparison