instagrapi

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_required flow, 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

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
More from the team