TypeScript schemas for unofficial messaging platform APIs. Endpoints, auth patterns, volatile IDs, and anti-detection notes.
| Platform | Auth Pattern | API Style | Read | Write | Stability | Detection Risk |
|---|---|---|---|---|---|---|
๐ฌ Slack | xoxc- token + d= cookie | REST (POST forms) | โ | โ | Stable | Low |
๐ผ LinkedIn | JSESSIONID CSRF | GraphQL (Voyager) | โ | โ | Volatile IDs | High |
๐ท Instagram | csrftoken + ig-app-id + fb_dtsg | GraphQL (doc_id) | โ | โ | Volatile IDs | Critical |
๐ฅ Tinder | x-auth-token | REST (JSON) | โ | โ | Stable | Low |
๐ฌ iMessage | Full Disk Access | SQLite + AppleScript | โ | โ | Stable | None |
REST API with session tokens. Your own workspace โ relatively permissive.
// 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]
// 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 }
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 }; }
GraphQL via Voyager API. Volatile query IDs change on every deploy.
// CSRF from JSESSIONID cookie (strip quotes) // document.cookie.match(/JSESSIONID="?([^";]+)/)?.[1] // Header: { "csrf-token": "{csrfToken}", accept: "application/graphql" }
// โ ๏ธ VOLATILE โ these change when LinkedIn deploys export const QUERY_IDS = { conversations: "messengerConversations.0d5e6781bbee71c3e51c8843c6519f48", messages: "messengerMessages.5846eeb71c981f11e0134cb6626cc314", } as const;
// 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" }
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[]; }
Meta's GraphQL with doc_id mutations. The strictest platform โ tread carefully.
// โ ๏ธ 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
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
// 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", }), }
Simple REST JSON API. The easiest platform to work with.
// Simplest auth of all platforms // x-auth-token from localStorage: TinderWeb/APIToken // That's it. One header.
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}" }
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; }
No API, no network. Just SQLite on disk and AppleScript for writes.
// 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); }
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
tell application "Messages" send "{text}" to participant "{handle}" of ยฌ (first conversation whose id = "{chatId}") end tell
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 }>; }
How to update volatile IDs when things break.