npm package ยท TypeScript ยท Open Source

@boe-ventures/platform-schemas

TypeScript schemas for unofficial messaging platform APIs. Endpoints, auth patterns, volatile IDs, and anti-detection notes.

$npm install @boe-ventures/platform-schemas
5
Platforms
15+
Endpoints
50+
Types
June 2026
Verified

Platform Overview

PlatformAuth PatternAPI StyleReadWriteStabilityDetection Risk
๐Ÿ’ฌ Slack
xoxc- token + d= cookieREST (POST forms)โœ“โœ“StableLow
๐Ÿ’ผ LinkedIn
JSESSIONID CSRFGraphQL (Voyager)โœ“โœ“Volatile IDsHigh
๐Ÿ“ท Instagram
csrftoken + ig-app-id + fb_dtsgGraphQL (doc_id)โœ“โœ“Volatile IDsCritical
๐Ÿ”ฅ Tinder
x-auth-tokenREST (JSON)โœ“โœ“StableLow
๐Ÿ’ฌ iMessage
Full Disk AccessSQLite + AppleScriptโœ“โœ“StableNone
๐Ÿ’ฌ

Slack

REST API with session tokens. Your own workspace โ€” relatively permissive.

Auth Extraction

// Auth: xoxc- token + d= session cookie
requiredTokens: ["xoxcToken", "dCookie", "teamDomain"]

// xoxcToken โ†’ boot_data.api_token || localStorage.getItem('localConfig_v2')
// dCookie   โ†’ document.cookie "d=" (HttpOnly on some setups)
// teamDomain โ†’ window.location.hostname.split('.')[0]

Key Endpoints

// client.boot โ€” returns everything in one call
POST https://{teamDomain}.slack.com/api/client.boot
Body: { token: "{xoxcToken}" }
// Returns: channels, IMs, users, team info

// conversations.history โ€” per-channel messages
POST https://{teamDomain}.slack.com/api/conversations.history
Body: { token: "{xoxcToken}", channel: "{channelId}", limit: 20 }

Types

interface SlackBootResponse {
  ok: boolean;
  self: { id: string; name: string };
  team: { id: string; name: string; domain: string };
  channels: SlackRawChannel[];
  users?: SlackRawUser[];
  ims?: SlackRawIM[];
}

interface SlackRawChannel {
  id: string;
  name: string;
  is_channel: boolean;
  is_im: boolean;
  is_mpim: boolean;
  is_private: boolean;
  unread_count?: number;
  latest?: { text?: string; ts?: string; user?: string };
}
Anti-detection: Slack is relatively permissive โ€” it's your own workspace. Main risk is IT admin monitoring.
๐Ÿ’ผ

LinkedIn

GraphQL via Voyager API. Volatile query IDs change on every deploy.

Auth Extraction

// CSRF from JSESSIONID cookie (strip quotes)
// document.cookie.match(/JSESSIONID="?([^";]+)/)?.[1]
// Header: { "csrf-token": "{csrfToken}", accept: "application/graphql" }

Volatile IDs โ€” The Key Feature

// โš ๏ธ VOLATILE โ€” these change when LinkedIn deploys
export const QUERY_IDS = {
  conversations: "messengerConversations.0d5e6781bbee71c3e51c8843c6519f48",
  messages:      "messengerMessages.5846eeb71c981f11e0134cb6626cc314",
} as const;

Key Endpoints

// Conversations (GraphQL)
GET https://www.linkedin.com/voyager/api/voyagerMessagingGraphQL/graphql
  ?queryId=messengerConversations.0d5e6...
  &variables=(mailboxUrn:{encodedProfileUrn})
Headers: { "csrf-token": "...", "x-restli-protocol-version": "2.0.0", accept: "application/graphql" }

Types

interface LinkedInRawConversation {
  entityUrn: string;     // urn:li:msg_conversation:(urn:li:fsd_profile:...,threadId)
  backendUrn: string;
  descriptionText: { text: string };
  lastActivityAt: number;  // epoch ms
  unreadCount: number;
  conversationParticipants: LinkedInRawParticipant[];
}
Anti-detection: LinkedIn actively scans for 6,200+ extensions. CRITICAL: Zero DOM injection, zero web-accessible resources.
๐Ÿ“ท

Instagram

Meta's GraphQL with doc_id mutations. The strictest platform โ€” tread carefully.

The Thread ID Trap โ€” Biggest Gotcha

// โš ๏ธ THE #1 INSTAGRAM API BUG:
// REST inbox gives:   thread_id  = "340282366841710301..." (LONG)
// GraphQL wants:      thread_fbid = "1360243288701783"     (SHORT)
// Use the WRONG one โ†’ silent failure (HTML response, 0 messages)
// ALWAYS capture thread_fbid from the inbox response for GraphQL calls

Volatile doc_ids

export const DOC_IDS = {
  inboxQuery:   "27228858046797698",
  threadFetch:  "27110549851904846",   // โ† current as of 2026-06-21
  textSend:     "26911679871773184",
  threadDetail: "27530161873341603",   // โ† STALE, returns HTML
} as const;

export const IG_APP_ID = "936619743392459"; // hardcoded in their JS bundle

Write Endpoint โ€” IGDirectTextSendMutation

// IGDirectTextSendMutation โ€” send a DM
POST https://www.instagram.com/api/graphql
Headers: {
  "x-csrftoken":     "{csrfToken}",
  "x-ig-app-id":     "936619743392459",
  "x-fb-friendly-name": "IGDirectTextSendMutation",
  "x-fb-lsd":         "{lsd}",     // from page config
  "content-type":    "application/x-www-form-urlencoded",
}
Body: {
  doc_id: "26911679871773184",
  fb_dtsg: "{fbDtsg}",     // CSRF-style token from page config
  variables: JSON.stringify({
    ig_thread_igid: "{threadFbid}",  // SHORT id
    offline_threading_id: "{otid}",  // client-generated 19-digit id
    text: { sensitive_string_value: "{text}" },
    send_attribution: "igd_web_chat_tab:in_thread",
  }),
}
Anti-detection: Instagram is the STRICTEST platform. NEVER inject DOM elements. A spike from 5 DMs/day to 50 will trigger algorithmic flags.
๐Ÿ”ฅ

Tinder

Simple REST JSON API. The easiest platform to work with.

Auth

// Simplest auth of all platforms
// x-auth-token from localStorage: TinderWeb/APIToken
// That's it. One header.

Endpoints

GET  https://api.gotinder.com/v2/matches?count=60&message=1
POST https://api.gotinder.com/user/matches/{matchId}  // send message
Headers: { "x-auth-token": "***", platform: "web" }
Body: { message: "{text}" }

Types

interface TinderRawMatch {
  _id: string;
  person: {
    _id: string;
    name: string;
    bio?: string;
    photos: Array<{ url: string; processedFiles: Array<{...}> }>;
  };
  messages: TinderRawMessage[];
  last_activity_date: string;
  message_count: number;
}
Anti-detection: Tinder's API is straightforward with minimal bot detection. Standard rate limiting applies.
๐Ÿ’ฌ

iMessage

No API, no network. Just SQLite on disk and AppleScript for writes.

Database Access

// No network requests. No tokens. Just SQLite.
// Path: ~/Library/Messages/chat.db
// Requires: Full Disk Access (macOS)

// Key tables: chat, message, handle, chat_message_join, attachment
// chat.style: 43 = DM, 45 = group

// Apple's timestamp format (because nothing can be simple):
function coreDataTimestampToDate(timestamp: number): Date {
  const CORE_DATA_EPOCH = 978307200; // seconds since 2001-01-01
  const ts = timestamp > 1e15 ? timestamp / 1e9 : timestamp; // after 2020: nanoseconds
  return new Date((ts + CORE_DATA_EPOCH) * 1000);
}

SQL Queries

SELECT c.ROWID as chat_id, c.chat_identifier, c.display_name, c.style,
       MAX(m.date) as last_message_date
FROM chat c
JOIN chat_message_join cmj ON c.ROWID = cmj.chat_id
JOIN message m ON cmj.message_id = m.ROWID
GROUP BY c.ROWID
ORDER BY last_message_date DESC

Write โ€” AppleScript

tell application "Messages"
  send "{text}" to participant "{handle}" of ยฌ
    (first conversation whose id = "{chatId}")
end tell
Anti-detection: No network activity at all. Just local file reads. Requires macOS Full Disk Access permission.
๐Ÿ”„

Normalized Types

Common interfaces that all platforms normalize to. The unified layer.

type Platform = "slack" | "linkedin" | "instagram" | "tinder" | "imessage" | "whatsapp" | "x" | "homi";

interface Conversation {
  platform: Platform;
  conversationId: string;
  conversationType: "dm" | "group_dm" | "channel" | "thread";
  name: string;
  participants: Participant[];
  lastMessage: Message | null;
  lastActivityAt: string;
  unreadCount: number;
  platformMeta?: Record<string, unknown>;
}

interface Message {
  id: string;
  senderId: string;
  senderName?: string;
  text: string;
  timestamp: string;
  type: "text" | "image" | "file" | "other";
  threadId?: string;
  attachments?: Array<{ type: string; url?: string; name?: string }>;
}
๐Ÿ”ง

Contributing

How to update volatile IDs when things break.

1Open the platform in your browser. Open DevTools โ†’ Network tab.
2Navigate to the messaging section. Find the GraphQL request(s).
3Copy the new queryId or doc_id from the request payload.
4Update the constants in packages/platform-schemas/src/ and open a PR.