Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

8 Commits

Folders and files

Repository files navigation

IndReelForge — Node.js SDK & MCP bridge

npm version License: MIT

Official Node.js SDK for the IndReelForge Social Media API. Publish videos, photos, text and documents to Instagram, YouTube, TikTok, Facebook, X (Twitter), LinkedIn, Threads, Pinterest, Reddit, Bluesky, Google Business Profile, Discord, Telegram, Slack, Mastodon & WordPress with a single API call.

The package also ships npx indreelforge mcp, a zero-dependency stdio bridge that connects AI agents (Claude Desktop, Cursor and other MCP clients) to the hosted IndReelForge MCP server.

Installation

npm install indreelforge

Requires Node.js 18 or newer (uses the built-in fetch, FormData and Blob). No runtime dependencies.

Quick start

import IndReelForge from 'indreelforge';

const client = new IndReelForge({ apiKey: process.env.INDREELFORGE_API_KEY! });

const result = await client.uploadVideo({
  user: 'my-profile',
  platforms: ['instagram', 'tiktok', 'youtube'],
  videoPath: './my-video.mp4',
  title: 'Check out this video!',
});

console.log(result.data.upload_id);

CommonJS works too: const { IndReelForge } = require('indreelforge');

Configuration

const client = new IndReelForge({
  apiKey: 'your-api-key',                  // Required. Dashboard → API Keys
  baseUrl: 'https://api.indreelforge.com', // Optional (default)
  timeout: 120000,                         // Optional, ms (default 120 s)
  fetch: customFetch,                      // Optional, custom fetch implementation
  headers: { 'X-Trace': 'abc' },           // Optional, extra headers on every request
});

Every request sends Authorization: Apikey <key> and User-Agent: indreelforge-node/<version>.

Publishing

Common upload options

All upload* methods accept:

Option API field Description
user user Profile name (required)
platforms platform[] instagram, youtube, tiktok, facebook, x, linkedin, threads, pinterest, reddit, bluesky, google_business, discord, telegram, slack, mastodon, wordpress (also exported as PLATFORMS)
title title Title / caption
description description Long description
firstComment first_comment First comment
schedule schedule ISO-8601 datetime to publish later
timezone timezone IANA timezone for schedule
addToQueue add_to_queue Put the post in the profile's next free queue slot
asyncUpload async_upload false = wait (up to ~50 s) for the publish result
requestId request_id Your own correlation id (see getRequestStatus)
idempotencyKey Idempotency-Key header Retries with the same key never double-post
platformOptions any Platform-specific fields, sent as-is (see below)

Upload a video

// From a local file
await client.uploadVideo({
  user: 'my-profile',
  platforms: ['instagram', 'tiktok', 'youtube', 'facebook'],
  videoPath: './video.mp4',
  title: 'My video',
  firstComment: '#socialmedia',
  schedule: '2026-12-01T10:00:00Z',
  youtubePrivacy: 'public',
  youtubeTags: ['tutorial', 'tech'],
  idempotencyKey: 'video-2026-12-01',
});

// From a public URL (the server downloads it)
await client.uploadVideo({
  user: 'my-profile',
  platforms: ['tiktok'],
  videoUrl: 'https://cdn.example.com/clip.mp4',
});

Upload photos

Local files, public URLs, or both — up to 10 in total.

await client.uploadPhotos({
  user: 'my-profile',
  platforms: ['instagram', 'facebook', 'pinterest'],
  photoPaths: ['./photo1.jpg'],
  photoUrls: ['https://cdn.example.com/photo2.jpg'],
  title: 'Photo carousel',
  platformOptions: { pinterest_board_id: '1234567890' },
});

Text post

await client.uploadText({
  user: 'my-profile',
  platforms: ['x', 'threads', 'bluesky', 'linkedin'],
  text: 'Just shipped a new feature!',   // `title` is still accepted as before
});

Document post (LinkedIn)

await client.uploadDocument({
  user: 'my-profile',
  documentPath: './deck.pdf',            // PDF, PPT, PPTX, DOC, DOCX (max 100 MB)
  title: 'Q4 results',
  visibility: 'PUBLIC',
  targetLinkedinPageId: '12345',         // optional: post as a company page
});

Synchronous mode

By default an upload returns immediately and publishes in the background. With asyncUpload: false the server waits up to about 50 seconds and returns the outcome:

const res = await client.uploadText({ user: 'my-profile', platforms: ['bluesky'], text: 'hi', asyncUpload: false });
if (res.sync) {
  console.log(res.data.status, res.data.platforms, res.data.platform_post_ids);
} else {
  // Took longer than the wait window: poll instead
  console.log(await client.getUploadStatus(res.data.upload_id));
}

Platform-specific options

Pass any platform-specific API field through platformOptions. Arrays are sent as repeated fields, booleans as "true"/"false", objects as JSON.

await client.uploadPhotos({
  user: 'my-profile',
  platforms: ['tiktok', 'google_business', 'reddit'],
  photoUrls: ['https://cdn.example.com/menu.jpg'],
  title: 'New menu is live',
  platformOptions: {
    privacy_level: 'PUBLIC_TO_EVERYONE',   // TikTok
    auto_add_music: true,
    gbp_location_id: 'locations/123',      // Google Business Profile
    gbp_cta_type: 'LEARN_MORE',
    gbp_cta_url: 'https://example.com',
    reddit_subreddit: 'food',              // Reddit
  },
});

Use the platform helper methods below to look up page, board and location ids.

Per-platform text overrides (every platform). <platform>_title and <platform>_description replace the shared title / description for that platform only, e.g. x_title, linkedin_description, mastodon_title, wordpress_description.

Mastodon

Field Values
mastodon_visibility public (default), unlisted, private or direct
mastodon_spoiler_text Content warning shown before the post
mastodon_sensitive true to mark media as sensitive. Omit it otherwise: the server treats any non-empty value, including "false", as true
mastodon_alt_text Alt text for attached media (defaults to the title)
mastodon_language ISO 639 language code, e.g. en

WordPress

Field Values
wordpress_status publish (default) or draft
wordpress_categories Numeric category IDs: '3,7' or [3, 7]
wordpress_tags Numeric tag IDs: '12,15' or [12, 15]
await client.uploadText({
  user: 'my-profile',
  platforms: ['mastodon', 'wordpress', 'slack'],
  title: 'Release notes',
  description: 'Full changelog for 1.1 …',
  platformOptions: {
    mastodon_title: 'Release notes are out (short version for Mastodon)',
    mastodon_visibility: 'unlisted',
    mastodon_language: 'en',
    wordpress_status: 'draft',
    wordpress_categories: [3, 7],
    wordpress_tags: '12,15',
  },
});

Connect platforms with credentials

Discord, Telegram, Slack, Mastodon and WordPress connect with credentials instead of an OAuth login. connectWithCredentials(profile, platform, credentials) sends them to the API, which verifies them live with the platform before saving them (encrypted). Connecting again replaces the stored credentials. The credentials object is typed per platform:

Platform Credentials (all required)
discord { webhook_url } — channel webhook URL
telegram { bot_token, chat_id } — token from @BotFather; public channel @username or numeric chat ID
slack { webhook_url } — incoming webhook URL (Slack can only verify it on the first post)
mastodon { instance_url, access_token } — e.g. https://mastodon.social; token with write:statuses and write:media
wordpress { site_url, username, app_password } — self-hosted site, a user who can publish, an application password
const res = await client.connectWithCredentials('my-profile', 'telegram', {
  bot_token: '123456789:AA…',
  chat_id: '@mychannel',
});
console.log(res.platform_username);

try {
  await client.connectWithCredentials('my-profile', 'mastodon', { instance_url: 'https://mastodon.social', access_token: 'bad' });
} catch (err) {
  if (err instanceof IndReelForgeError && err.status === 400) {
    // err.code: 'MISSING_FIELD' | 'FIELD_TOO_LONG' | 'INVALID_CREDENTIALS'
    console.error(err.code, err.body?.details?.field, err.message);
  }
}

Other failures: 404 (profile not found) and 502 (the platform could not be reached to verify the details). The required fields per platform are also exported as CREDENTIAL_PLATFORMS.

All methods

Area Method Endpoint
Upload uploadVideo(options) POST /api/upload
uploadPhotos(options) POST /api/upload_photos
uploadText(options) POST /api/upload_text
uploadDocument(options) POST /api/upload_document
Account getMe() GET /api/me
Uploads getUploadStatus(uploadId) GET /api/upload/:id/status
getRequestStatus(requestId) GET /api/uploadposts/status?request_id=
listUploads({ limit, page, status }) GET /api/uploads
getUpload(uploadId) GET /api/uploads/:id
deleteUpload(uploadId) DELETE /api/uploads/:id
retryUpload(uploadId) POST /api/uploads/:id/retry
getPlatformLimits() GET /api/platform-limits
Profiles getProfiles() GET /api/profiles
createProfile(name) POST /api/profiles
deleteProfile(name) DELETE /api/profiles/:name
connectWithCredentials(profile, platform, credentials) POST /api/profiles/:name/connect/:platform/credentials
Schedule getScheduledPosts() GET /api/schedule
updateScheduledPost(jobId, { scheduledDate, timezone, title, caption }) PATCH /api/schedule/:jobId
cancelScheduledPost(jobId) DELETE /api/schedule/:jobId
Queue getQueueSettings(profile) GET /api/queue/settings
updateQueueSettings({ profile, slots, daysOfWeek, timezone, maxPostsPerSlot }) POST /api/queue/settings
getQueuePreview(profile, count?) GET /api/queue/preview
getNextQueueSlot(profile) GET /api/queue/next-slot
Platform helpers getFacebookPages(profile) GET /api/platforms/facebook/pages
getLinkedInPages(profile) GET /api/platforms/linkedin/pages
getPinterestBoards(profile) GET /api/platforms/pinterest/boards
getGoogleBusinessLocations(profile) GET /api/platforms/google-business/locations
Analytics getAnalytics(range = '30d') GET /api/analytics/overview
getLiveAnalytics(profile, platforms?) GET /api/analytics/live/:profile
getPostAnalytics(uploadId, platform?) GET /api/analytics/post/:upload_id
getTotalImpressions(profile, { platform, period, startDate, endDate }) GET /api/analytics/total-impressions/:profile
FFmpeg getFfmpegUsage() GET /api/ffmpeg/usage
listFfmpegJobs() GET /api/ffmpeg/jobs
getFfmpegJob(jobId) GET /api/ffmpeg/jobs/:jobId
Plans getPlans() GET /api/plans

Examples:

const me = await client.getMe();                              // plan + monthly usage
const { uploads } = await client.listUploads({ status: 'failed', limit: 10 });
await client.retryUpload(uploads[0].id);

await client.updateQueueSettings({
  profile: 'my-profile',
  slots: ['09:00', '17:30'],
  daysOfWeek: [1, 2, 3, 4, 5],
  timezone: 'Europe/London',
});
await client.uploadVideo({ user: 'my-profile', platforms: ['youtube'], videoPath: './v.mp4', addToQueue: true });

const { scheduled_posts } = await client.getScheduledPosts();
await client.updateScheduledPost(scheduled_posts[0].id, { scheduledDate: '2026-12-24T09:00:00Z' });

Error handling

Every API failure throws an IndReelForgeError:

Property Description
status HTTP status (0 = network error, 408 = client-side timeout)
code API error code (error_code / code), e.g. MISSING_VIDEO, or NETWORK_ERROR / TIMEOUT
body Parsed response body
message Human-readable message from the API
import { IndReelForgeError } from 'indreelforge';

try {
  await client.uploadVideo({ user: 'my-profile', platforms: ['tiktok'], videoPath: './v.mp4' });
} catch (err) {
  if (err instanceof IndReelForgeError) {
    console.error(`API error ${err.status} (${err.code}): ${err.message}`, err.body);
  } else {
    throw err;
  }
}

Invalid arguments (for example no videoPath/videoUrl, or more than 10 photos) throw a TypeError before any request is made.

MCP: use IndReelForge from AI agents

IndReelForge runs a hosted Model Context Protocol server, so Claude, ChatGPT, Cursor and other agents can publish posts, check upload status and read analytics with your API key.

Option 1 — stdio bridge (Claude Desktop, Cursor, any stdio MCP client)

Add this to your client's MCP config (claude_desktop_config.json for Claude Desktop; ~/.cursor/mcp.json or .cursor/mcp.json for Cursor):

{
  "mcpServers": {
    "indreelforge": {
      "command": "npx",
      "args": ["-y", "indreelforge", "mcp"],
      "env": {
        "INDREELFORGE_API_KEY": "your-api-key"
      }
    }
  }
}

Environment variables:

Variable Required Description
INDREELFORGE_API_KEY yes Your IndReelForge API key
INDREELFORGE_MCP_URL no MCP endpoint (default https://api.indreelforge.com/mcp)
INDREELFORGE_MCP_TIMEOUT_MS no Per-request timeout in ms (default 300000)

The bridge reads JSON-RPC messages from stdin, forwards each to the hosted server and writes the responses to stdout, one per line. Only JSON-RPC goes to stdout; diagnostics go to stderr. HTTP and network failures come back as JSON-RPC errors for the matching request id, so the client never hangs. npx indreelforge --help and npx indreelforge --version are available.

Option 2 — connect to the hosted URL directly

Clients that support remote (Streamable HTTP) MCP servers with custom headers (for example Cursor or Claude Code) can skip the bridge:

  • URL: https://api.indreelforge.com/mcp
  • Header: Authorization: ApiKey your-api-key
{
  "mcpServers": {
    "indreelforge": {
      "url": "https://api.indreelforge.com/mcp",
      "headers": { "Authorization": "ApiKey your-api-key" }
    }
  }
}

Claude Code:

claude mcp add --transport http indreelforge https://api.indreelforge.com/mcp \
  --header "Authorization: ApiKey your-api-key"

Programmatic bridge

import { runMcpBridge } from 'indreelforge';

await runMcpBridge({ apiKey: process.env.INDREELFORGE_API_KEY!, input: process.stdin, output: process.stdout });

Supported platforms

Platform Video Photo Text Document Scheduling
Instagram ✓ ✓ - - ✓
YouTube ✓ - - - ✓
TikTok ✓ ✓ - - ✓
Facebook ✓ ✓ ✓ - ✓
X (Twitter) ✓ ✓ ✓ - ✓
LinkedIn ✓ ✓ ✓ ✓ ✓
Threads ✓ ✓ ✓ - ✓
Pinterest ✓ ✓ - - ✓
Reddit ✓ ✓ ✓ - ✓
Bluesky ✓ ✓ ✓ - ✓
Google Business Profile - ✓ ✓ - ✓
Discord ✓ ✓ ✓ - ✓
Telegram ✓ ✓ ✓ - ✓
Slack - - ✓ - ✓
Mastodon ✓ ✓ ✓ - ✓
WordPress ✓ ✓ ✓ - ✓

Discord, Telegram, Slack, Mastodon and WordPress are connected with credentials; the others connect through an OAuth login in the dashboard (or an app password for Bluesky).

Upgrading from 1.0

1.1 is backward compatible: existing method names, options and return values keep working.

  • The class is now IndReelForge; InReelForge is still exported as an alias, and the default export is the same class.
  • Errors are IndReelForgeError (now exported). InReelForgeError is an alias, so instanceof InReelForgeError still works. err.name is now 'IndReelForgeError', so prefer instanceof checks.
  • uploadText sends your text as text (the API treats text and title the same).

Links

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages