npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nodejs-insta-private-api-mqt

v1.5.14

Published

Complete Instagram MQTT protocol with full-featured REALTIME and REST API — all in one project.

Readme

Full featured Java and nodejs Instagram api private with Mqtt full suport and api rest

Dear users The repository on github is currently unavailable will be available soon

If you This Project you can help with any ammount donation at USDC 0x8AD64F47a715eC24DeF193FBb9aC64d4E857f0f3

nodejs-insta-private-api-mqt

This project implements a complete and production-ready MQTT protocol client for Instagram's real-time messaging infrastructure. Instagram uses MQTT natively for direct messages, notifications, and real-time presence updates. This library replicates that exact implementation, allowing developers to build high-performance bots and automation tools that communicate with Instagram's backend using the same protocol the official app uses.

By leveraging MQTT instead of Instagram's REST API, this library achieves sub-500ms message latency, bidirectional real-time communication, and native support for notifications, presence tracking, and thread management. The implementation is reverse-engineered from Instagram's mobile app protocol and tested extensively for reliability and compatibility.

Features (v5.66.0 - Complete REST API + MQTT)

REST API (32 Repositories)

  • Account - Login, 2FA (TOTP + SMS), challenge resolver, edit profile, change password, privacy settings
  • User - Info, search, follow/unfollow, block/unblock, mute, get posts/reels/stories with pagination
  • Media - Like, comment (pin/unpin/bulk delete/reply), save, archive, download, PK/shortcode conversion
  • Reels/Clips - Upload, configure, discover reels, download, music info
  • Stories - Upload photo/video stories, react, mark seen, highlights management
  • Highlights - Create, edit, delete highlights, add/remove stories, update cover
  • Direct Messages - Send text/photo/video/link/media, inbox, pending inbox, group threads
  • Friendship - Follow, block, restrict, close friends, favorites, pending requests
  • Search - Users, hashtags, places, music, recent/suggested searches
  • Explore - Topical explore, report, mark seen
  • Feed - Timeline, hashtag/location feeds, saved/liked, carousel upload
  • Upload - Photo/video upload with configure to feed, story, or clips
  • Insights - Account, media, reel, story analytics (business/creator accounts)
  • Notes - Create, delete, view Instagram Notes
  • Notifications - Per-type notification settings (likes, comments, follows, etc.)
  • TOTP - 2FA setup with authenticator app, SMS 2FA, backup codes
  • Challenge - Auto-resolve security checkpoints, verify methods
  • Signup - Account creation, email/phone verification, username availability
  • Music/Tracks - Search, get info, download audio tracks
  • Fundraiser - Create, donate, get fundraiser info
  • Multiple Accounts - Account family, switch accounts
  • Captcha - reCAPTCHA / hCaptcha handling
  • Share - Decode QR/NFC share codes, parse share URLs
  • Bloks - Low-level Instagram Bloks engine actions

MQTT Real-Time

  • Real-time MQTT messaging - Receive and send DMs with <500ms latency
  • FBNS Push Notifications - Follows, likes, comments, story mentions, live broadcasts
  • 33 Preset Devices - 21 iOS + 12 Android device emulation
  • View-Once Media - Download disappearing photos/videos before they expire
  • Raven (View-Once) Sending - Send view-once and replayable photos/videos via REST
  • sendPhoto() / sendVideo() - Upload and send media directly via MQTT
  • Session persistence - Multi-file auth state for seamless reconnects
  • Automatic reconnection - Smart error classification with type-specific backoff
  • Session health monitoring - Auto-relogin, uptime tracking
  • Persistent logging - File-based logging with rotation
  • Message ordering - Per-thread message queuing
  • Pure JavaScript - No compilation required, works in Node.js 18+

Scope: DM-Focused Implementation

This library is optimized for Direct Messages and implements the core MQTT protocols used by Instagram for:

  • Real-time message delivery and reception
  • Presence status tracking
  • Typing indicators
  • Notifications for follows, mentions, and calls
  • Group thread management

For full MQTT coverage analysis, see MQTT_COVERAGE_ANALYSIS.md

Installation

npm install nodejs-insta-private-api-mqt

Requires Node.js 18 or higher.


NEW: Custom Device Emulation (v5.60.7)

Default Device: Samsung Galaxy S25 Ultra (Android 15) - used automatically if you don't set a custom device.

This feature allows you to choose which phone model Instagram sees when your bot connects. Instead of using a default device, you can emulate any Android phone like Samsung Galaxy S25 Ultra, Huawei P60 Pro, Google Pixel 9, and more.

Why Use Custom Device Emulation?

  • Avoid detection - Use realistic, modern device fingerprints
  • Match your target audience - Emulate devices popular in specific regions
  • Testing - Test how Instagram behaves with different devices
  • Reduce bans - Modern devices are less likely to trigger security checks

Quick Start: Use a Preset Device

const { IgApiClient } = require('nodejs-insta-private-api');

const ig = new IgApiClient();

// Set device BEFORE login
ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');

// Now login - Instagram will see a Samsung S25 Ultra
await ig.login({
  username: 'your_username',
  password: 'your_password'
});

console.log('Logged in with device:', ig.state.deviceString);

Available Preset Devices

| Device Name | Manufacturer | Android Version | |-------------|--------------|-----------------| | Samsung Galaxy S25 Ultra | Samsung | Android 15 | | Samsung Galaxy S24 Ultra | Samsung | Android 14 | | Samsung Galaxy S23 Ultra | Samsung | Android 14 | | Samsung Galaxy Z Fold 5 | Samsung | Android 14 | | Huawei P60 Pro | Huawei | Android 12 | | Huawei Mate 60 Pro | Huawei | Android 12 | | Google Pixel 8 Pro | Google | Android 14 | | Google Pixel 9 Pro | Google | Android 15 | | OnePlus 12 | OnePlus | Android 14 | | Xiaomi 14 Ultra | Xiaomi | Android 14 | | Xiaomi Redmi Note 13 Pro | Xiaomi | Android 14 | | OPPO Find X7 Ultra | OPPO | Android 14 |

Set a Fully Custom Device

For complete control, use setCustomDevice() with your own configuration:

const ig = new IgApiClient();

ig.state.setCustomDevice({
  manufacturer: 'samsung',
  model: 'SM-S928B',
  device: 'e3q',
  androidVersion: '15',
  androidApiLevel: 35,
  resolution: '1440x3120',
  dpi: '505dpi',
  chipset: 'qcom',
  build: 'UP1A.231005.007'
});

Quick Start: Instant MQTT Boot

const { IgApiClient, RealtimeClient, useMultiFileAuthState } = require('nodejs-insta-private-api-mqt');

async function startBot() {
  const ig = new IgApiClient();
  const auth = await useMultiFileAuthState('./auth_info_ig');
  
  ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');
  
  const realtime = new RealtimeClient(ig);

  realtime.on('connected', () => {
    console.log('Bot is online and MQTT is connected!');
  });

  realtime.on('message_live', async (msg) => {
    console.log(`[${msg.username}]: ${msg.text}`);
    
    if (msg.text.toLowerCase() === 'ping') {
      await realtime.directCommands.sendText({
        threadId: msg.thread_id,
        text: 'pong!'
      });
    }
  });

  if (!auth.hasSession()) {
    await ig.login({
      username: 'your_username',
      password: 'your_password'
    });
    
    await auth.saveCreds(ig);
    await realtime.startRealTimeListener();
    await auth.saveMqttSession(realtime);
  }
}

startBot().catch(console.error);

EnhancedDirectCommands - Complete MQTT Methods Reference

All MQTT direct messaging functionality is available through realtime.directCommands. These methods use proper payload formatting that matches the instagram_mqtt library format.

Basic Messaging

Send Text Message

await realtime.directCommands.sendText({
  threadId: '340282366841710300949128114477782749726',
  text: 'Hello from MQTT!'
});

Send Text (Alternative Signature)

await realtime.directCommands.sendTextViaRealtime(threadId, 'Hello!');

Reply to Message (Quote Reply)

await realtime.directCommands.replyToMessage(threadId, messageId, 'This is my reply');

Edit Message

await realtime.directCommands.editMessage(threadId, itemId, 'Updated text here');

Delete Message

await realtime.directCommands.deleteMessage(threadId, itemId);

Content Sharing

Send Hashtag

await realtime.directCommands.sendHashtag({
  threadId: threadId,
  hashtag: 'photography',
  text: 'Check this out'
});

Send Like (Heart)

await realtime.directCommands.sendLike({
  threadId: threadId
});

Send Location

await realtime.directCommands.sendLocation({
  threadId: threadId,
  locationId: '123456789',
  text: 'Meet me here'
});

Send Media (Share Post)

await realtime.directCommands.sendMedia({
  threadId: threadId,
  mediaId: 'media_id_here',
  text: 'Check this post'
});

Send Profile

await realtime.directCommands.sendProfile({
  threadId: threadId,
  userId: '12345678',
  text: 'Follow this account'
});

Send User Story

await realtime.directCommands.sendUserStory({
  threadId: threadId,
  storyId: 'story_id_here',
  text: 'Did you see this?'
});

Send Link

await realtime.directCommands.sendLink({
  threadId: threadId,
  link: 'https://example.com',
  text: 'Check this link'
});

Send Animated Media (GIF/Sticker)

await realtime.directCommands.sendAnimatedMedia({
  threadId: threadId,
  id: 'giphy_id_here',
  isSticker: false
});

Send Voice Message (after upload)

await realtime.directCommands.sendVoice({
  threadId: threadId,
  uploadId: 'your_upload_id',
  waveform: [0.1, 0.5, 0.8, 0.3],
  waveformSamplingFrequencyHz: 10
});

Reactions

Send Reaction

await realtime.directCommands.sendReaction({
  threadId: threadId,
  itemId: messageId,
  reactionType: 'like'
});

Send Emoji Reaction

await realtime.directCommands.sendReaction({
  threadId: threadId,
  itemId: messageId,
  reactionType: 'emoji',
  emoji: '🔥'
});

Remove Reaction

await realtime.directCommands.removeReaction({
  threadId: threadId,
  itemId: messageId
});

Read Receipts & Activity

Mark Message as Seen

Marks a message as seen (read receipt) in a DM thread. This uses Instagram's REST API internally and falls back to MQTT if needed. The method returns the server response — when successful, status will be "ok".

// Basic usage — mark a specific message as seen
const result = await realtime.directCommands.markAsSeen({
  threadId: '340282366841710300949128114477782749726',
  itemId: '32661457411201420841385410521202688'
});
console.log(result.status); // "ok"

Auto-Read All Incoming Messages

A common pattern is to automatically mark every incoming message as seen, so the sender always sees the blue "Seen" indicator:

realtime.on('message', async (data) => {
  const threadId = data.parsed?.threadId || data.message?.thread_id;
  const itemId = data.parsed?.messageId || data.message?.item_id;
  const status = data.parsed?.status;

  // Only mark messages from other users (skip our own)
  if (status === 'sent' || !threadId || !itemId) return;

  try {
    await realtime.directCommands.markAsSeen({ threadId, itemId });
  } catch (err) {
    console.error('Failed to mark as seen:', err.message);
  }
});

Mark as Seen with Delay (Human-Like Behavior)

If you want the bot to appear more human, you can add a small delay before sending the read receipt:

realtime.on('message', async (data) => {
  const threadId = data.parsed?.threadId || data.message?.thread_id;
  const itemId = data.parsed?.messageId || data.message?.item_id;

  if (data.parsed?.status === 'sent' || !threadId || !itemId) return;

  // Wait 1-3 seconds before marking as seen
  const delay = 1000 + Math.random() * 2000;
  await new Promise(resolve => setTimeout(resolve, delay));

  await realtime.directCommands.markAsSeen({ threadId, itemId });
});

Indicate Typing

// Start typing
await realtime.directCommands.indicateActivity({
  threadId: threadId,
  isActive: true
});

// Stop typing
await realtime.directCommands.indicateActivity({
  threadId: threadId,
  isActive: false
});

Mark Visual Message as Seen (disappearing media)

Mark view-once photos/videos as seen. Works the same way as markAsSeen but specifically for disappearing media items:

await realtime.directCommands.markVisualMessageSeen({
  threadId: threadId,
  itemId: messageId
});

Thread Management

Add Member to Thread

await realtime.directCommands.addMemberToThread(threadId, userId);

// Add multiple members
await realtime.directCommands.addMemberToThread(threadId, [userId1, userId2]);

Remove Member from Thread

await realtime.directCommands.removeMemberFromThread(threadId, userId);

Leave Thread

await realtime.directCommands.leaveThread(threadId);

Update Thread Title

await realtime.directCommands.updateThreadTitle(threadId, 'New Group Name');

Mute Thread

await realtime.directCommands.muteThread(threadId);

// Mute until specific time
await realtime.directCommands.muteThread(threadId, Date.now() + 3600000);

Unmute Thread

await realtime.directCommands.unmuteThread(threadId);

Message Requests

Approve Pending Thread

await realtime.directCommands.approveThread(threadId);

Decline Pending Thread

await realtime.directCommands.declineThread(threadId);

Moderation

Block User in Thread

await realtime.directCommands.blockUserInThread(threadId, userId);

Report Thread

await realtime.directCommands.reportThread(threadId, 'spam');

Disappearing Media (View-Once)

Send Disappearing Photo

await realtime.directCommands.sendDisappearingPhoto({
  threadId: threadId,
  uploadId: 'your_upload_id',
  viewMode: 'once'  // 'once' or 'replayable'
});

Send Disappearing Video

await realtime.directCommands.sendDisappearingVideo({
  threadId: threadId,
  uploadId: 'your_upload_id',
  viewMode: 'once'
});

Raven (View-Once / Disappearing) Media

Send photos and videos that disappear after viewing — just like the Instagram app camera button in DMs. Instagram calls these "raven" messages internally.

How it works under the hood:

  1. The photo/video is uploaded to rupload.facebook.com/messenger_image/ (Instagram's dedicated raven upload endpoint)
  2. Then broadcast via REST to /direct_v2/threads/broadcast/raven_attachment/

You don't need to worry about any of that — the library handles everything. Just pick a method and go.

Two view modes: | Mode | What happens | |------|-------------| | 'once' | Recipient opens it once, then it's gone forever | | 'replayable' | Recipient can replay it, but it still disappears from the chat |


Quick Start — Send a View-Once Photo (Standalone, No MQTT)

The simplest way to send a disappearing photo. No MQTT setup needed — just login and send:

const { IgApiClient, useMultiFileAuthState, sendRavenPhotoOnce } = require('nodejs-insta-private-api-mqt');
const fs = require('fs');

async function main() {
  const ig = new IgApiClient();
  const auth = await useMultiFileAuthState('./my_session');
  ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');

  // Login (or restore session)
  await ig.login({ username: 'your_username', password: 'your_password' });
  await auth.saveCreds(ig);

  // Read any photo from disk
  const photo = fs.readFileSync('./secret_photo.jpg');

  // Send it as view-once — one line, that's it
  const result = await sendRavenPhotoOnce(ig, photo, {
    threadId: '340282366841710300949128114477782749726'  // your DM thread ID
  });

  console.log('Sent! Status:', result.status);  // "ok"
}

main().catch(console.error);

Send a Replayable Photo

Same thing, but the recipient can replay it before it disappears:

const { sendRavenPhotoReplayable } = require('nodejs-insta-private-api-mqt');

const photo = fs.readFileSync('./photo.jpg');

const result = await sendRavenPhotoReplayable(ig, photo, {
  threadId: 'your_thread_id'
});

console.log('Replayable photo sent!', result.status);

Send a View-Once Video

Experience the new and improved video sending! Our implementation is now fully compatible with the latest Instagram protocols.

const { IgApiClientExt } = require('nodejs-insta-private-api-mqt');
const fs = require('fs');

async function sendMySecretVideo() {
  const ig = new IgApiClientExt();
  await ig.login({ username: 'your_username', password: 'your_password' });

  // Just read your video file
  const videoData = fs.readFileSync('./my_awesome_video.mp4');

  // Send it instantly as a view-once message
  const result = await ig.sendRavenVideoOnce(videoData, {
    threadId: '1234567890', // The DM thread ID
    duration: 10,           // Video length in seconds
    width: 720,             // Width (default 720)
    height: 1280            // Height (default 1280)
  });

  if (result.status === 'ok') {
    console.log('Boom! Your secret video is on its way.');
  }
}

Send a Replayable Video

If you want them to be able to see it one more time before it disappears:

const result = await ig.sendRavenVideoReplayable(videoData, {
  threadId: '1234567890',
  duration: 15
});

Using sendRavenPhoto / sendRavenVideo Directly (Choose Mode)

If you want to pick the view mode dynamically, use the base sendRavenPhoto or sendRavenVideo functions with the viewMode option:

const { sendRavenPhoto, sendRavenVideo } = require('nodejs-insta-private-api-mqt');
const fs = require('fs');

// Photo — choose 'once' or 'replayable'
const photoResult = await sendRavenPhoto(ig, fs.readFileSync('./photo.jpg'), {
  threadId: 'your_thread_id',
  viewMode: 'once'          // or 'replayable'
});

// Video — same pattern
const videoResult = await sendRavenVideo(ig, fs.readFileSync('./video.mp4'), {
  threadId: 'your_thread_id',
  viewMode: 'replayable',   // or 'once'
  duration: 8,
  width: 720,
  height: 1280
});

With MQTT / RealtimeClient (directCommands)

If you already have an MQTT connection running (for receiving messages in real-time), you can use directCommands to send raven media without importing anything extra:

// After setting up realtime connection...

const fs = require('fs');
const photo = fs.readFileSync('./secret.jpg');

// View-once photo
await realtime.directCommands.sendRavenPhotoOnce({
  threadId: threadId,
  photoBuffer: photo
});

// Replayable photo
await realtime.directCommands.sendRavenPhotoReplayable({
  threadId: threadId,
  photoBuffer: photo
});

// Or specify viewMode manually
await realtime.directCommands.sendRavenPhoto({
  threadId: threadId,
  photoBuffer: photo,
  viewMode: 'once'    // 'once' or 'replayable'
});

// View-once video
const video = fs.readFileSync('./clip.mp4');
await realtime.directCommands.sendRavenVideoOnce({
  threadId: threadId,
  videoBuffer: video,
  duration: 10,
  width: 720,
  height: 1280
});

// Replayable video
await realtime.directCommands.sendRavenVideoReplayable({
  threadId: threadId,
  videoBuffer: video,
  duration: 10
});

Complete Bot Example — Auto-Reply with Disappearing Photo

Here's a full working example of a bot that automatically replies to incoming messages with a view-once photo:

const {
  IgApiClient,
  withRealtime,
  useMultiFileAuthState,
  GraphQLSubscriptions,
  SkywalkerSubscriptions,
  sendRavenPhotoOnce,
  sendRavenPhotoReplayable,
} = require('nodejs-insta-private-api-mqt');
const fs = require('fs');

async function startBot() {
  const ig = new IgApiClient();
  const auth = await useMultiFileAuthState('./bot_session');
  ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');

  await ig.login({ username: 'bot_username', password: 'bot_password' });
  await auth.saveCreds(ig);

  const { realtime } = withRealtime(ig);

  realtime.on('message', async (data) => {
    // When someone sends you a text message
    if (data.message && data.message.text) {
      const threadId = data.message.thread_id;
      const text = data.message.text.toLowerCase();

      if (text === '!secret') {
        // Reply with a view-once photo
        const photo = fs.readFileSync('./secret_photo.jpg');
        await sendRavenPhotoOnce(ig, photo, { threadId });
        console.log('Sent view-once photo to', threadId);
      }

      if (text === '!replay') {
        // Reply with a replayable photo
        const photo = fs.readFileSync('./replay_photo.jpg');
        await sendRavenPhotoReplayable(ig, photo, { threadId });
        console.log('Sent replayable photo to', threadId);
      }
    }
  });

  await realtime.connect({
    graphQlSubs: [GraphQLSubscriptions.getDirectTypingSubscription(ig.state.cookieUserId)],
    skywalkerSubs: [SkywalkerSubscriptions.directSub(ig.state.cookieUserId)],
    irisData: await ig.request.send({ url: '/api/v1/direct_v2/inbox/', method: 'GET' }),
  });

  console.log('Bot is running! Send "!secret" or "!replay" in DMs.');
}

startBot().catch(console.error);

All Available Raven Functions

| Function | What it does | |----------|-------------| | sendRavenPhoto(ig, buffer, { threadId, viewMode }) | Send disappearing photo with chosen mode | | sendRavenPhotoOnce(ig, buffer, { threadId }) | Send view-once photo (opens once, then gone) | | sendRavenPhotoReplayable(ig, buffer, { threadId }) | Send replayable photo (can replay, still disappears) | | sendRavenVideo(ig, buffer, { threadId, viewMode, duration, width, height }) | Send disappearing video with chosen mode | | sendRavenVideoOnce(ig, buffer, { threadId, duration, width, height }) | Send view-once video | | sendRavenVideoReplayable(ig, buffer, { threadId, duration, width, height }) | Send replayable video |

All functions return a response object with status: 'ok' on success and a payload containing the item_id and thread_id.


Low-Level: broadcastRaven (DirectThread Repository)

If you want full control over the process, you can handle the upload yourself and just call the broadcast. Important: media must be uploaded to rupload.facebook.com/messenger_image/ (not rupload_igphoto/) for raven to work.

// Step 1: Upload to messenger_image endpoint (see sendRavenPhoto.js for full headers)
// Step 2: Broadcast
const result = await ig.directThread.broadcastRaven({
  uploadId: uploadId,           // from the messenger_image upload response
  attachmentFbid: mediaId,      // media_id from the upload response
  threadId: threadId,
  viewMode: 'once'              // 'once' or 'replayable'
});

Notifications

Send Screenshot Notification

await realtime.directCommands.sendScreenshotNotification({
  threadId: threadId,
  itemId: messageId
});

Send Replay Notification

await realtime.directCommands.sendReplayNotification({
  threadId: threadId,
  itemId: messageId
});

Media Upload (HTTP + Broadcast)

These methods handle the full flow: upload via HTTP rupload, then broadcast to thread.

Send Photo

const fs = require('fs');
const photoBuffer = fs.readFileSync('./photo.jpg');

await realtime.directCommands.sendPhoto({
  threadId: threadId,
  photoBuffer: photoBuffer,
  caption: 'Check this out',
  mimeType: 'image/jpeg'
});

Send Video

const fs = require('fs');
const videoBuffer = fs.readFileSync('./video.mp4');

await realtime.directCommands.sendVideo({
  threadId: threadId,
  videoBuffer: videoBuffer,
  caption: 'Watch this',
  duration: 15,
  width: 720,
  height: 1280
});

Foreground State (Connection Keepalive)

await realtime.directCommands.sendForegroundState({
  inForegroundApp: true,
  inForegroundDevice: true,
  keepAliveTimeout: 60
});

Complete Method Reference Table

| Method | Description | |--------|-------------| | sendText({ threadId, text }) | Send text message | | sendTextViaRealtime(threadId, text) | Send text (alternative) | | sendHashtag({ threadId, hashtag, text }) | Send hashtag | | sendLike({ threadId }) | Send heart/like | | sendLocation({ threadId, locationId, text }) | Send location | | sendMedia({ threadId, mediaId, text }) | Share a post | | sendProfile({ threadId, userId, text }) | Share a profile | | sendUserStory({ threadId, storyId, text }) | Share a story | | sendLink({ threadId, link, text }) | Send a link | | sendAnimatedMedia({ threadId, id, isSticker }) | Send GIF/sticker | | sendVoice({ threadId, uploadId, waveform }) | Send voice message | | sendReaction({ threadId, itemId, emoji }) | React to message | | removeReaction({ threadId, itemId }) | Remove reaction | | replyToMessage(threadId, messageId, text) | Quote reply | | editMessage(threadId, itemId, newText) | Edit message | | deleteMessage(threadId, itemId) | Delete message | | markAsSeen({ threadId, itemId }) | Mark as read | | indicateActivity({ threadId, isActive }) | Typing indicator | | markVisualMessageSeen({ threadId, itemId }) | Mark disappearing media seen | | addMemberToThread(threadId, userId) | Add group member | | removeMemberFromThread(threadId, userId) | Remove group member | | leaveThread(threadId) | Leave group | | updateThreadTitle(threadId, title) | Change group name | | muteThread(threadId, muteUntil) | Mute thread | | unmuteThread(threadId) | Unmute thread | | approveThread(threadId) | Accept message request | | declineThread(threadId) | Decline message request | | blockUserInThread(threadId, userId) | Block user | | reportThread(threadId, reason) | Report thread | | sendDisappearingPhoto({ threadId, uploadId }) | Send view-once photo (MQTT metadata only) | | sendDisappearingVideo({ threadId, uploadId }) | Send view-once video (MQTT metadata only) | | sendRavenPhoto({ threadId, photoBuffer, viewMode }) | Upload & send disappearing photo (full flow) | | sendRavenPhotoOnce({ threadId, photoBuffer }) | Upload & send view-once photo | | sendRavenPhotoReplayable({ threadId, photoBuffer }) | Upload & send replayable photo | | sendRavenVideo({ threadId, videoBuffer, viewMode }) | Upload & send disappearing video (full flow) | | sendRavenVideoOnce({ threadId, videoBuffer }) | Upload & send view-once video | | sendRavenVideoReplayable({ threadId, videoBuffer }) | Upload & send replayable video | | sendScreenshotNotification({ threadId, itemId }) | Screenshot alert | | sendReplayNotification({ threadId, itemId }) | Replay alert | | sendPhoto({ threadId, photoBuffer, caption }) | Upload & send photo | | sendVideo({ threadId, videoBuffer, caption }) | Upload & send video | | sendForegroundState(state) | Connection keepalive |


Download Media from Messages

This feature provides persistent media download for Instagram DM messages.

Quick Start: Save View-Once Photo

const { 
  downloadContentFromMessage,
  isViewOnceMedia 
} = require('nodejs-insta-private-api-mqt');
const fs = require('fs');

realtime.on('message', async (data) => {
  const msg = data.message;
  
  if (isViewOnceMedia(msg)) {
    const stream = await downloadContentFromMessage(msg);
    
    let buffer = Buffer.from([]);
    for await (const chunk of stream) {
      buffer = Buffer.concat([buffer, chunk]);
    }
    
    const ext = stream.mediaInfo.type.includes('video') ? 'mp4' : 'jpg';
    fs.writeFileSync(`viewonce_${Date.now()}.${ext}`, buffer);
  }
});

Download Regular Media

const { downloadMediaBuffer, hasMedia } = require('nodejs-insta-private-api-mqt');

realtime.on('message', async (data) => {
  const msg = data.message;
  
  if (hasMedia(msg)) {
    const { buffer, mediaInfo } = await downloadMediaBuffer(msg);
    fs.writeFileSync(`media_${Date.now()}.jpg`, buffer);
  }
});

Media Functions Reference

| Function | Description | |----------|-------------| | downloadContentFromMessage(message) | Download as stream | | downloadMediaBuffer(message) | Download as Buffer | | extractMediaUrls(message) | Get CDN URLs | | hasMedia(message) | Check if has media | | getMediaType(message) | Get media type | | isViewOnceMedia(message) | Check if disappearing |


Building Instagram Bots

The core of any bot built with this library is the connection.update event. It fires every time the MQTT connection state changes and tells you exactly what happened — whether you just connected, got disconnected, or the session expired. Combined with the message event for incoming DMs, these two hooks are all you need for most bots.

Both client.on('connection.update', ...) and client.ev.on('connection.update', ...) do exactly the same thing. The .ev property is just an alias for the client itself — use whichever style you prefer.


Minimal Working Bot

The smallest complete bot you can write: logs in, connects to MQTT, replies "pong" to any "ping" message, and handles session expiry gracefully.

const {
  IgApiClient,
  RealtimeClient,
  useMultiFileAuthState,
  GraphQLSubscriptions,
  DisconnectReason,
} = require('nodejs-insta-private-api-mqt');

async function main() {
  const ig = new IgApiClient();
  const auth = await useMultiFileAuthState('./session');

  ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');

  if (auth.hasSession()) {
    await auth.loadCreds(ig);
    const valid = await auth.isSessionValid(ig).catch(() => false);
    if (!valid) {
      const result = await ig.login({ username: 'your_username', password: 'your_password' });
      await auth.saveCreds(ig);
    }
  } else {
    const result = await ig.login({ username: 'your_username', password: 'your_password' });
    await auth.saveCreds(ig);
  }

  const realtime = new RealtimeClient(ig);

  realtime.ev.on('connection.update', ({ connection, lastDisconnect }) => {
    if (connection === 'connecting') {
      console.log('Connecting to Instagram MQTT...');
    } else if (connection === 'open') {
      console.log('Connected! Bot is live.');
    } else if (connection === 'close') {
      const code = lastDisconnect?.error?.output?.statusCode;
      if (code === DisconnectReason.loggedOut) {
        console.log('Session expired. Delete the ./session folder and re-run the bot.');
        process.exit(1);
      } else {
        console.log('Disconnected, will reconnect automatically...');
      }
    }
  });

  realtime.on('message', async (data) => {
    const msg = data.message || data.parsed;
    if (!msg?.text || !msg.thread_id) return;

    if (msg.text.toLowerCase() === 'ping') {
      await realtime.directCommands.sendText({ threadId: msg.thread_id, text: 'pong!' });
    }
  });

  const userId = ig.state.cookieUserId;
  await realtime.connect({
    graphQlSubs: [
      userId && GraphQLSubscriptions.getDirectTypingSubscription(userId),
      GraphQLSubscriptions.getAppPresenceSubscription(),
      GraphQLSubscriptions.getDirectStatusSubscription(),
    ].filter(Boolean),
  });

  await new Promise(() => {});
}

main().catch(console.error);

Production Bot — Full Lifecycle Handling

A production bot needs to survive everything: sessions that expire without warning, rate limits, network drops, and server hiccups. This example shows the full setup with every disconnect reason handled and a graceful shutdown on Ctrl+C.

const {
  IgApiClient,
  RealtimeClient,
  useMultiFileAuthState,
  GraphQLSubscriptions,
  DisconnectReason,
} = require('nodejs-insta-private-api-mqt');

const SESSION_FOLDER = './bot_session';
const USERNAME = 'your_username';
const PASSWORD = 'your_password';

async function startBot() {
  const ig = new IgApiClient();
  const auth = await useMultiFileAuthState(SESSION_FOLDER);

  ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');

  // Restore or create session
  if (auth.hasSession()) {
    await auth.loadCreds(ig);
    const valid = await auth.isSessionValid(ig).catch(() => false);
    if (!valid) {
      console.log('Session expired, logging in again...');
      const result = await ig.login({ username: USERNAME, password: PASSWORD });
      await auth.saveCreds(ig);
      console.log('Logged in, session saved.');
    } else {
      console.log('Session restored.');
    }
  } else {
    console.log('No session found, logging in...');
    const result = await ig.login({ username: USERNAME, password: PASSWORD });
    await auth.saveCreds(ig);
    console.log('Logged in, session saved.');
  }

  const realtime = new RealtimeClient(ig);
  const userId = ig.state.cookieUserId;

  // Connection lifecycle — this is how you react to every state change
  realtime.ev.on('connection.update', ({ connection, lastDisconnect, isNewLogin }) => {
    if (connection === 'connecting') {
      console.log('[BOT] Connecting...');
      return;
    }

    if (connection === 'open') {
      console.log(`[BOT] Connected${isNewLogin ? ' (first login)' : ''}`);
      return;
    }

    if (connection === 'close') {
      const code = lastDisconnect?.error?.output?.statusCode;
      const msg  = lastDisconnect?.error?.message;

      switch (code) {
        case DisconnectReason.loggedOut:
          // Instagram invalidated the session — must log in fresh
          console.error('[BOT] Logged out by Instagram. Delete session and re-run.');
          process.exit(1);
          break;

        case DisconnectReason.rateLimited:
          // Too many requests — library backs off automatically
          console.warn('[BOT] Rate limited. Backing off before next reconnect...');
          break;

        case DisconnectReason.connectionClosed:
          // You called realtime.disconnect() — expected
          console.log('[BOT] Disconnected intentionally.');
          break;

        case DisconnectReason.timedOut:
          // Keepalive ping failed — library will reconnect
          console.warn('[BOT] Connection timed out, reconnecting...');
          break;

        case DisconnectReason.reconnectFailed:
          // All retry attempts exhausted
          console.error(`[BOT] Reconnect failed after all attempts: ${msg}`);
          process.exit(1);
          break;

        default:
          // Network drop, server error, etc. — library auto-reconnects
          console.warn(`[BOT] Disconnected (code ${code}): ${msg}`);
      }
    }
  });

  // Fires when the library successfully reconnects after a drop
  realtime.on('reconnected', ({ attempt }) => {
    console.log(`[BOT] Reconnected on attempt #${attempt}`);
  });

  // Fires when all reconnect attempts have been exhausted
  realtime.on('reconnect_failed', ({ attempts, lastErrorType }) => {
    console.error(`[BOT] Failed to reconnect after ${attempts} attempts. Last error type: ${lastErrorType}`);
    process.exit(1);
  });

  // Fires after 3+ consecutive auth failures
  realtime.on('auth_failure', ({ count }) => {
    console.error(`[BOT] Auth failed ${count} times in a row. Session is probably expired.`);
  });

  // Non-fatal warnings (bad payload, unknown topic, etc.)
  realtime.on('warning', (w) => {
    console.warn('[BOT] Warning:', w?.message || w);
  });

  // Incoming DMs
  realtime.on('message', async (data) => {
    const msg = data.message || data.parsed;
    if (!msg?.text || !msg.thread_id) return;

    const threadId = msg.thread_id;
    const itemId   = msg.item_id || msg.messageId;
    const text     = msg.text.trim();

    // Mark as seen
    if (itemId) {
      await realtime.directCommands.markAsSeen({ threadId, itemId }).catch(() => {});
    }

    console.log(`[MSG] ${msg.from_user_id || msg.userId}: "${text}"`);

    if (text.toLowerCase() === 'ping') {
      await realtime.directCommands.sendText({ threadId, text: 'pong!' });
    }
  });

  // Typing indicators
  realtime.on('typing', (data) => {
    console.log(`[TYPING] Thread ${data?.thread_id || data?.threadId}`);
  });

  // Fetch inbox for iris data (needed for full message sync)
  let irisData = null;
  try {
    irisData = await ig.request.send({ url: '/api/v1/direct_v2/inbox/', method: 'GET' });
  } catch (_) {}

  // Connect with subscriptions
  await realtime.connect({
    irisData: irisData || undefined,
    graphQlSubs: [
      userId && GraphQLSubscriptions.getDirectTypingSubscription(userId),
      GraphQLSubscriptions.getAppPresenceSubscription(),
      GraphQLSubscriptions.getDirectStatusSubscription(),
      userId && GraphQLSubscriptions.getAsyncAdSubscription(userId),
      GraphQLSubscriptions.getClientConfigUpdateSubscription(),
    ].filter(Boolean),
  });

  console.log('[BOT] Bot is running. Send it a DM to test.');

  // Graceful shutdown
  process.on('SIGINT', () => {
    console.log('[BOT] Shutting down...');
    realtime.disconnect();
    process.exit(0);
  });

  await new Promise(() => {});
}

startBot().catch(console.error);

Smart Bot with Typing, Read Receipts, and Reactions

This pattern is what makes a bot feel human: mark the message as seen right away, show a typing indicator while "thinking", send the reply, then react to the original message.

realtime.on('message', async (data) => {
  const msg = data.message || data.parsed;
  if (!msg?.text || !msg.thread_id) return;

  const threadId = msg.thread_id;
  const itemId   = msg.item_id || msg.messageId;
  const text     = msg.text.toLowerCase().trim();

  // 1. Mark as seen immediately (sender sees blue "Seen")
  if (itemId) {
    await realtime.directCommands.markAsSeen({ threadId, itemId }).catch(() => {});
  }

  // 2. Show typing indicator
  await realtime.directCommands.indicateActivity({ threadId, isActive: true });

  // 3. Small delay to look human
  await new Promise(r => setTimeout(r, 1500 + Math.random() * 1500));

  // 4. Send reply based on message content
  if (text === 'hi' || text === 'hello' || text === 'hey') {
    await realtime.directCommands.sendText({ threadId, text: 'Hey! What can I do for you?' });

    // React to the greeting
    if (itemId) {
      await realtime.directCommands.sendReaction({
        threadId,
        itemId,
        reactionType: 'emoji',
        emoji: '👋',
      });
    }
  } else if (text === 'ping') {
    await realtime.directCommands.sendText({ threadId, text: 'pong!' });
  } else if (text === 'status') {
    await realtime.directCommands.sendText({
      threadId,
      text: `I'm online. MQTT connected: ${realtime._mqttConnected ? 'yes' : 'no'}`,
    });
  } else {
    // Default reply
    await realtime.directCommands.sendText({
      threadId,
      text: "I got your message! I don't know how to respond to that yet.",
    });
  }

  // 5. Stop typing indicator
  await realtime.directCommands.indicateActivity({ threadId, isActive: false });
});

Checking the Disconnect Reason Programmatically

When the connection drops, lastDisconnect.error.output.statusCode gives you a numeric code. The DisconnectReason enum maps every code to a human-readable name so you don't have to remember the numbers:

const { DisconnectReason } = require('nodejs-insta-private-api-mqt');

realtime.ev.on('connection.update', ({ connection, lastDisconnect }) => {
  if (connection !== 'close') return;

  const code = lastDisconnect?.error?.output?.statusCode;
  const message = lastDisconnect?.error?.message;
  const when = lastDisconnect?.date;

  console.log('Disconnected at:', when?.toISOString());
  console.log('Reason code:', code);
  console.log('Message:', message);

  if (code === DisconnectReason.loggedOut) {
    // 401 — Instagram says your session is invalid
    // Only fix: delete the session folder and log in again
    console.log('Need to re-login');

  } else if (code === DisconnectReason.rateLimited) {
    // 429 — sent too many requests too fast
    // The library automatically applies a longer backoff for rate limits

  } else if (code === DisconnectReason.connectionLost) {
    // 408 — connection dropped with no specific reason
    // This is the most common disconnect — usually just a network blip

  } else if (code === DisconnectReason.connectionClosed) {
    // 428 — you called realtime.disconnect() intentionally

  } else if (code === DisconnectReason.timedOut) {
    // 504 — MQTT keepalive ping timed out

  } else if (code === DisconnectReason.networkError) {
    // 503 — ECONNRESET, ETIMEDOUT, DNS failure, etc.

  } else if (code === DisconnectReason.protocolError) {
    // 500 — Thrift or MQTT parsing failed

  } else if (code === DisconnectReason.serverError) {
    // 502 — Instagram server returned a 5xx

  } else if (code === DisconnectReason.reconnectFailed) {
    // 503 — all automatic reconnect attempts were exhausted
    process.exit(1);
  }
});

All DisconnectReason values at a glance:

| Name | Code | When it happens | |------|------|-----------------| | loggedOut | 401 | Session invalid — fresh login required | | rateLimited | 429 | Too many requests — library backs off automatically | | connectionLost | 408 | Unexpected drop, no specific error info | | connectionClosed | 428 | You called disconnect() yourself | | timedOut | 504 | MQTT ping/keepalive timeout | | networkError | 503 | ECONNRESET, ETIMEDOUT, or DNS failure | | protocolError | 500 | Thrift or MQTT parse error | | serverError | 502 | Instagram returned a 5xx response | | reconnectFailed | 503 | All auto-reconnect attempts exhausted |


Using startRealTimeListener() (Simpler Connect Method)

startRealTimeListener() is a convenience wrapper around connect() that automatically builds the recommended set of GraphQL subscriptions for you. It also accepts optional health monitor and logger config:

const realtime = new RealtimeClient(ig);

realtime.ev.on('connection.update', ({ connection, lastDisconnect }) => {
  // same as above
});

realtime.on('message', async (data) => {
  // handle incoming DMs
});

// Simplest connect — auto-builds subscriptions
await realtime.startRealTimeListener();

// Or with health monitoring and file logging enabled:
await realtime.startRealTimeListener({
  credentials: { username: 'your_username', password: 'your_password' },
  enablePersistentLogger: true,
  logDir: './mqtt-logs',
});

The health monitor periodically calls /api/v1/accounts/current_user/ to check if the session is still valid and automatically re-logs in if it detects expiry. The persistent logger writes all MQTT events to rotating files in logDir, which is useful for debugging disconnects that happen when you're not watching.


Session Health Events

When the health monitor is enabled, these additional events fire on realtime:

realtime.on('health_check', ({ status, stats }) => {
  // status: 'ok' or 'failed'
  // stats: { totalUptimeHuman, uptimePercent, reconnects, ... }
  console.log(`Health: ${status}, uptime: ${stats.totalUptimeHuman}`);
});

realtime.on('session_expired', () => {
  console.log('Session detected as expired by health monitor');
});

realtime.on('relogin_success', () => {
  console.log('Automatically re-logged in after session expiry');
});

realtime.on('relogin_failed', ({ error }) => {
  console.error('Auto re-login failed:', error?.message);
});

You can also pull stats at any time:

const health = realtime.getHealthStats();
console.log('Uptime:', health.uptimePercent + '%');
console.log('Reconnects so far:', health.reconnects);
console.log('Session started:', health.sessionStart);

Session Management

Multi-File Auth State

const authState = await useMultiFileAuthState('./auth_folder');

| Method | Description | |--------|-------------| | hasSession() | Check if credentials exist | | hasMqttSession() | Check if MQTT session exists | | loadCreds(ig) | Load saved credentials | | saveCreds(ig) | Save current credentials | | isSessionValid(ig) | Validate with Instagram | | loadMqttSession() | Get saved MQTT session | | saveMqttSession(realtime) | Save MQTT session | | clearSession() | Delete all session files |


API Reference

IgApiClient

// Login
await ig.login({
  username: 'your_username',
  password: 'your_password'
});

// Save session
const serialized = ig.state.serialize();
fs.writeFileSync('session.json', JSON.stringify(serialized));

// Load session
const session = JSON.parse(fs.readFileSync('session.json'));
await ig.state.deserialize(session);

Direct Messages (REST)

// Get inbox
const inbox = await ig.direct.getInbox();

// Get thread
const thread = await ig.direct.getThread(threadId);

// Send text by username (looks up user, finds/creates thread, sends)
await ig.direct.send({ to: 'target_username', message: 'Hello!' });

// Send text by user ID (no username lookup needed)
await ig.direct.sendToUserId('12345678', 'Hello!');

// Send text to an existing thread (by thread ID)
await ig.directThread.sendToGroup({ threadId: '340282366841710300...', message: 'Hello!' });

// Low-level broadcast (full control over payload)
await ig.directThread.broadcast({
  threadIds: ['340282366841710300...'],
  item: 'text',
  form: { text: 'Hello!' },
});

REST API — Full Reference (v5.66.0)

Everything below uses the REST HTTP endpoints, not MQTT. You don't need RealtimeClient for any of this — just IgApiClient and a valid session.

const { IgApiClient } = require('nodejs-insta-private-api-mqt');
const ig = new IgApiClient();

ig.state.usePresetDevice('Samsung Galaxy S25 Ultra');
await ig.login({ username: 'your_username', password: 'your_password' });
// you're ready to use any of the methods below

Authentication & Login

Basic Login

await ig.login({ username: 'your_username', password: 'your_password' });

// or the shorter way
await ig.account.login('your_username', 'your_password');

Two-Factor Authentication (2FA)

When Instagram asks for a 2FA code, login() throws an error that contains the two_factor_identifier. Catch it and finish the flow:

try {
  await ig.account.login('username', 'password');
} catch (err) {
  if (err.response?.body?.two_factor_required) {
    const twoFactorId = err.response.body.two_factor_info.two_factor_identifier;
    
    // user enters the code from their authenticator app or SMS
    const code = '123456';
    
    await ig.account.twoFactorLogin('username', code, twoFactorId, '1');
    // verificationMethod: '1' = SMS, '3' = TOTP app
    console.log('2FA login successful');
  }
}

TOTP Two-Factor Setup

Set up authenticator-app-based 2FA for your account. This generates a seed you can add to Google Authenticator, Authy, etc.

// generate a TOTP seed (this is the secret key for your authenticator app)
const seed = await ig.totp.generateSeed();
console.log('Add this to your authenticator app:', seed.totp_seed);

// after adding it, verify with a code from the app to confirm
const code = '123456'; // from authenticator app
await ig.totp.enable(code);
console.log('TOTP 2FA is now enabled');

// get backup codes in case you lose your authenticator
const backupCodes = await ig.totp.getBackupCodes();
console.log('Save these somewhere safe:', backupCodes);

// disable TOTP 2FA
await ig.totp.disable();

SMS-Based 2FA

// enable SMS 2FA
await ig.totp.smsTwoFactorEnable('+1234567890');

// confirm with the code you received
await ig.totp.smsTwoFactorConfirm('123456');

// disable it later
await ig.totp.disableSmsTwoFactor();

Challenge Resolver

When Instagram triggers a security checkpoint (suspicious login, new location, etc.), you need to resolve the challenge:

try {
  await ig.account.login('username', 'password');
} catch (err) {
  if (err.response?.body?.challenge) {
    const challengeUrl = err.response.body.challenge.api_path;
    
    // option 1: automatic resolution (tries to handle it for you)
    const result = await ig.challenge.auto(challengeUrl);
    console.log('Challenge result:', result);
    
    // option 2: manual step-by-step
    // first, see what verification methods are available
    const page = await ig.challenge.getChallengePage(challengeUrl);
    
    // choose SMS (0) or email (1)
    await ig.challenge.selectVerifyMethod(challengeUrl, 1);
    
    // enter the code you received
    await ig.challenge.sendSecurityCode(challengeUrl, '123456');
  }
}

Account Management

Get Current User Info

const me = await ig.account.currentUser();
console.log('Username:', me.user.username);
console.log('Follower count:', me.user.follower_count);

Edit Profile

await ig.account.editProfile({
  fullName: 'John Doe',
  biography: 'building cool stuff',
  externalUrl: 'https://example.com',
  email: '[email protected]',
  phoneNumber: '+1234567890',
  username: 'johndoe_new'
});

Set Biography

await ig.account.setBiography('i like building things that work');

Set External URL / Remove Bio Links

await ig.account.setExternalUrl('https://mywebsite.com');
await ig.account.removeBioLinks();

Change Password

await ig.account.changePassword('old_password_here', 'new_password_here');

Switch to Private / Public

await ig.account.setPrivate();
await ig.account.setPublic();

Set Gender

// 1 = male, 2 = female, 3 = prefer not to say, 4 = custom
await ig.account.setGender(1);

Profile Picture

// you need an upload_id from a previous photo upload
await ig.account.profilePictureChange(uploadId);
await ig.account.profilePictureRemove();

Password Encryption Keys

// get the public keys for Instagram's password encryption (needed for some flows)
const keys = await ig.account.passwordPublicKeys();

Account Recovery

// send password recovery via email
await ig.account.sendRecoveryFlowEmail('[email protected]');

// or via SMS
await ig.account.sendRecoveryFlowSms('+1234567890');

User Operations

Fetch User Info

// by username
const user = await ig.user.infoByUsername('instagram');
console.log('User ID:', user.pk);
console.log('Followers:', user.follower_count);

// by user ID
const userById = await ig.user.info('25025320');

Resolve Username ↔ User ID

const userId = await ig.user.userIdFromUsername('instagram');
// returns '25025320'

const username = await ig.user.usernameFromUserId('25025320');
// returns 'instagram'

Search Users

const results = await ig.user.search('john', 20);
results.users.forEach(u => {
  console.log(u.username, '-', u.full_name);
});

// exact match
const exact = await ig.user.searchExact('johndoe');

Follow / Unfollow

await ig.user.follow('25025320');
await ig.user.unfollow('25025320');

Block / Unblock

await ig.user.block('25025320');
await ig.user.unblock('25025320');

// see all blocked users
const blocked = await ig.user.getBlockedUsers();

Mute / Unmute

// mute posts, stories, or both
await ig.user.mute('25025320', { mutePosts: true, muteStories: true });
await ig.user.unmute('25025320', { unmutePosts: true, unmuteStories: true });

Get Followers / Following (with pagination)

// get up to 200 followers at a time
const followers = await ig.user.getFollowers('25025320', 200);
console.log('Got', followers.users.length, 'followers');

// pagination — pass the maxId from the previous response
const moreFollowers = await ig.user.getFollowers('25025320', 200, followers.next_max_id);

// same for following
const following = await ig.user.getFollowing('25025320', 200);

Get User's Posts (with pagination)

// grab the latest 50 posts
const posts = await ig.user.getUserMedias('25025320', 50);
posts.items.forEach(item => {
  console.log(item.pk, '-', item.caption?.text?.substring(0, 50));
});

// next page
const morePosts = await ig.user.getUserMedias('25025320', 50, posts.next_max_id);

Get User's Reels / Clips

const reels = await ig.user.getUserReels('25025320', 50);
const clips = await ig.user.getUserClips('25025320', 50);

Get User's Stories

const stories = await ig.user.getUserStories('25025320');
stories.reel?.items.forEach(story => {
  console.log('Story:', story.pk, 'taken at:', story.taken_at);
});

Get Tagged Posts

const tagged = await ig.user.getUserTags('25025320');

Mutual Followers

const mutual = await ig.user.getMutualFollowers('25025320');

Remove a Follower

await ig.user.removeFollower('25025320');

Report a User

// reason: 1 = spam, 2 = inappropriate, etc.
await ig.user.report('25025320', 1);

Get Suggested Users

const suggestions = await ig.user.getSuggested();

Friendship Status (Bulk)

// check relationship status with multiple users at once
const statuses = await ig.user.getFriendshipStatuses(['12345', '67890', '11111']);

Media Operations

Get Media Info

const info = await ig.media.info('3193593212003331660');
console.log('Type:', info.items[0].media_type);
console.log('Likes:', info.items[0].like_count);

PK / Shortcode Conversion

These are super useful when you have a post URL and need the numeric ID, or the other way around.

const { MediaRepository } = require('nodejs-insta-private-api-mqt');

// convert shortcode to numeric PK
const pk = MediaRepository.mediaPkFromCode('CxR7Bsejq5M');
// '3193593212003331660'

// convert PK back to shortcode
const code = MediaRepository.mediaCodeFromPk('3193593212003331660');
// 'CxR7Bsejq5M'

// extract PK directly from a full URL
const pkFromUrl = MediaRepository.mediaPkFromUrl('https://www.instagram.com/p/CxR7Bsejq5M/');
// '3193593212003331660'

Like / Unlike

await ig.media.like('3193593212003331660');
await ig.media.unlike('3193593212003331660');

Comment

const comment = await ig.media.comment('3193593212003331660', 'great shot!');
console.log('Comment ID:', comment.comment.pk);

Reply to a Comment

await ig.media.replyToComment('3193593212003331660', '17858893269000001', '@user thanks!');

Like / Unlike Comments

await ig.media.likeComment('3193593212003331660', '17858893269000001');
await ig.media.unlikeComment('3193593212003331660', '17858893269000001');

Pin / Unpin Comments

await ig.media.pinComment('3193593212003331660', '17858893269000001');
await ig.media.unpinComment('3193593212003331660', '17858893269000001');

Delete Comments (Single or Bulk)

// single
await ig.media.deleteComment('3193593212003331660', '17858893269000001');

// bulk delete
await ig.media.bulkDeleteComments('3193593212003331660', [
  '17858893269000001',
  '17858893269000002',
  '17858893269000003'
]);

Get Comments (Paginated)

const comments = await ig.media.comments('3193593212003331660', null, 20);
// next page:
const moreComments = await ig.media.comments('3193593212003331660', comments.next_min_id, 20);

Get Comment Thread (Replies to a Comment)

const thread = await ig.media.commentThreadComments('3193593212003331660', '17858893269000001');

Get Likers

const likers = await ig.media.likers('3193593212003331660');
likers.users.forEach(u => console.log(u.username));

Save / Unsave

await ig.media.save('3193593212003331660');

// save to a specific collection
await ig.media.save('3193593212003331660', 'collection_id_here');

await ig.media.unsave('3193593212003331660');

Archive / Unarchive

await ig.media.archive('3193593212003331660');
await ig.media.unarchive('3193593212003331660');

Delete Media

// mediaType: 'PHOTO', 'VIDEO', 'CAROUSEL'
await ig.media.delete('3193593212003331660', 'PHOTO');

Edit Caption

await ig.media.edit('3193593212003331660', 'new caption goes here', {
  usertags: { in: [{ user_id: '12345', position: [0.5, 0.5] }] }
});

Enable / Disable Comments

await ig.media.disableComments('3193593212003331660');
await ig.media.enableComments('3193593212003331660');

Download Media

// download by URL
const buffer = await ig.media.downloadByUrl('https://instagram.cdnurl.com/...');

// download by PK (photo or video)
const photo = await ig.media.downloadPhoto('3193593212003331660');
const video = await ig.media.downloadVideo('3193593212003331660');

oEmbed

const oembed = await ig.media.oembed('https://www.instagram.com/p/CxR7Bsejq5M/');
console.log(oembed.title, '-', oembed.author_name);

Get User Who Posted a Media

const user = await ig.media.getUser('3193593212003331660');

Reels / Clips

Upload and browse Reels through the REST API.

Upload a Reel

const result = await ig.clip.upload({
  videoBuffer: fs.readFileSync('./reel.mp4'),
  caption: 'check this out',
  coverImage: fs.readFileSync('./cover.jpg'), // optional
  duration: 15,
  width: 1080,
  height: 1920,
  audisMuted: false,
});
console.log('Reel PK:', result.media?.pk);

Configure a Video as Reel (after uploading separately)

const configured = await ig.clip.configure({
  upload_id: uploadId,
  caption: 'my reel',
  duration: 15,
  width: 1080,
  height: 1920,
});

Discover Reels / Connected Reels

// the explore-style reels feed
const discover = await ig.clip.discoverReels(10);
discover.items.forEach(reel => {
  console.log(reel.media.code, '-', reel.media.caption?.text?.substring(0, 40));
});

// paginate
const more = await ig.clip.discoverReels(10, discover.paging_info?.max_id);

// connected reels (similar reels after watching one)
const connected = await ig.clip.connectedReels(10);

Download a Reel

const reelBuffer = await ig.clip.download('3193593212003331660');

// or from URL
const reelFromUrl = await ig.clip.downloadByUrl('https://instagram.cdnurl.com/...');

Get Music Info for Reels

const music = await ig.clip.musicInfo({ music_canonical_id: '12345' });

Stories

Get Your Story Feed (Tray)

const tray = await ig.story.getFeed();
tray.tray.forEach(reel => {
  console.log(reel.user.username, '- stories:', reel.media_count);
});

Get Someone's Stories

const stories = await ig.story.getUserStories('25025320');
stories.reel?.items.forEach(item => {
  console.log('Type:', item.media_type, 'Taken at:', item.taken_at);
});

Upload a Photo Story

const result = await ig.story.upload({
  file: fs.readFileSync('./story.jpg'),
  caption: 'hello world', // optional
});
console.log('Story ID:', result.media?.pk);

Upload a Video Story

const result = await ig.story.uploadVideo({
  file: fs.readFileSync('./story.mp4'),
  duration: 10,
  width: 1080,
  height: 1920,
});

Mark Stories as Seen

await ig.story.seen([
  { id: 'media_id_1', taken_at: 1700000000, user: { pk: '25025320' } }
]);

React to a Story

await ig.story.react({
  mediaId: '3193593212003331660',
  reelId: '25025320',
  emoji: '🔥'
});

Highlights

Get User's Highlights

const highlights = await ig.highlights.getHighlightsTray('25025320');
highlights.tray.forEach(h => {
  console.log(h.id, '-', h.title);
});

Get a Specific Highlight

const highlight = await ig.highlights.getHighlight('highlight:12345678');

Create a Highlight

await ig.highlights.create('My Trip', ['story_id_1', 'story_id_2'], 'cover_media_id');

Edit a Highlight

await ig.highlights.edit('highlight_id', 'Updated Title', ['new_story_id']);

Add / Remove Stories from Highlight

await ig.highlights.addStories('highlight_id', ['story_id_3', 'story_id_4']);
await ig.highlights.removeStories('highlight_id', ['story_id_1']);

Update Highlight Cover

await ig.highlights.updateCover('highlight_id', 'cover_media_id');

Delete a Highlight

await ig.highlights.delete('highlight_id');

Upload & Configure Media

Upload a Photo Post

const upload = await ig.upload.photo({
  file: fs.readFileSync('./photo.jpg'),
});

const post = await ig.upload.configurePhoto({
  upload_id: upload.upload_id,
  caption: 'sunset vibes',
  usertags: {
    in: [{ user_id: '12345', position: [0.5, 0.5] }]
  }
});
console.log('Posted! PK:', post.media?.pk);

Upload a Video Post

const upload = await ig.upload.video({
  file: fs.readFileSync('./video.mp4'),
  duration: 30,
  width: 1080,
  height: 1920,
});

const post = await ig.upload.configureVideo({
  upload_id: upload.upload_id,
  caption: 'check this clip',
  duration: 30,
  width: 1080,
  height: 1920,
});

Configure as Reel (Clips)

const reel = await ig.upload.configureToClips({
  upload_id: upload.upload_id,
  caption: 'my first reel',
  duration: 15,
  width: 1080,
  height: 1920,
});

Configure as Story

const story = await ig.upload.configureToStory({
  upload_id: upload.upload_id,
});

Upload a Carousel (Multiple Photos/Videos)

const carousel = await ig.feed.uploadCarousel({
  caption: 'summer trip highlights',
  children: [
    { type: 'photo', file: fs.readFileSync('./pic1.jpg') },
    { type: 'photo', file: fs.readFileSync('./pic2.jpg') },
    { type: 'video', file: fs.readFileSync('./vid1.mp4'), duration: 10, width: 1080, height: 1080 },
  ]
});

Feed

Home Timeline

const feed = await ig.timeline.getFeed();
feed.feed_items?.forEach(item => {
  const media = item.media_or_ad;
  if (media) console.log(media.user.username, '-', media.caption?.text?.substring(0, 40));
});

Hashtag Feed

const tagFeed = await ig.feed.getTag('photography');

Location Feed

const locFeed = await ig.feed.getLocation('213385402');

Liked Posts

const liked = await ig.feed.getLiked();

Saved Posts

const saved = await ig.feed.getSaved();

Reels Tray (Stories of People You Follow)

const tray = await ig.feed.reelsTray();

Explore Feed

const explore = await ig.feed.getExploreFeed();

Reels Feed

const reels = await ig.feed.getReelsFeed();
const userReels = await ig.feed.getUserReelsFeed('25025320');

Reels Media (Bulk)

// get stories for multiple users at once
const reelsMedia = await ig.feed.reelsMedia(['25025320', '12345678']);

Timeline (Reels)

// get reels from your timeline
const reels = await ig.timeline.reels(10);

// explore reels
const exploreReels = await ig.timeline.exploreReels(10);

Direct Messages (REST API)

The REST-based DM methods. These work without MQTT — they're regular HTTP requests.

Get Inbox

const inbox = await ig.direct.getInbox();
inbox.inbox.threads.forEach(t => {
  console.log(t.thread_title || t.users[0]?.username, '- last:', t.last_permanent_item?.text);
});

// paginate
const page2 = await ig.direct.getInbox(inbox.inbox.oldest_cursor, 20);

Pending Inbox (Message Requests)

const pending = await ig.direct.getPendingInbox();

Get a Thread

const thread = await ig.direct.getThread(threadId);
thread.thread.items.forEach(msg => {
  console.log(msg.user_id, ':', msg.text || `[${msg.item_type}]`);
});

Send Text via REST

There are multiple ways to send a text message via the REST API:

// Method 1: By username (auto-resolves user ID and thread)
await ig.direct.send({ to: 'target_username', message: 'hey there!' });

// Method 2: By user ID (skips username lookup)
await ig.direct.sendToUserId('25025320', 'hey there!');