Skip to content

Your first program ​

In about 15 minutes you install the Simulator, log an account in, save its session, and like a post from the saved session. Every value in the code is explained below it.

At the end you have an account logged in once, its session saved in session.json, and a second run that likes the first post of the home feed without logging in again.

Before you start ​

  • The Simulator installed (Get access and install), with IGPAPI_LICENSE_TOKEN set.
  • A test account first, not the account you care about (Account safety and pacing).
  • The account's name and password in the environment: IG_USERNAME and IG_PASSWORD. If the account uses an authenticator app, you also need its setup key (Log in); this page uses an account without a second step.
bash
export IG_USERNAME='your_test_account'
export IG_PASSWORD='its password'

Step 1: log in and save the session ​

Save this as login.ts.

ts
import { AndroidIgpapi, FeedTimelineScreen } from '@igpapi/android';
import { Effect } from 'effect';
import { writeFile } from 'node:fs/promises';

const username = process.env['IG_USERNAME'] ?? '';
const password = process.env['IG_PASSWORD'] ?? '';

// One call is one app process: a fresh install, the login, and the checkpoint of the feed it reaches.
const checkpoint = await AndroidIgpapi.install({
  seed: username,
  main: home =>
    Effect.gen(function* () {
      const next = yield* home.logIn({ username, password });
      // An account with a second step shows another screen: see "Log in".
      if (!FeedTimelineScreen.is(next)) return undefined;
      return yield* next.checkpoint();
    }),
});

if (checkpoint === undefined) {
  console.log('Instagram asked for a second step: see "Log in".');
} else {
  await writeFile('session.json', JSON.stringify(checkpoint));
  console.log('The session is saved in session.json.');
}

What each name is:

  • AndroidIgpapi.install starts the Instagram Android app as a fresh install on a virtual phone, runs your program, and closes the app when the program ends. The call resolves with what main returns.
  • seed names the virtual phone. Use a stable string for an account (its username is a good one): the same account then always gets the same phone. Instagram recognises a phone it has seen before.
  • main is your program. It receives the first screen the app shows, which for a fresh install is the login screen, and it is an Effect: Effect.gen(function* () { … }) with yield* in front of every step.
  • home is the handle of that screen. home.logIn({ username, password }) types the username and password and taps Log in. It continues into the app's start-up by itself and answers the next screen: the home feed for an ordinary account.
  • FeedTimelineScreen.is(next) asks whether that screen is the home feed. An account with a second step shows another screen (see Log in).
  • next.checkpoint() returns the session and the screens you have open, as an object. It is a secret: it holds the account's credentials. This program writes it to a file; a real project stores it as a secret.

Run it:

bash
npx tsx login.ts

It prints The session is saved in session.json. and writes the file.

Step 2: start from the session and like a post ​

Save this as like.ts.

ts
import { AndroidIgpapi, FeedTimelinePostItem, FeedTimelineScreen } from '@igpapi/android';
import { Effect } from 'effect';
import { readFile } from 'node:fs/promises';

// The checkpoint the first program saved: an object, parsed from the file.
const saved = JSON.parse(await readFile('session.json', 'utf8'));

// A stored session is a new app process: `launch` starts the app again for the saved account.
const liked = await AndroidIgpapi.launch({
  checkpoint: saved,
  screen: FeedTimelineScreen,
  main: feed =>
    Effect.gen(function* () {
      // The post in front, or the first post after a few swipes (an ad or a Reel can come first).
      let item = feed.current;
      for (let swipes = 0; item !== undefined && !FeedTimelinePostItem.is(item) && swipes < 10; swipes += 1) {
        item = yield* feed.swipeToNext();
      }
      if (item === undefined || !FeedTimelinePostItem.is(item)) return undefined;

      const post = yield* item.open();
      yield* post.like();
      return post.data.mediaId;
    }),
});

console.log(liked === undefined ? 'No post came in front.' : `Liked the post ${liked}.`);

What each name is:

  • JSON.parse(…) turns the file back into the object the entry takes. An entry never takes JSON text.
  • AndroidIgpapi.launch starts the app again for a saved account, the way a person opens the app after it was closed. It does not log in: the saved session is used, and the app sends what it sends when it starts.
  • screen: FeedTimelineScreen names the screen you expect. main then receives exactly that screen's handle, with no narrowing.
  • feed.current is the post, Reel or ad in front; feed.swipeToNext() brings the next one in front, as a swipe does.
  • FeedTimelinePostItem.is(item) keeps only posts. item.open() opens the post screen, post.like() likes it, and post.data holds what the screen shows.

Run it:

bash
npx tsx like.ts

It prints Liked the post <id>. The post is liked on the account.

If something goes wrong ​

What you seeWhat it isWhat to do
The call rejects with IgLoginBadPasswordErrorThe password is wrong.Fix IG_PASSWORD. Retrying the same password only adds failed attempts.
Instagram asked for a second stepThe account has two-factor authentication or Instagram wants another approval.Log in.
The call rejects with an UnexpectedScreenErrorInstagram showed a screen your program does not describe, usually a challenge.Challenges: answer them once with otherwise.
Please, provide license token as …IGPAPI_LICENSE_TOKEN is not set.Get access and install.

Every error has a page: Errors.

Next ​

The pages of this site are the README files of the repository. The API reference is generated from the source.