# Connect a social account Source: https://docs.social-api.ai/api-reference/accounts/connect-a-social-account /api-reference/openapi.json post /accounts/connect Initiates account connection. For OAuth2 platforms returns an auth_url to redirect the user to. For apikey/credentials platforms stores the credentials immediately and returns account_id. # Disconnect a social account Source: https://docs.social-api.ai/api-reference/accounts/disconnect-a-social-account /api-reference/openapi.json delete /accounts/{id} Soft-deletes a connected account. It will no longer appear in the accounts list and all API calls using this account will return 404. # Exchange OAuth code Source: https://docs.social-api.ai/api-reference/accounts/exchange-oauth-code /api-reference/openapi.json post /oauth/exchange Exchanges an OAuth authorization code for a connected social account. Use this when you handle the OAuth callback on your own server (proxy flow). For a single account the response is a flat ConnectAccountResponse (HTTP 201). When a single OAuth flow connects multiple accounts (e.g. Facebook Pages) the response is OAuthCallbackResponse. # Get a pending Google Profile selection Source: https://docs.social-api.ai/api-reference/accounts/get-a-pending-google-profile-selection /api-reference/openapi.json get /accounts/pending/{connection_id} Returns the candidate Google Business Profiles for a pending connection created when a Google login manages more than one Profile. Choose one with the select endpoint. # Get creator info for publishing Source: https://docs.social-api.ai/api-reference/accounts/get-creator-info-for-publishing /api-reference/openapi.json get /accounts/{id}/creator-info Returns platform-specific creator settings needed before publishing (privacy levels, interaction toggles, posting availability). Currently supported for TikTok only; other platforms return 501. # List account pages Source: https://docs.social-api.ai/api-reference/accounts/list-account-pages /api-reference/openapi.json get /accounts/{id}/pages Returns pages (e.g. Facebook Pages) associated with a connected account. # List connected accounts Source: https://docs.social-api.ai/api-reference/accounts/list-connected-accounts /api-reference/openapi.json get /accounts Returns all active social media accounts connected to the authenticated API key's workspace. # List Pinterest boards Source: https://docs.social-api.ai/api-reference/accounts/list-pinterest-boards /api-reference/openapi.json get /accounts/{id}/boards Returns the boards for a connected Pinterest account, for choosing a publish destination. # Select a Google Business Profile or Facebook Pages to connect Source: https://docs.social-api.ai/api-reference/accounts/select-a-google-business-profile-or-facebook-pages-to-connect /api-reference/openapi.json post /accounts/pending/{connection_id}/select Promotes a pending connection into a connected account. For Google, pass platform_account_id to choose exactly one candidate Profile. For Facebook, pass page_ids with the subset of candidate Pages to assign to the brand. First selection wins; the pending connection is then consumed. # Update account page Source: https://docs.social-api.ai/api-reference/accounts/update-account-page /api-reference/openapi.json patch /accounts/{id}/pages/{pageId} Update a page's is_default or is_active status. # List active announcements Source: https://docs.social-api.ai/api-reference/announcements/list-active-announcements /api-reference/openapi.json get /announcements Returns service announcements that are currently visible, newest first. Each item carries a `seen` flag for the calling user. `window_start` and `window_end` describe the announced maintenance window and are absent for non-maintenance announcements. Free operation, consumes no credits. # Mark an announcement as seen Source: https://docs.social-api.ai/api-reference/announcements/mark-an-announcement-as-seen /api-reference/openapi.json post /announcements/{id}/seen Records that the calling user has seen an announcement, so it is not shown again. Idempotent: repeat calls succeed without changing anything. Free operation, consumes no credits. # Create API key Source: https://docs.social-api.ai/api-reference/api-keys/create-api-key /api-reference/openapi.json post /keys Generates a new API key for the authenticated user. Optionally restrict it to a set of scopes (see GET /keys/scopes) and/or brands. Empty scopes or brand_ids mean unrestricted. # List API key scopes Source: https://docs.social-api.ai/api-reference/api-keys/list-api-key-scopes /api-reference/openapi.json get /keys/scopes Returns the catalog of scopes assignable to restricted API keys. # List API keys Source: https://docs.social-api.ai/api-reference/api-keys/list-api-keys /api-reference/openapi.json get /keys Returns all API keys for the authenticated user, including each key's scope and brand restrictions. # Revoke API key Source: https://docs.social-api.ai/api-reference/api-keys/revoke-api-key /api-reference/openapi.json delete /keys/{id} Deactivates an API key. This is irreversible. # Rotate API key Source: https://docs.social-api.ai/api-reference/api-keys/rotate-api-key /api-reference/openapi.json post /keys/{id}/rotate Issues a new secret for an existing key, keeping its id, name, scopes, and brand restrictions. The old key value stops working immediately. The new raw key is shown once. Requires a full-access key or the dashboard. # Update API key Source: https://docs.social-api.ai/api-reference/api-keys/update-api-key /api-reference/openapi.json patch /keys/{id} Edits an existing key's name, scopes, and brand restrictions in place. The key value is unchanged (no new secret is issued). Empty scopes or brand_ids mean unrestricted. Requires a full-access key or the dashboard. # Create a brand Source: https://docs.social-api.ai/api-reference/brands/create-a-brand /api-reference/openapi.json post /brands Creates a new brand for grouping connected social accounts. Each brand can have one account per platform. # Delete a brand Source: https://docs.social-api.ai/api-reference/brands/delete-a-brand /api-reference/openapi.json delete /brands/{id} Deletes a brand and deactivates all its connected accounts (sets is_active=false). # List brands Source: https://docs.social-api.ai/api-reference/brands/list-brands /api-reference/openapi.json get /brands Returns all brands for the authenticated user with their connected account counts. # Update a brand Source: https://docs.social-api.ai/api-reference/brands/update-a-brand /api-reference/openapi.json patch /brands/{id} Renames a brand. # Event summary counts Source: https://docs.social-api.ai/api-reference/events/event-summary-counts /api-reference/openapi.json get /events/summary Returns aggregated event counts for the requested window: totals, failure counts, breakdowns by category and error category, plus inbox-specific breakdown. Useful for dashboard summary cards. # List developer events Source: https://docs.social-api.ai/api-reference/events/list-developer-events /api-reference/openapi.json get /events Returns a paginated, filtered slice of the unified event log. Events cover post lifecycle, inbox activity, account changes, and webhook deliveries. Retention varies by plan tier; the response includes the user's current retention window. # Create analytics export Source: https://docs.social-api.ai/api-reference/exports/create-analytics-export /api-reference/openapi.json post /accounts/{id}/export Enqueue an async analytics export job for a connected account. Subject to plan limits (exports per month, cooldown hours, concurrent export cap). # Get export status and result Source: https://docs.social-api.ai/api-reference/exports/get-export-status-and-result /api-reference/openapi.json get /exports/{id} Returns the current status, progress, result download URL (presigned Excel), and report URL for an export job. # Get export videos Source: https://docs.social-api.ai/api-reference/exports/get-export-videos /api-reference/openapi.json get /exports/{id}/videos Returns the cached video data for a completed export, including metrics and optional AI-enriched fields (transcript, vision summary). # List analytics exports Source: https://docs.social-api.ai/api-reference/exports/list-analytics-exports /api-reference/openapi.json get /exports Returns the 50 most recent export jobs for the authenticated user. Optionally filter by status. # Send feedback Source: https://docs.social-api.ai/api-reference/feedback/send-feedback /api-reference/openapi.json post /feedback Submit feedback (bug report, feature request, or general comment). Stored for the team and forwarded to their notification channel. # Delete a comment Source: https://docs.social-api.ai/api-reference/inbox-comments/delete-a-comment /api-reference/openapi.json delete /inbox/comments/{postId}/{commentId} Deletes a comment on the platform and removes it from the inbox. Not all platforms support comment deletion - check the can_delete capability flag. # Hide a comment Source: https://docs.social-api.ai/api-reference/inbox-comments/hide-a-comment /api-reference/openapi.json post /inbox/comments/{postId}/{commentId}/hide Hides a comment on the platform so it is not visible to the public, and marks it hidden in the inbox. Supported on Instagram, Facebook, Threads, and YouTube. Check the can_hide capability flag. # Like a comment Source: https://docs.social-api.ai/api-reference/inbox-comments/like-a-comment /api-reference/openapi.json post /inbox/comments/{postId}/{commentId}/like Likes a comment on the platform as the connected account. Supported on Facebook and LinkedIn Page only (the Instagram API does not expose comment likes). Check the can_like capability flag. # List commented posts Source: https://docs.social-api.ai/api-reference/inbox-comments/list-commented-posts /api-reference/openapi.json get /inbox/comments Returns inbox posts that have comments, ordered by most-recently-commented. Supports filtering by account, platform, minimum comment count, and date range. Uses cursor-based pagination. YouTube has no webhooks, so YouTube commented posts are served live from the platform and only appear when the request is scoped with account_id (they are absent from the unfiltered aggregate); the live YouTube list returns a single page (no cursor). # List comments on a post Source: https://docs.social-api.ai/api-reference/inbox-comments/list-comments-on-a-post /api-reference/openapi.json get /inbox/comments/{postId} Returns comments for a specific inbox post, including per-platform capability flags (can_reply, can_delete, can_hide, can_like, can_private_reply). Comments are fetched live from the platform on every request. Uses cursor-based pagination. # List replies to a comment Source: https://docs.social-api.ai/api-reference/inbox-comments/list-replies-to-a-comment /api-reference/openapi.json get /inbox/comments/{postId}/{commentId}/replies Returns paginated replies to a specific comment. Same response shape as top-level comments. Uses cursor-based pagination. # Reply to a comment Source: https://docs.social-api.ai/api-reference/inbox-comments/reply-to-a-comment /api-reference/openapi.json post /inbox/comments/{postId} Posts a reply on the platform. If comment_id is provided, replies to that specific comment (threaded reply); otherwise replies to the post itself (top-level comment). Consumes 1 interaction credit. # Send a private reply to a commenter Source: https://docs.social-api.ai/api-reference/inbox-comments/send-a-private-reply-to-a-commenter /api-reference/openapi.json post /inbox/comments/{postId}/{commentId}/private-reply Sends a direct message to the author of a comment. Only supported on Instagram and Facebook. Meta allows one private reply per comment, within 7 days of the comment; a second attempt or an expired window returns HTTP 400 with error code platform.instagram.private_reply_ineligible or platform.facebook.private_reply_ineligible. On Facebook the commenter's reply continues as a normal Messenger conversation; on Instagram the private reply does not open a standard 24-hour messaging window. Check the can_private_reply capability flag. # Unhide a comment Source: https://docs.social-api.ai/api-reference/inbox-comments/unhide-a-comment /api-reference/openapi.json delete /inbox/comments/{postId}/{commentId}/hide Restores a previously hidden comment so it is publicly visible again. Supported on Instagram, Facebook, Threads, and YouTube. # Unlike a comment Source: https://docs.social-api.ai/api-reference/inbox-comments/unlike-a-comment /api-reference/openapi.json delete /inbox/comments/{postId}/{commentId}/like Removes a like from a comment on the platform. Supported on Facebook and LinkedIn Page only (the Instagram API does not expose comment likes). # List Accounts with reviews Source: https://docs.social-api.ai/api-reference/inbox-reviews/list-accounts-with-reviews /api-reference/openapi.json get /inbox/reviews Lists your review-capable connected Accounts, each with its live review summary (average rating, total count, last review time). Use this to build the reviews inbox navigation, then GET /inbox/reviews/{account_id} for one Account's reviews. # List reviews for an Account Source: https://docs.social-api.ai/api-reference/inbox-reviews/list-reviews-for-an-account /api-reference/openapi.json get /inbox/reviews/{account_id} Returns one page of reviews for a single connected Account, newest first by default. Reviews are proxied live from the platform and not stored. Currently only Google Business Profile returns reviews. # Reply to a review Source: https://docs.social-api.ai/api-reference/inbox-reviews/reply-to-a-review /api-reference/openapi.json post /inbox/reviews/{id}/reply Posts a public reply to a review on the platform (e.g. Google Business Profile). The reply is visible to anyone who views the review. Consumes 1 interaction credit. # Get a conversation Source: https://docs.social-api.ai/api-reference/inbox/get-a-conversation /api-reference/openapi.json get /inbox/conversations/{id} Returns a single inbox conversation by ID. # List inbox conversations Source: https://docs.social-api.ai/api-reference/inbox/list-inbox-conversations /api-reference/openapi.json get /inbox/conversations Returns paginated DM conversations for the authenticated user, optionally filtered by account, platform, and status. # List messages in a conversation Source: https://docs.social-api.ai/api-reference/inbox/list-messages-in-a-conversation /api-reference/openapi.json get /inbox/conversations/{id}/messages Returns paginated messages for an inbox conversation, newest first. # Mark conversation as read Source: https://docs.social-api.ai/api-reference/inbox/mark-conversation-as-read /api-reference/openapi.json post /inbox/conversations/{id}/read Resets the unread count on a conversation to zero. # Send a message in a conversation Source: https://docs.social-api.ai/api-reference/inbox/send-a-message-in-a-conversation /api-reference/openapi.json post /inbox/conversations/{id}/messages Sends an outgoing DM via the connected platform account and records it in the conversation. # Update conversation status Source: https://docs.social-api.ai/api-reference/inbox/update-conversation-status /api-reference/openapi.json patch /inbox/conversations/{id} Sets the status of a conversation to active or archived. # Get Instagram account insights summary (beta) Source: https://docs.social-api.ai/api-reference/instagram/get-instagram-account-insights-summary-beta /api-reference/openapi.json get /platforms/instagram/accounts/{id}/summary Returns live profile counts plus trailing-30-day insight totals (reach, views, interactions, engaged accounts, net follower change) for a connected Instagram account. Beta testers only; the totals require the instagram_business_manage_insights permission granted at connect. # List active Instagram stories Source: https://docs.social-api.ai/api-reference/instagram/list-active-instagram-stories /api-reference/openapi.json get /platforms/instagram/accounts/{id}/stories Returns the currently-active stories for a connected Instagram account. Stories are ephemeral and expire about 24 hours after posting; each item carries is_story and a computed expires_at in metadata. Returns 501 for non-Instagram accounts. # Create an invite link Source: https://docs.social-api.ai/api-reference/invites/create-an-invite-link /api-reference/openapi.json post /invites Creates a single-use invite link for an external user to connect a social account to a brand. The brand must not already have a connected account for the specified platform, and no active (unused, unexpired) invite may exist for the same brand+platform combination. # List invites for a brand Source: https://docs.social-api.ai/api-reference/invites/list-invites-for-a-brand /api-reference/openapi.json get /invites Returns all invites (active and used/expired) for a brand, ordered by created_at DESC. # Redeem an invite link Source: https://docs.social-api.ai/api-reference/invites/redeem-an-invite-link /api-reference/openapi.json get /invite/{token} Public endpoint (no auth). Validates the invite token and redirects the user to the platform's OAuth authorization page. On successful OAuth callback, the account is connected to the invite creator's brand. # Revoke an invite Source: https://docs.social-api.ai/api-reference/invites/revoke-an-invite /api-reference/openapi.json delete /invites/{id} Marks an invite as used (revoked), preventing it from being redeemed. # Get LinkedIn page insights Source: https://docs.social-api.ai/api-reference/linkedin/get-linkedin-page-insights /api-reference/openapi.json get /platforms/linkedin/insights # Delete a media file Source: https://docs.social-api.ai/api-reference/media/delete-a-media-file /api-reference/openapi.json delete /media/{id} # Get a presigned media upload URL Source: https://docs.social-api.ai/api-reference/media/get-a-presigned-media-upload-url /api-reference/openapi.json get /media/upload-url Returns a presigned S3 PUT URL for client-side media upload. Upload the file directly via HTTP PUT to the returned URL, then call POST /v1/media/:id/verify to confirm. # Get storage usage Source: https://docs.social-api.ai/api-reference/media/get-storage-usage /api-reference/openapi.json get /media/storage Returns total bytes used, the plan limit, and file count for the authenticated user. # List media files Source: https://docs.social-api.ai/api-reference/media/list-media-files /api-reference/openapi.json get /media Returns a paginated list of uploaded media files for the authenticated user. # Upload media (server-side) Source: https://docs.social-api.ai/api-reference/media/upload-media-server-side /api-reference/openapi.json post /media/upload Upload a media file directly through the API server. # Verify a media upload Source: https://docs.social-api.ai/api-reference/media/verify-a-media-upload /api-reference/openapi.json post /media/{id}/verify Confirms that a presigned-URL upload completed successfully and marks the media as ready. # List brand mentions Source: https://docs.social-api.ai/api-reference/mentions/list-brand-mentions /api-reference/openapi.json get /accounts/{id}/mentions Fetches posts and stories where your brand account was mentioned or tagged. Results are sorted newest-first. Returns 501 for platforms that do not support mention tracking. # Reply to a mention Source: https://docs.social-api.ai/api-reference/mentions/reply-to-a-mention /api-reference/openapi.json post /accounts/{id}/mentions/reply Replies, as a public comment, to a mention of the connected account, including on a third-party post where the normal comment-reply endpoint is not permitted. Provide media_id (the media/post the mention is on); set comment_id only when the mention was in a comment (omit it to reply to a mention in the post caption). Supported on Instagram and Facebook. Consumes 1 interaction credit. # Assign a Facebook Page to a brand Source: https://docs.social-api.ai/api-reference/platforms/assign-a-facebook-page-to-a-brand /api-reference/openapi.json post /platforms/facebook/logins/{loginId}/pages/{pageId}/assign Assigns one unassigned Page from this login to the given brand. Each Page belongs to exactly one brand. Each brand can connect up to 5 Facebook Pages, a hard cap. Assigning beyond that limit is refused. # List a Facebook login's Pages Source: https://docs.social-api.ai/api-reference/platforms/list-a-facebook-logins-pages /api-reference/openapi.json get /platforms/facebook/logins/{loginId}/pages Lists every Page the login's stored credential can manage, live from Meta, merged with assignment state. Pages granted after the last OAuth exchange appear here without re-authenticating. assignable reflects tenancy only; the 5-page-per-brand cap is enforced when assigning. # Bulk import posts from CSV Source: https://docs.social-api.ai/api-reference/posts/bulk-import-posts-from-csv /api-reference/openapi.json post /posts/import Upload a CSV file to create multiple posts. Use dry_run=true to validate without creating. # Create or schedule a post Source: https://docs.social-api.ai/api-reference/posts/create-or-schedule-a-post /api-reference/openapi.json post /posts Creates a post for one or more connected accounts. Set `publish_now: true` to publish immediately: the post is accepted with status `publishing` and delivery runs in the background (so slow reel/video uploads do not block the request). The terminal status (`published`, `partial`, or `failed`) arrives via the `post.published`/`post.partial`/ `post.failed` webhooks and by polling GET /posts/{pid}. Set `scheduled_at` to queue the post for future publication. With neither flag the post is saved as a draft (this is the default, even when targets are provided); publish it later with POST /posts/{pid}/publish. Consumes 1 post credit when the post is published or scheduled; saving a draft is free. # Delete or cancel a post Source: https://docs.social-api.ai/api-reference/posts/delete-or-cancel-a-post /api-reference/openapi.json delete /posts/{pid} Deletes a draft/scheduled/failed post locally, or deletes a published post from every platform that supports it. Platforms that do not support deletion (or fail) keep their copy live and the post is kept (shrunk) rather than removed. Returns a per-platform result. # Get a single post Source: https://docs.social-api.ai/api-reference/posts/get-a-single-post /api-reference/openapi.json get /posts/{pid} Fetch a post by its ID with all platform delivery details. # Get platform constraints Source: https://docs.social-api.ai/api-reference/posts/get-platform-constraints /api-reference/openapi.json get /posts/validate Returns media and text constraints for all supported platforms. Cacheable. # Get post metrics Source: https://docs.social-api.ai/api-reference/posts/get-post-metrics /api-reference/openapi.json get /posts/{pid}/metrics Fetches live engagement metrics from platform APIs for a published post. # List all posts Source: https://docs.social-api.ai/api-reference/posts/list-all-posts /api-reference/openapi.json get /posts Returns posts with rich filtering and cursor pagination. Supports filtering by status, platform, account_ids, date range, and text search. # Publish a draft post now Source: https://docs.social-api.ai/api-reference/posts/publish-a-draft-post-now /api-reference/openapi.json post /posts/{pid}/publish Publishes a post in `draft` or `scheduled` status immediately. The post is accepted with status `publishing` and delivery runs in the background (so slow reel/video uploads do not block the request); the terminal status arrives via the `post.published`/ `post.partial`/`post.failed` webhooks and by polling GET /posts/{pid}. Use this to publish a post that was saved as a draft, or to publish a scheduled post ahead of its time. Consumes 1 post credit. # Retry a failed post Source: https://docs.social-api.ai/api-reference/posts/retry-a-failed-post /api-reference/openapi.json post /posts/{pid}/retry Re-attempts publication of a post in `failed` or `partial` status. Consumes 1 post credit. # Unpublish a post Source: https://docs.social-api.ai/api-reference/posts/unpublish-a-post /api-reference/openapi.json post /posts/{pid}/unpublish Unpublishes a published post from a specific account (if account_id provided) or all accounts. # Update a post Source: https://docs.social-api.ai/api-reference/posts/update-a-post /api-reference/openapi.json patch /posts/{pid} Updates the text, media, schedule time, targets, or hidden status of a pending post. Only posts in `draft`, `scheduled`, or `failed` status can be updated. # Validate post content Source: https://docs.social-api.ai/api-reference/posts/validate-post-content /api-reference/openapi.json post /posts/validate Dry-run validation. Returns errors for hard limit violations and warnings for best-practice issues. # Get TikTok account summary Source: https://docs.social-api.ai/api-reference/tiktok/get-tiktok-account-summary /api-reference/openapi.json get /platforms/tiktok/accounts/{id}/summary # Re-sync a TikTok account's videos Source: https://docs.social-api.ai/api-reference/tiktok/re-sync-a-tiktok-accounts-videos /api-reference/openapi.json post /platforms/tiktok/accounts/{id}/sync # Get platform usage limits Source: https://docs.social-api.ai/api-reference/usage/get-platform-usage-limits /api-reference/openapi.json get /accounts/{id}/limits Returns platform-specific quota data for a connected account as a key/value map where values are remaining counts. Keys are platform-defined - for example Instagram returns `posts_remaining`. An empty object is returned for platforms without usage limits. # Get resource usage for the current billing period Source: https://docs.social-api.ai/api-reference/usage/get-resource-usage-for-the-current-billing-period /api-reference/openapi.json get /usage Returns brands, posts, and interactions consumed during the current billing period alongside the plan limits. -1 means unlimited (all paid plans). Period boundaries come from Stripe subscription or rolling 30-day windows for free users. # Delete user account Source: https://docs.social-api.ai/api-reference/users/delete-user-account /api-reference/openapi.json delete /users/me Permanently deletes the authenticated user account and all associated data (API keys, connected accounts, usage logs). This action is irreversible. # Get current user Source: https://docs.social-api.ai/api-reference/users/get-current-user /api-reference/openapi.json get /users/me Returns the authenticated user's profile (id, email, plan tier, onboarding flag, beta_tester flag, allowed_platforms list). # Update current user Source: https://docs.social-api.ai/api-reference/users/update-current-user /api-reference/openapi.json patch /users/me Updates the authenticated user's profile. Supports setting onboarding=false to mark onboarding as complete, and optionally storing use_case and referral for onboarding analytics. # Create webhook endpoint Source: https://docs.social-api.ai/api-reference/webhooks/create-webhook-endpoint /api-reference/openapi.json post /webhooks Registers a new webhook endpoint and performs a live reachability ping. If the endpoint is unreachable or returns an unexpected response, the request fails with 400 (webhook_verification_failed). The generated secret is returned once - store it securely. # Delete webhook endpoint Source: https://docs.social-api.ai/api-reference/webhooks/delete-webhook-endpoint /api-reference/openapi.json delete /webhooks/{id} Removes a webhook endpoint. Future events will not be delivered to this URL. # Get webhook delivery Source: https://docs.social-api.ai/api-reference/webhooks/get-webhook-delivery /api-reference/openapi.json get /webhooks/{id}/deliveries/{did} Returns the full record of a single webhook delivery attempt, including request payload, response status, response body excerpt, and timing. # Get webhook endpoint Source: https://docs.social-api.ai/api-reference/webhooks/get-webhook-endpoint /api-reference/openapi.json get /webhooks/{id} Returns the configuration and aggregate delivery stats for a single webhook endpoint owned by the caller. # List available webhook event types Source: https://docs.social-api.ai/api-reference/webhooks/list-available-webhook-event-types /api-reference/openapi.json get /webhooks/events Returns the catalog of webhook event types you can subscribe to, grouped by category (posts, inbox, accounts). # List webhook deliveries Source: https://docs.social-api.ai/api-reference/webhooks/list-webhook-deliveries /api-reference/openapi.json get /webhooks/{id}/deliveries Returns recent delivery attempts for a webhook endpoint. Optionally filter by status (success, failed, pending) or event_type. Cursor-paginated. # List webhook endpoints Source: https://docs.social-api.ai/api-reference/webhooks/list-webhook-endpoints /api-reference/openapi.json get /webhooks Returns all webhook endpoints for the authenticated user. # Retry webhook delivery Source: https://docs.social-api.ai/api-reference/webhooks/retry-webhook-delivery /api-reference/openapi.json post /webhooks/{id}/deliveries/{did}/retry Requeues a failed webhook delivery for another attempt. Returns the updated delivery record. # Send test webhook delivery Source: https://docs.social-api.ai/api-reference/webhooks/send-test-webhook-delivery /api-reference/openapi.json post /webhooks/{id}/test Sends a synthetic event payload to the webhook endpoint to validate signature, reachability, and consumer handling. Returns the delivery result. # Update webhook endpoint Source: https://docs.social-api.ai/api-reference/webhooks/update-webhook-endpoint /api-reference/openapi.json patch /webhooks/{id} Partially updates a webhook endpoint. All fields are optional; at least one must be provided. # Create a message template Source: https://docs.social-api.ai/api-reference/whatsapp/create-a-message-template /api-reference/openapi.json post /platforms/whatsapp/accounts/{id}/templates Submits a new message template to the WhatsApp Business Account (WABA) for review. The request body is the raw WhatsApp Cloud API template payload (name, category, language, components). New templates start in PENDING status; Meta reviews them and the status transitions to APPROVED or REJECTED asynchronously. # Delete a message template Source: https://docs.social-api.ai/api-reference/whatsapp/delete-a-message-template /api-reference/openapi.json delete /platforms/whatsapp/accounts/{id}/templates Deletes a template by name from the WhatsApp Business Account (WABA) associated with this connected account. WhatsApp deletes all language variants that share the given name. Templates cannot be deleted while they are being used in active campaigns. # Deregister a phone number from WhatsApp Business Source: https://docs.social-api.ai/api-reference/whatsapp/deregister-a-phone-number-from-whatsapp-business /api-reference/openapi.json post /platforms/whatsapp/phone-numbers/{id}/deregister Releases the phone number from WhatsApp Business, freeing it for use with another Business Solution Provider (BSP). This does not delete the connected account on SocialAPI; the account remains until it is disconnected explicitly. # Edit a message template Source: https://docs.social-api.ai/api-reference/whatsapp/edit-a-message-template /api-reference/openapi.json put /platforms/whatsapp/templates/{id} Updates the components of an existing message template by its WhatsApp template ID. Only APPROVED templates can be edited, and editing resets the template to PENDING for re-review. APPROVED templates are limited to one edit per day. # Get a message template Source: https://docs.social-api.ai/api-reference/whatsapp/get-a-message-template /api-reference/openapi.json get /platforms/whatsapp/templates/{id} Returns a single message template by its WhatsApp template ID. Any active WhatsApp account on the caller's workspace is used to authorize the lookup; the template itself is scoped by its platform ID. # Get a WhatsApp Business Profile Source: https://docs.social-api.ai/api-reference/whatsapp/get-a-whatsapp-business-profile /api-reference/openapi.json get /platforms/whatsapp/phone-numbers/{id}/business-profile Returns the public business profile for a WhatsApp phone number, including about text, address, description, contact email, websites, and vertical. The profile is visible to customers in the WhatsApp chat header. # Get phone number registration status Source: https://docs.social-api.ai/api-reference/whatsapp/get-phone-number-registration-status /api-reference/openapi.json get /platforms/whatsapp/phone-numbers/{id}/status Returns whether a phone number is verified and registered with WhatsApp Business. Used by client UIs to decide which step of the registration flow to show. # List message templates Source: https://docs.social-api.ai/api-reference/whatsapp/list-message-templates /api-reference/openapi.json get /platforms/whatsapp/accounts/{id}/templates Returns all message templates configured on the WhatsApp Business Account (WABA) associated with this connected account. Templates include approved, pending, rejected, and paused entries. Use the after cursor from paging.cursors to fetch subsequent pages. # Register a phone number with WhatsApp Business Source: https://docs.social-api.ai/api-reference/whatsapp/register-a-phone-number-with-whatsapp-business /api-reference/openapi.json post /platforms/whatsapp/phone-numbers/{id}/register Registers a previously verified phone number with WhatsApp Business and sets the 6-digit two-step verification PIN. The phone number must be verified via verify-code first. The PIN must be retained by the caller: losing it triggers a mandatory 7-day wait before it can be reset. # Request a phone number verification code Source: https://docs.social-api.ai/api-reference/whatsapp/request-a-phone-number-verification-code /api-reference/openapi.json post /platforms/whatsapp/phone-numbers/{id}/request-code Asks WhatsApp to send a one-time verification code to the phone number via SMS or voice call. The code is delivered to the underlying phone line, not to the API caller; follow up with verify-code once the user receives it. Step 1 of the phone-number registration flow (request-code, verify-code, register). # Send a template message Source: https://docs.social-api.ai/api-reference/whatsapp/send-a-template-message /api-reference/openapi.json post /platforms/whatsapp/accounts/{id}/send-template Sends an approved WhatsApp template message to a recipient phone number. Template messages can be sent outside the 24-hour customer service window. The template must already be APPROVED on the WABA, and the language code must match an approved variant. Component parameter order must match the template's placeholders. # Update a WhatsApp Business Profile Source: https://docs.social-api.ai/api-reference/whatsapp/update-a-whatsapp-business-profile /api-reference/openapi.json put /platforms/whatsapp/phone-numbers/{id}/business-profile Updates one or more fields of the public business profile (about, address, description, email, websites, vertical, profile_picture_handle). Only provided fields are changed. The profile picture itself is changed by passing a profile_picture_handle obtained from the Resumable Upload API; profile_picture_url is read-only. # Validate a message template Source: https://docs.social-api.ai/api-reference/whatsapp/validate-a-message-template /api-reference/openapi.json post /platforms/whatsapp/templates/validate Dry-run validation of a WhatsApp message template against the Cloud API rules (name format, component cardinality, header format, text limits, placeholder sequencing, button constraints). Returns errors for hard violations that WhatsApp would reject and warnings for best-practice issues. Performs no network call and does not submit the template. # Verify a phone number with a code Source: https://docs.social-api.ai/api-reference/whatsapp/verify-a-phone-number-with-a-code /api-reference/openapi.json post /platforms/whatsapp/phone-numbers/{id}/verify-code Submits the 6-digit code received via SMS or voice to prove ownership of the phone number. Once verified, the phone number can be registered with WhatsApp Business. Codes expire quickly; if verification fails, request a new code. # Edit a published YouTube video Source: https://docs.social-api.ai/api-reference/youtube/edit-a-published-youtube-video /api-reference/openapi.json patch /platforms/youtube/accounts/{id}/videos/{videoId} # Edit a YouTube comment Source: https://docs.social-api.ai/api-reference/youtube/edit-a-youtube-comment /api-reference/openapi.json patch /platforms/youtube/comments/{commentId} Edits the text of one of the connected account's own YouTube comments via comments.update. Only the comment author (the connected account) can edit. YouTube only. # Get YouTube channel summary Source: https://docs.social-api.ai/api-reference/youtube/get-youtube-channel-summary /api-reference/openapi.json get /platforms/youtube/accounts/{id}/summary # Moderate a YouTube comment Source: https://docs.social-api.ai/api-reference/youtube/moderate-a-youtube-comment /api-reference/openapi.json post /platforms/youtube/comments/{commentId}/moderate Sets the moderation status of a YouTube comment (published, held_for_review, or rejected), optionally banning the author. ban_author is only valid with status=rejected. YouTube only. # Re-sync a YouTube account's videos Source: https://docs.social-api.ai/api-reference/youtube/re-sync-a-youtube-accounts-videos /api-reference/openapi.json post /platforms/youtube/accounts/{id}/sync # Facebook Source: https://docs.social-api.ai/connectors/facebook Connect and interact with Facebook Pages via the Meta Graph API. Available **Managed connector** - [SocialAPI](https://social-api.ai) handles the Meta App and platform app review for you. No customer-side Meta for Developers registration required. See [Platform credentials](/guides/platform-credentials). Facebook Pages are fully supported. The connector uses the Meta Graph API for posts, comments, DMs (Messenger), and mentions. ## At a glance | Field | Value | | ------------- | -------------------------------------------------------- | | Platform slug | `facebook` | | Auth type | OAuth 2.0 (Meta) | | API | Meta Graph API | | Best for | Page-level engagement: post and reply on Facebook Pages. | ## Capabilities | Feature | Supported | Notes | | ------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | List posts | Yes | Page feed posts | | Comments | Yes | Scoped to a specific post | | Reply to comment | Yes | | | Private reply | Yes | Sends a one-time DM to the commenter | | Comment replies (thread) | Yes | Fetch the reply thread under a comment | | Moderate comment (hide/unhide/delete) | Yes | | | Toggle post comments | No | Not supported by Facebook Graph API | | DMs | Yes | Via Messenger (24-hour messaging window) | | Send DM | Yes | Text and/or media attachment (`attachment_url`) | | Send DM attachment | Yes | Image, video, audio, or file via `attachment_url` (type inferred from URL extension, max 25 MB), or by `attachment_id` (requires `attachment_type`) | | Get DM thread by user | Yes | | | Reviews | No | Deprecated in Graph API v22.0 | | Mentions | Yes | Posts where the Page is tagged | | Reply to mention | Yes | Posts a public comment reply on a mention (post or comment mention) | | Like/unlike comment | Yes | Page can like or unlike a comment | | Create post | Yes | Text, photos, links, videos, Reels (set `content_type: reel` in platform\_data) | | Delete post | Yes | | ## Connecting ```bash theme={null} curl -X POST https://api.social-api.ai/v1/accounts/connect \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "platform": "facebook", "redirect_uri": "https://app.example.com/oauth/callback", "state": "session_abc123" }' ``` Response: ```json theme={null} { "auth_url": "https://www.facebook.com/v25.0/dialog/oauth?client_id=...&state=...", "state": "4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2" } ``` Redirect your user to `auth_url`. After they authorize, SocialAPI exchanges the code with Facebook, but the result is never an immediate `status=success`: every Facebook connect returns a pending connection that asks you to choose which Pages to assign. See [OAuth flows: Facebook, choosing which Pages to connect](/guides/oauth#facebook-choosing-which-pages-to-connect) for the full `login_id` / `pages[]` / `lost_access[]` shape and the `POST /v1/accounts/pending/{connection_id}/select` call that completes it. ### One login, many brands A Facebook login is a shared credential, not a one-to-one link to a brand. The same login can serve many brands at once: each Page you have access to belongs to exactly one brand, but the login behind it, and the OAuth grant that keeps it alive, is shared across all of them. This has two practical consequences: * **Assigning Pages is explicit.** Connecting a login does not attach every Page it can see to the brand you connected from. Each Page is assigned to a brand only when you select it, either during the connect flow's pending-selection step or afterward through the endpoints below. * **Re-authenticating refreshes everyone.** When you or a teammate re-runs the OAuth flow for a login that already backs one or more brands, the refreshed token and Page grant apply to every brand using that login, not just the one you happened to reconnect from. ### Managing Pages after connecting Pages don't have to be assigned at connect time. List a login's current Pages, live from Meta, merged with which brand (if any) already owns each one: ```bash theme={null} curl "https://api.social-api.ai/v1/platforms/facebook/logins/{loginId}/pages" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ```json theme={null} { "login_id": "cred_01J8X3Q4R5M6N7P8V2K0W1J", "pages": [ { "platform_page_id": "17841400000000000", "name": "Acme Bakery", "assigned_brand_id": "", "assignable": true } ], "count": 1 } ``` Assign one unassigned Page to a brand: ```bash theme={null} curl -X POST "https://api.social-api.ai/v1/platforms/facebook/logins/{loginId}/pages/{pageId}/assign" \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"brand_id": "b_01HZ9X3Q4R5M6N7P8V2K0W1J"}' ``` A Page already assigned to one of your brands cannot be assigned to a different one while it stays connected there. Trying to connect the same login into a brand whose Facebook account is already tied to a different login returns `409`: ```json theme={null} { "error": { "code": "account.different_login", "message": "This brand's Facebook is connected through a different login. Disconnect it first." } } ``` Disconnect the brand's existing Facebook account first if you intend to move it to a different login. Each brand can connect up to 5 Facebook Pages. This is a hard cap, the same on every plan, not a billed allowance: there is no per-page charge for staying under it, and no way to go over it. Assigning a 6th Page to a brand returns `403 platform.facebook.page_cap`: ```json theme={null} { "error": { "code": "platform.facebook.page_cap", "message": "Each brand can connect up to 5 Facebook Pages. Disconnect one or connect the rest to another brand." } } ``` ## Reading samples ### List posts ```bash theme={null} curl "https://api.social-api.ai/v1/accounts/{id}/posts?limit=10" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Sample response: ```json theme={null} { "data": [ { "id": "123456789_987654321", "platform": "facebook", "caption": "Check out our new product!", "media_type": "status", "permalink": "https://www.facebook.com/123456789/posts/987654321", "timestamp": "2026-03-01T12:00:00Z", "like_count": 0, "comments_count": 0, "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" } ], "count": 1 } ``` ### List comments on a post ```bash theme={null} curl "https://api.social-api.ai/v1/accounts/{id}/posts/123456789_987654321/comments?limit=10" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Sample response: ```json theme={null} { "data": [ { "id": "sapi_cmt_...", "platform": "facebook", "type": "comment", "author": { "id": "111222333", "name": "Jane Smith" }, "content": { "text": "Great post!" }, "metadata": { "post_id": "123456789_987654321", "hidden": false, "like_count": 2, "has_replies": true }, "created_at": "2026-03-01T13:00:00Z", "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform_id": "987654321_111222333" } ], "count": 1 } ``` ### List DMs ```bash theme={null} curl "https://api.social-api.ai/v1/accounts/{id}/dms?limit=10" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ## Publishing Create a Facebook Page post with optional platform-specific fields: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Check out our new product!", "targets": [ { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform_data": { "link": "https://example.com/product" } } ] }' ``` For the full field reference, media constraints, and error recovery, see [Facebook posts](/posts/facebook). ## Limitations and gotchas * **Shared login, one Page per brand**: a single OAuth flow surfaces every Page the login can manage, but each Page belongs to exactly one brand's `account_id`, assigned explicitly during the pending-connection step or later. See [One login, many brands](#one-login-many-brands). * **Page tokens never expire**: unlike Instagram, Facebook Page tokens derived from long-lived user tokens do not expire. No reconnection needed unless the user revokes access. * **Private replies**: replying to a comment with `"private": true` sends a one-time DM to the commenter. Can only be sent once per comment, within 7 days of the comment. * **24-hour messaging window**: Pages can send any message within 24 hours of the user's last message. After that, only approved message tags are allowed. If you get an error sending a DM, the window may have closed. * **Reviews deprecated**: Facebook deprecated Page Recommendations/Reviews in Graph API v22.0 (January 2025). The API returns `501` for review endpoints. ## Permissions [SocialAPI](https://social-api.ai)'s managed Meta App already requests these scopes on your behalf: `pages_show_list`, `pages_read_engagement`, `pages_read_user_content`, `pages_manage_engagement`, `pages_manage_metadata`, `pages_messaging`, `pages_manage_posts`, `public_profile`, `business_management`. You don't need to apply for any platform app review. See [Platform credentials](/guides/platform-credentials). ## Related * [Platform credentials](/guides/platform-credentials) * [OAuth flows](/guides/oauth) * [Publishing to Facebook](/posts/facebook) * [Errors](/guides/errors) * [Interaction IDs](/guides/interaction-ids) # Google Business Profile Source: https://docs.social-api.ai/connectors/google Connect and interact with Google Business Profile locations via the Google Business Profile API. Available [SocialAPI](https://social-api.ai) handles the Google Cloud OAuth client and Google Business Profile API access for you. No customer-side Google Cloud setup is required. See [Platform credentials](/guides/platform-credentials). ## At a glance | Field | Value | | ------------- | ---------------------------------------------------------------------------- | | Platform slug | `google` | | Auth type | OAuth 2.0 (Google) | | API | Google Business Profile API | | Best for | Local business presence: local posts and reviews on Google Business Profile. | ## Capabilities | Feature | Supported | Notes | | -------------------- | --------- | -------------------------------------------------------------------- | | List posts | Yes | GBP local posts (updates, offers, events) | | Comments | No | Google Business Profile has no comments or Q\&A API (see note below) | | Reply to comment | No | No comments or Q\&A API | | Moderate comment | No | Not supported by Google Business Profile API | | Toggle post comments | No | Not supported by Google Business Profile API | | DMs | No | Not supported by platform | | Reviews | Yes | Google Reviews on your business listing | | Reply to review | Yes | | | Mentions | No | Not supported by platform | | Create post | Yes | Local posts (updates, offers, events) | | Update post | Yes | | | Delete post | Yes | | ## Connecting ```bash theme={null} curl -X POST https://api.social-api.ai/v1/accounts/connect \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "platform": "google", "redirect_uri": "https://app.example.com/oauth/callback", "state": "session_abc123" }' ``` Response: ```json theme={null} { "auth_url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&state=...", "state": "4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2" } ``` Redirect your user to `auth_url`. After they authorize, SocialAPI exchanges the code with Google and redirects the user to your `redirect_uri` with the connection result. See [OAuth flows](/guides/oauth#step-3-receive-the-result-at-your-redirect_uri) for the redirect parameters. If the Google login manages more than one Business Profile, the redirect carries `status=selection_required` and a `connection_id` instead of an immediate `account_id`. See [Google: connecting a login with multiple Business Profiles](/guides/oauth#google-connecting-a-login-with-multiple-business-profiles) for the selection steps. This only applies to logins with more than one Profile; a single-Profile login connects directly. ## Reading samples ### List posts ```bash theme={null} curl "https://api.social-api.ai/v1/posts?account_ids=acc_01HZ9X3Q4R5M6N7P8V2K0W1J" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ### Comments and Q\&A Google Business Profile does not offer a comments or Q\&A API. Google discontinued the Business Profile Q\&A API on November 3, 2025, with no replacement, and Google Business Profile has never had post-level comments. Reading or answering questions through the API is not available. ### List reviews ```bash theme={null} curl "https://api.social-api.ai/v1/inbox/reviews/{account_id}" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Sample response: ```json theme={null} { "data": [ { "author": { "avatar_url": "https://example.com/avatar.jpg", "id": "17841405793187218", "name": "Jane Smith", "username": "janesmith" }, "created_at": "2026-06-20T18:24:31Z", "id": "sapi_rev_TXY...", "rating": 3, "reply": { "text": "Thanks for the feedback!", "updated_at": "2026-06-21T10:00:00Z" }, "text": "Great school", "updated_at": "2026-06-20T18:24:31Z" } ], "pagination": { "has_more": true, "next_cursor": "" }, "summary": { "average_rating": 4.2, "total_count": 87 } } ``` To reply to a review: ```bash theme={null} curl -X POST \ "https://api.social-api.ai/v1/inbox/reviews/{id}/reply" \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"text": "Thank you for your kind review, Jane!", "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J"}' ``` ## Publishing For the full field reference, media constraints, and error recovery, see [Publishing to Google](/posts/google). ## Limitations and gotchas * **Local posts** include updates, offers, and event posts published through Google Business Profile. * **Token refresh**: Google OAuth tokens expire after 1 hour. [SocialAPI](https://social-api.ai) handles refresh automatically. If you receive `401` with `code: "platform.google.auth"`, reconnect the account. ## Permissions [SocialAPI](https://social-api.ai)'s managed Google Cloud OAuth app already requests these scopes on your behalf: `https://www.googleapis.com/auth/business.manage`. You don't need to set up a Google Cloud project. See [Platform credentials](/guides/platform-credentials). ## Related * [Platform credentials](/guides/platform-credentials) * [OAuth flows](/guides/oauth) * [Publishing to Google](/posts/google) * [Errors](/guides/errors) * [Interaction IDs](/guides/interaction-ids) # Instagram Source: https://docs.social-api.ai/connectors/instagram Connect and interact with Instagram accounts via the Meta Graph API. Available **Managed connector** - [SocialAPI](https://social-api.ai) handles the Meta App and platform app review for you. No customer-side Meta for Developers registration required. See [Platform credentials](/guides/platform-credentials). ## At a glance | Field | Value | | ------------- | ------------------------------------------------------------------- | | Platform slug | `instagram` | | Auth type | OAuth 2.0 (Meta) | | API | Meta Graph API | | Best for | Largest reach for visual brands; full inbound and outbound surface. | ## Capabilities | Feature | Supported | Notes | | ------------------------------------- | --------- | ----------------------------------------------------------------------------------------------- | | List posts | Yes | Returns posts, reels, carousels | | List stories | Yes | Active stories only (expire about 24 hours after posting) | | Comments | Yes | Scoped to a specific post | | Reply to comment | Yes | | | Private reply | Yes | Sends a DM to the commenter | | Comment replies (thread) | Yes | | | Like comment | No | Instagram Graph API does not expose comment likes | | Moderate comment (hide/unhide/delete) | Yes | | | Toggle post comments | No | No route exists; planned for a future release | | DMs | Yes | | | Send DM | Yes | Text and/or media attachment (`attachment_url`) | | Send DM attachment | Yes | Image, video, audio, or file via `attachment_url` (type inferred from URL extension, max 25 MB) | | Get DM thread by user | Yes | | | Reviews | No | Instagram has no review feature | | Mentions | Yes | | | Create post | Yes | Images, videos, reels, carousels | | Delete post | Yes | | ## Connecting ```bash theme={null} curl -X POST https://api.social-api.ai/v1/accounts/connect \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "platform": "instagram", "redirect_uri": "https://app.example.com/oauth/callback", "state": "session_abc123" }' ``` Response: ```json theme={null} { "auth_url": "https://api.instagram.com/oauth/authorize?client_id=...&state=...", "state": "4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2" } ``` Redirect your user to `auth_url`. After they authorize, SocialAPI exchanges the code with Instagram and redirects the user to your `redirect_uri` with the connection result. See [OAuth flows](/guides/oauth#step-3-receive-the-result-at-your-redirect_uri) for the redirect parameters. ## Reading samples ### List posts ```bash theme={null} curl "https://api.social-api.ai/v1/posts?account_ids={id}&limit=10" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Sample response: ```json theme={null} { "data": [ { "id": "p_01HZ9X3Q4R5M6N7P8V2K0W1J", "text": "Hello world #socapi", "status": "published", "targets": [ { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform": "instagram", "platform_post_id": "17895695668004550", "status": "published" } ], "created_at": "2026-03-01T12:00:00Z" } ], "pagination": { "limit": 25, "has_more": false } } ``` ### List active stories Returns the account's currently-active stories. Stories are ephemeral: they expire about 24 hours after posting, so this endpoint only ever returns what is live right now. Instagram only. ```bash theme={null} curl "https://api.social-api.ai/v1/platforms/instagram/accounts/{id}/stories?limit=10" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Each item uses the same shape as a post. Two story-specific values are surfaced in `metadata`: `is_story` is always `true`, and `expires_at` is the computed expiry (posting `timestamp` plus 24 hours; Instagram does not return an expiry field). For video stories without a `media_url`, the cover image is returned as `media_url`. Sample response: ```json theme={null} { "data": [ { "id": "17895695668004550", "platform": "instagram", "caption": "", "media_type": "VIDEO", "media_url": "https://scontent-...", "permalink": "https://www.instagram.com/stories/acmecorp/17895695668004550/", "timestamp": "2026-06-16T12:00:00Z", "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "metadata": { "is_story": true, "expires_at": "2026-06-17T12:00:00Z" } } ], "count": 1 } ``` ### List comments on a post Comments are scoped to a specific post. Use the platform post ID (e.g. from `targets[].platform_post_id`): ```bash theme={null} curl "https://api.social-api.ai/v1/inbox/comments/17895695668004550?limit=10" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Sample response: ```json theme={null} { "data": [ { "platform_id": "17895695668004550", "platform": "instagram", "text": "Love this!", "author_id": "17841400000000000", "author_name": "Jane Doe", "author_username": "janedoe", "author_picture": "https://scontent.cdninstagram.com/v/t51.../profile.jpg", "is_owner": false, "like_count": 3, "reply_count": 1, "has_replies": true, "liked_by_viewer": false, "is_hidden": false, "capabilities": { "can_reply": true, "can_delete": true, "can_hide": true, "can_like": false, "can_private_reply": true } } ], "pagination": { "has_more": false, "next_cursor": "" } } ``` ### List DMs ```bash theme={null} curl "https://api.social-api.ai/v1/inbox/conversations?account_id={id}&platform=instagram&limit=10" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Sample response: ```json theme={null} { "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "user_id": "9f1b...", "account_id": "acct_...", "platform": "instagram", "platform_id": "aWdfZG1f...", "participant_id": "17841400000000000", "participant_name": "Jane Doe", "participant_picture": "https://scontent.cdninstagram.com/v/.../profile.jpg", "last_message": "Thanks for reaching out!", "last_message_at": "2026-06-20T12:00:00Z", "status": "active", "unread_count": 0, "created_at": "2026-06-19T09:30:00Z", "updated_at": "2026-06-20T12:00:00Z" } ], "pagination": { "has_more": false, "next_cursor": "" } } ``` ## Publishing Create an Instagram post with optional platform-specific fields: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Hello world #socapi", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "targets": [ { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform_data": { "alt_text": "A product photo showing our new widget", "share_to_feed": true } } ] }' ``` For the full field reference, media constraints, and error recovery, see [Instagram posts](/posts/instagram). ## Account summary (beta) Retrieve live profile statistics and trailing-30-day insights for a connected Instagram account: username, display name, picture, follower, following, and media counts, plus reach, views, interactions, engaged accounts, and net follower change. ```bash theme={null} curl "https://api.social-api.ai/v1/platforms/instagram/accounts/{id}/summary" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Response: ```json theme={null} { "username": "acmecorp", "display_name": "Acme Corp", "picture_url": "https://scontent.cdninstagram.com/...", "follower_count": 12400, "following_count": 87, "media_count": 340, "reach": 22400, "views": 90100, "interactions": 5500, "accounts_engaged": 4000, "net_follows": 120, "period_start": "2026-07-01T10:00:00Z", "period_end": "2026-07-31T10:00:00Z" } ``` This endpoint is in beta. The insight totals come from Instagram's `instagram_business_manage_insights` permission: accounts connected before the permission was granted must be reconnected before totals can be returned. ## Limitations and gotchas * **Private replies.** Replying to a comment with `"private": true` sends a DM to the commenter. This is Instagram's native "Send Private Reply" feature, not a [SocialAPI](https://social-api.ai) workaround. Meta allows one private reply per comment, within 7 days of the comment, and the reply does not open a standard 24-hour messaging window: the user must respond before you can send another message. * **Token expiry.** Instagram access tokens can expire. If you receive `401` with `code: "invalid_token"`, reconnect the account using the OAuth flow. * **Account fields.** See the shared [account object](/connectors/overview#account-object) reference for the fields returned by `/v1/accounts`. ## Permissions * **Permissions.** [SocialAPI](https://social-api.ai)'s managed Meta App already requests these scopes on your behalf: `instagram_business_basic`, `instagram_business_manage_comments`, `instagram_business_manage_messages`, `instagram_business_content_publish`. You don't need to apply for any platform app review. See [Platform credentials](/guides/platform-credentials). ## Related * [Platform credentials](/guides/platform-credentials) * [OAuth flows](/guides/oauth) * [Publishing to Instagram](/posts/instagram) * [Errors](/guides/errors) * [Interaction IDs](/guides/interaction-ids) # LinkedIn Source: https://docs.social-api.ai/connectors/linkedin Connect a LinkedIn member account to publish posts as the individual, or connect a LinkedIn organization page (beta). ## Overview The LinkedIn connector lets you publish posts as the **authenticated individual member** (your personal LinkedIn profile). It authenticates through LinkedIn's OAuth 2.0 flow with OpenID Connect and uses the "Share on LinkedIn" REST API. Organization (company) page support is available in beta via the separate `linkedin_page` platform identifier. See [LinkedIn organization pages](#linkedin-organization-pages-beta) below. ## Capabilities ### LinkedIn member (`linkedin`) | Capability | Supported | | ----------------------------------------------- | --------- | | Publish posts (text, image, video, multi-image) | Yes | | Update post text | Yes | | Delete posts | Yes | | Read posts | No | | Comments | No | | Direct messages | No | | Reviews | No | | Mentions | No | Posts only. Reading existing posts from the platform, comments, DMs, reviews, and mentions are not available for member accounts. Direct messages, reviews, and mentions are not offered by LinkedIn's API and will remain unavailable. ### LinkedIn organization page (`linkedin_page`, beta) | Capability | Supported | | ----------------------------------------------- | ---------- | | Publish posts (text, image, video, multi-image) | Yes (beta) | | Update post text | Yes (beta) | | Delete posts | Yes (beta) | | Read posts | Yes (beta) | | Comments (read, reply, like, delete) | Yes (beta) | | Page insights | Yes (beta) | | Direct messages | No | | Reviews | No | | Mentions | No | ## Authentication LinkedIn uses OAuth 2.0. Connect an account through the standard connect flow: 1. Call the connect endpoint with `platform=linkedin`. 2. Redirect the user to the returned `auth_url`. 3. LinkedIn redirects back to your `redirect_uri` with a `code`. 4. The code is exchanged for an access token automatically. ### Scopes The connector requests these scopes: * `openid`, `profile` — identify the member (used to read the person's name, picture, and ID) * `w_member_social` — publish posts as the member ### Token lifetime LinkedIn member access tokens last about 60 days and cannot be refreshed programmatically. When a token expires, the member reconnects the account to continue publishing. ### Required header All API calls send `LinkedIn-Version` (currently `202605`) and use the Rest.li protocol. ## Connecting an account ```bash cURL theme={null} curl -X POST https://api.social-api.ai/v1/accounts/connect \ -H "Authorization: Bearer $SOCIALAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"platform": "linkedin", "redirect_uri": "https://yourapp.com/callback"}' ``` After connecting, the response contains a single connected account: the authenticated member. That account is what you publish to. ## Publishing a post ```bash cURL theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCIALAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Hello from SocialAPI!", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"] }' ``` Posts publish to the member's feed. You can attach a single image, a single video, or multiple images (a carousel). Mixed image and video carousels are not supported. ## LinkedIn organization pages (beta) LinkedIn organization (company) page support is live in beta via the `linkedin_page` platform identifier. It uses the `w_organization_social` scope and the LinkedIn Community Management API. Access is beta-gated: your account must have `linkedin_page` in its `allowed_platforms` list. Contact support to request access. ### Connecting an organization page Pass `"connection_type": "organization"` inside the `"metadata"` field to anchor the credential on the first administered company page (the default behavior). The returned account includes all administered organization pages. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/accounts/connect \ -H "Authorization: Bearer $SOCIALAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "platform": "linkedin_page", "redirect_uri": "https://yourapp.com/callback", "metadata": { "connection_type": "organization" } }' ``` ### Scopes The `linkedin_page` organization flow (default) requests these 7 scopes: * `r_organization_followers`: read the company page's follower statistics * `r_organization_social`: read the page's posts and comments * `rw_organization_admin`: read and write page administrative data (manage the page) * `r_organization_social_feed`: read the page's social feed * `w_organization_social`: publish posts as the page * `w_organization_social_feed`: create comments and replies on the page's feed * `r_basicprofile`: resolve the connecting admin's identity via /v2/me The optional `"connection_type": "personal"` variant uses a different scope set (`w_member_social`, `w_member_social_feed`, `r_member_postAnalytics`, `r_member_profileAnalytics`, `r_basicprofile`, `r_1st_connections_size`) and anchors the credential on the connecting member rather than a company page. ### Organization URN requirement All org-page operations require the LinkedIn organization URN (for example, `urn:li:organization:12345678`). This URN is returned as the account's `platform_account_id` when the account is connected. ### Capabilities The `linkedin_page` connector supports publishing posts (text, image, video, multi-image), reading posts, managing comments (read, reply, like, delete, hide), and retrieving page-level analytics via `GetPageInsights`. DMs, reviews, and mentions are not available through LinkedIn's API. ## Quirks and limitations * LinkedIn member (`linkedin`) connects as the individual member. Use `linkedin_page` for organization pages. * Reading a member's own posts from the platform requires elevated permissions and is not available, so listing posts returns the posts SocialAPI has stored, not a live read from LinkedIn. * Comments, direct messages, reviews, and mentions are not available for member accounts. Use the `linkedin_page` connector for organization page comment management. * LinkedIn does not scrape link previews; provide your own thumbnail, title, and description for article posts. * LinkedIn returns the post URN to SocialAPI in an upstream response header. SocialAPI captures it for you: it is returned as `platform_post_id` in the post body (and in `post.published` webhooks) along with a `permalink`. You never need to read a header on SocialAPI responses. * Member access tokens last about 60 days with no programmatic refresh; the member reconnects when the token expires. * LinkedIn does not provide a token revocation API; disconnecting removes the stored token on our side only. ## Next steps Full reference for the LinkedIn post payload. How publishing works across platforms. # Connectors Overview Source: https://docs.social-api.ai/connectors/overview Platform capabilities and feature matrix. Connectors give SocialAPI.ai one unified API across social networks: each connector normalizes a platform's comments, messages, mentions, reviews, and publishing into the shared SocialAPI schema. [SocialAPI.AI](https://social-api.ai) uses a connector pattern - each platform implements the same interface. If a platform doesn't support a feature, the API returns `501` with `error.code: "resource.not_supported"`. Your code doesn't need to branch per platform; just handle the 501. ## Feature matrix For the full per-feature, per-platform support table (reply, hide, like, delete, private reply, DMs, reviews, mentions, publishing), see [Platform support](/guides/platforms). It is the single source of truth and is kept in sync with each connector page. ## OAuth app ownership Every connector except Twitter uses SocialAPI's managed platform app. You don't need to create your own Meta App, Google Cloud project, or TikTok Developer registration. | Platform | OAuth app provided by | | ----------- | ------------------------------------------------------------ | | Instagram | SocialAPI | | Facebook | SocialAPI | | Threads | SocialAPI | | TikTok | SocialAPI | | LinkedIn | SocialAPI | | YouTube | SocialAPI | | WhatsApp | SocialAPI | | Telegram | **You (bot token from @BotFather)** - no OAuth | | X / Twitter | **You (BYOK)** - see [setup guide](/connectors/twitter-byok) | | Google | SocialAPI (beta) | | Pinterest | SocialAPI | | Trustpilot | API key (Coming soon) | See [Platform credentials](/guides/platform-credentials) for the full story, including consent-screen branding and FAQs. ## Account object `GET /v1/accounts` returns an array of Account objects: ```json theme={null} { "id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform": "instagram", "name": "Acme Corp", "username": "acmecorp", "brand_id": "b7e2c1a0-4f3d-11ee-be56-0242ac120002", "status": "active" } ``` | Field | Type | Description | | --------------------- | ------ | --------------------------------------------------------------------------------------------------- | | `id` | string | Account ID. Pass this as `:id` in all account-scoped endpoints. | | `platform` | string | Platform identifier (e.g. `instagram`, `facebook`, `tiktok`, `youtube`, `twitter`). | | `name` | string | Display name of the connected page or profile. | | `username` | string | Platform handle or username. | | `brand_id` | string | Internal brand UUID this account belongs to. Always present. | | `status` | string | Account status. One of: `active`, `reconnect_required`. Always present. | | `profile_picture_url` | string | Profile picture URL. Omitted when not available. | | `bio` | string | Profile biography or description. Omitted when not available. | | `reconnect_reason` | string | Human-readable reason why reconnection is required. Only set when `status` is `reconnect_required`. | ## Connect response metadata The `metadata` object is not included in the `GET /v1/accounts` account list response. It is returned only on the connect (`POST /v1/accounts/connect`) and OAuth exchange (`POST /v1/oauth/exchange`) responses. Use it to read platform-specific profile data at connection time. The following platforms populate `metadata`: | Key | Type | Platform | | --------------------- | ------- | ---------------------------------------- | | `avatar_url` | string | TikTok | | `follower_count` | integer | TikTok | | `following_count` | integer | TikTok | | `video_count` | integer | TikTok, YouTube | | `likes_count` | integer | TikTok | | `subscriber_count` | integer | YouTube | | `uploads_playlist_id` | string | YouTube | | `byok_source` | string | X/Twitter (value: `"user"` or `"brand"`) | | `bot_id` | string | Telegram | | `phone_number_id` | string | WhatsApp | | `waba_id` | string | WhatsApp | Platforms not listed above omit `metadata` entirely. ## What "not supported" means When a platform doesn't support a feature (e.g. Instagram has no reviews), calling that endpoint returns: ```json theme={null} { "error": { "code": "resource.not_supported", "message": "Operation not supported for this platform" }, "request_id": "01HZAB1234CDEF5678GHIJ9012" } ``` The `request_id` field is always present on every error response. Use it when reporting issues. This is expected behavior, not a bug. Design your app to handle `501` gracefully. ## Auth types | Type | How it works | | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `oauth2` | `POST /accounts/connect` with a top-level `redirect_uri` returns an `auth_url`. Send the user there; SocialAPI handles the platform callback and redirects the user to your `redirect_uri` with the result. See [OAuth flows](/guides/oauth). | | `apikey` | Pass the platform API key in `metadata` in `POST /accounts/connect`. Returns account ID immediately. | ## Private replies On Instagram and Facebook, posting a reply with `"private": true` sends a DM to the commenter instead of a public reply. This is the platform's native "Send Private Reply" feature. The `private` field is silently ignored on platforms that don't support it. # Telegram Source: https://docs.social-api.ai/connectors/telegram Connect a Telegram bot to read and reply to direct messages through SocialAPI. Available **Messaging connector.** Telegram is a DM-only connector: you connect a bot with a static token from @BotFather and use SocialAPI's unified inbox to read and reply to the direct messages the bot receives. There is no OAuth, no posts, and no comments. ## At a glance | Field | Value | | ------------- | -------------------------------------------------------- | | Platform slug | `telegram` | | Auth type | Static bot token (from @BotFather) | | API | Telegram Bot API | | Best for | A unified inbox for your Telegram bot's direct messages. | ## Capabilities | Feature | Supported | Notes | | ---------- | --------- | ----------------------------------------------------------------- | | DMs | Yes | Read direct messages your bot receives and reply to them | | Send DM | Yes | Send text, a photo (by URL), or a document (by URL) | | Comments | No | Telegram bots have no post-comment model | | Reviews | No | Not applicable | | Mentions | No | Not tracked for Telegram | | Publishing | No | Telegram is a messaging connector here; it does not publish posts | | Webhooks | Yes | Inbound messages arrive in real time via Telegram webhooks | ## Connecting Telegram does not use OAuth. There is no authorization URL, no `code` exchange, no refresh token, and no token expiry. You create a bot with @BotFather, copy its token, and pass it in `metadata.bot_token`: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/accounts/connect \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "platform": "telegram", "metadata": { "bot_token": "123456789:ABCdefGhIjKlMnOpQrStUvWxYz-1234" } }' ``` Response (HTTP 201): ```json theme={null} { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform": "telegram", "username": "mysocialinboxbot", "display_name": "My Social Inbox Bot" } ``` SocialAPI validates the token, registers a Telegram webhook for your bot automatically, and stores the account. No redirect, no second call. ### Getting a bot token 1. Open Telegram and message [@BotFather](https://t.me/BotFather). 2. Send `/newbot` and follow the prompts (choose a name and a username ending in `bot`). 3. BotFather replies with a token of the form `:`. 4. Paste that token as `metadata.bot_token`. The whole process takes about two minutes. There is no developer account, business verification, or app review. ## Reading and sending DMs Telegram has no message-history API: a bot only sees messages that arrive after it is connected. SocialAPI builds the inbox from inbound webhook events from connect time onward. ### List DM conversations ```bash theme={null} curl "https://api.social-api.ai/v1/inbox/conversations?account_id={id}" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ### Send a DM ```bash theme={null} curl -X POST https://api.social-api.ai/v1/inbox/conversations/{conversation_id}/messages \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "account_id": "{account_id}", "text": "Thanks for reaching out!" }' ``` `account_id` is required. Omitting it returns `400 field_required`. ### Send a DM with an attachment Use `attachment_url` to send a file. SocialAPI picks the Telegram method based on the URL extension: image extensions (`.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`) call `sendPhoto`; all other extensions call `sendDocument`. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/inbox/conversations/{conversation_id}/messages \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "account_id": "{account_id}", "text": "Here is the file you requested.", "attachment_url": "https://example.com/report.pdf" }' ``` ## Limitations and gotchas * **The bot cannot start a conversation.** Telegram forbids a bot from messaging a user who has never messaged it. Sending to such a user returns `telegram.cannot_message` (HTTP 403). The user must send `/start` or any message to the bot first. There is no 24-hour window once started. * **No message history backfill.** There is no list or history endpoint in the Bot API. The inbox is populated only by webhooks received after you connect. * **DM-only.** Posts, channels, and comments are not supported through this connector. * **No token expiry.** A bot token never expires. It is revoked only via @BotFather (`/revoke`). If a token is revoked, reconnect with a fresh token. ## Errors | HTTP | Code | When it occurs | | ---- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | | 401 | `platform.telegram.auth` | The bot token is invalid or has been revoked via @BotFather. Reconnect the account with a fresh token. | | 403 | `telegram.cannot_message` | The bot cannot message this chat: the user has not started the bot, or blocked or removed it. | | 429 | `platform.telegram.rate_limit` | Telegram rate-limited the bot. The `retry_after` metadata field contains the number of seconds to wait before retrying. | See [Errors](/guides/errors) for the full catalog. ## Related * [Platform support](/guides/platforms) * [Platform credentials](/guides/platform-credentials) * [Inbox](/guides/inbox) * [Errors](/guides/errors) # Threads Source: https://docs.social-api.ai/connectors/threads Connect and interact with Threads accounts via the Meta Threads API. Available **Managed connector** - SocialAPI handles the Meta Threads App and platform app review for you. No customer-side Meta for Developers registration required. See [Platform credentials](/guides/platform-credentials). Threads is fully supported for posts, comments, moderation, and publishing. ## At a glance | Field | Value | | ------------- | ------------------------------------------- | | Platform slug | `threads` | | Auth type | OAuth 2.0 (Meta) | | API | Threads Graph API | | Best for | Text-first posting and replying on Threads. | ## Capabilities | Feature | Supported | Notes | | ------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------- | | List posts | Yes | Text posts, images, videos, carousels | | Comments | Yes | Replies to a specific post | | Reply to comment | Yes | | | Comment replies (thread) | Yes | Fetch the reply thread under a comment | | Moderate comment (hide/unhide/delete) | Yes | Hide, unhide, and delete supported. Unknown actions return `501`. | | Toggle post comments | No | Not supported by Threads API | | DMs | No | Not supported by platform | | Reviews | No | Not applicable | | Mentions | Webhook only | Received via inbound webhook (field: mentions). Requires Advanced Access and threads\_manage\_mentions scope. No polling endpoint. | | Create post | Yes | Text, images, videos, carousels | | Delete post | Yes | | | Update post | No | Not supported by Threads API | ## Connecting ```bash theme={null} curl -X POST https://api.social-api.ai/v1/accounts/connect \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "platform": "threads", "redirect_uri": "https://app.example.com/oauth/callback", "state": "session_abc123" }' ``` Response: ```json theme={null} { "auth_url": "https://threads.net/oauth/authorize?client_id=...&state=...", "state": "4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2" } ``` Redirect your user to `auth_url`. After they authorize, SocialAPI exchanges the code with Threads and redirects the user to your `redirect_uri` with the connection result. See [OAuth flows](/guides/oauth#step-3-receive-the-result-at-your-redirect_uri) for the redirect parameters. ## Reading samples ### List posts ```bash theme={null} curl "https://api.social-api.ai/v1/accounts/{id}/posts?limit=10" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Sample response: ```json theme={null} { "data": [ { "id": "17895695668004550", "platform": "threads", "caption": "Hello from Threads!", "media_type": "TEXT_POST", "permalink": "https://www.threads.net/@acmecorp/post/ABC123", "timestamp": "2026-03-01T12:00:00Z", "like_count": 18, "comments_count": 3, "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" } ], "count": 1 } ``` ### List comments on a post ```bash theme={null} curl "https://api.social-api.ai/v1/accounts/{id}/posts/17895695668004550/comments?limit=10" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ## Publishing Create a Threads post with optional platform-specific fields: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Hello from Threads!", "targets": [ { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform_data": { "reply_control": "accounts_you_follow" } } ] }' ``` For the full field reference, media constraints, and error recovery, see [Threads posts](/posts/threads). ## Limitations and gotchas * **Moderation.** Hide, unhide, and delete are supported. Unsupported moderation actions return `501`. * **No post updates.** Threads does not support editing published posts. Use delete and re-create if needed. * **Publishing.** Supports text posts, single images, single videos, and carousel posts. * **Token refresh.** Threads tokens can expire. SocialAPI handles refresh automatically. If you receive `401` with `code: "platform.threads.auth"`, reconnect the account. ## Permissions SocialAPI's managed Meta Threads App already requests these scopes on your behalf: `threads_basic`, `threads_content_publish`, `threads_read_replies`, `threads_delete`. You don't need to apply for any platform app review. See [Platform credentials](/guides/platform-credentials). ## Related * [Platform credentials](/guides/platform-credentials) * [OAuth flows](/guides/oauth) * [Publishing to Threads](/posts/threads) * [Errors](/guides/errors) * [Interaction IDs](/guides/interaction-ids) # TikTok Source: https://docs.social-api.ai/connectors/tiktok Connect and interact with TikTok accounts via the TikTok API. Available **Managed connector** - [SocialAPI](https://social-api.ai) handles the TikTok Developer App and platform app review for you. No customer-side TikTok for Developers registration required. See [Platform credentials](/guides/platform-credentials). TikTok is supported for listing videos and publishing new posts via the TikTok Content Posting API. ## At a glance | Field | Value | | ------------- | -------------------------------------- | | Platform slug | `tiktok` | | Auth type | OAuth 2.0 (TikTok) | | API | TikTok Content Posting API | | Best for | Short-form video publishing to TikTok. | ## Capabilities | Feature | Supported | Notes | | -------------------- | --------- | ------------------------------ | | List posts | Yes | Returns published videos | | Create post | Yes | Via TikTok Content Posting API | | Delete post | - | Not supported via TikTok API | | Comments | - | Not supported via TikTok API | | Reply to comment | - | | | Moderate comment | - | | | Toggle post comments | - | | | DMs | - | Not supported by platform API | | Reviews | - | Not applicable | | Mentions | - | Not supported via TikTok API | ## Connecting ```bash theme={null} curl -X POST https://api.social-api.ai/v1/accounts/connect \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "platform": "tiktok", "redirect_uri": "https://app.example.com/oauth/callback", "state": "session_abc123" }' ``` Response: ```json theme={null} { "auth_url": "https://www.tiktok.com/v2/auth/authorize/?client_key=...&state=...", "state": "4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2" } ``` Redirect your user to `auth_url`. After they authorize, SocialAPI exchanges the code with TikTok and redirects the user to your `redirect_uri` with the connection result. See [OAuth flows](/guides/oauth#step-3-receive-the-result-at-your-redirect_uri) for the redirect parameters. TikTok accounts include a `metadata` object with profile data: ```json theme={null} { "id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform": "tiktok", "name": "Acme Corp", "username": "acmecorp", "metadata": { "avatar_url": "https://p16.tiktokcdn.com/...", "follower_count": 12400, "following_count": 180, "video_count": 94, "likes_count": 340000 } } ``` | Field | Type | Description | | ----------------- | ------- | ----------------------------- | | `avatar_url` | string | Profile picture URL | | `follower_count` | integer | Number of followers | | `following_count` | integer | Number of accounts followed | | `video_count` | integer | Total published videos | | `likes_count` | integer | Total likes across all videos | ## Reading samples ### List posts ```bash theme={null} curl -X GET "https://api.social-api.ai/v1/posts?account_ids=acc_01HZ9X3Q4R5M6N7P8V2K0W1J" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ## Publishing Create a TikTok post. `media_type` is optional and defaults to `video`. `privacy_level` must be supplied explicitly: omitting it returns a 400 validation error (`privacy_level_required`). Valid values depend on the creator's allowed options (returned by the TikTok creator info API); a common example is `PUBLIC_TO_EVERYONE`. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Check out this video!", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "targets": [ { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform_data": { "media_type": "video", "privacy_level": "PUBLIC_TO_EVERYONE" } } ] }' ``` For the full field reference, media constraints, and error recovery, see [TikTok posts](/posts/tiktok). ## Account summary Retrieve live profile statistics for a connected TikTok account: display name, username, avatar URL, and the follower, following, likes, and video counts. ```bash theme={null} curl "https://api.social-api.ai/v1/platforms/tiktok/accounts/{id}/summary" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Response: ```json theme={null} { "display_name": "Acme Corp", "username": "acmecorp", "avatar_url": "https://p16-sign.tiktokcdn-us.com/...", "follower_count": 12400, "following_count": 87, "likes_count": 980000, "video_count": 47 } ``` The statistics come from TikTok's `user.info.stats` scope. Accounts connected before this scope was granted must be reconnected before the endpoint can return counts. ## Force sync Force an immediate re-sync of the stored video list for an account. This pulls the most recent videos (up to 20) from TikTok, including view, like, comment, and share counts. Synced videos are then available from `GET /v1/posts?platform=tiktok`. ```bash theme={null} curl -X POST "https://api.social-api.ai/v1/platforms/tiktok/accounts/{id}/sync" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Returns `{"success": true}` on success. This endpoint uses TikTok's `video.list` scope and requires a reconnect for accounts connected before the scope was granted. ## Limitations and gotchas * **Publishing.** Post creation uses the TikTok Content Posting API. Videos are uploaded via media upload, then published. * **Read-only interactions.** TikTok's API does not expose comment management or DM access. Only post listing and publishing are available. * **Token refresh.** TikTok OAuth tokens expire. SocialAPI handles refresh automatically. If you receive `401` with `code: "platform.tiktok.auth"`, reconnect the account. * **Privacy level.** `privacy_level` is required and must be set explicitly for every post. Omitting it returns a 400 error (`privacy_level_required`). Use the value the TikTok creator info API returns for the account (for example, `PUBLIC_TO_EVERYONE`, `MUTUAL_FOLLOW_FRIENDS`, or `SELF_ONLY`); any other value is rejected. ## Permissions SocialAPI's managed TikTok Developer App already requests these scopes on your behalf: `user.info.basic`, `user.info.profile`, `video.publish`, `video.upload`. You don't need to register with TikTok for Developers. See [Platform credentials](/guides/platform-credentials). ## Related * [Platform credentials](/guides/platform-credentials) * [OAuth flows](/guides/oauth) * [Publishing to TikTok](/posts/tiktok) * [Errors](/guides/errors) # Trustpilot Source: https://docs.social-api.ai/connectors/trustpilot Trustpilot connector for SocialAPI.AI. Coming soon **Coming soon.** Trustpilot review reading and replying is in active development via the Trustpilot Business API. ## At a glance | Field | Value | | ------------------- | ------------------------------------------------------------------------------------------------------- | | Platform slug | `trustpilot` | | Auth type (planned) | OAuth 2.0 (write/private, for reply and delete reply) + API key (read-only public, for listing reviews) | | API | Trustpilot Business API | | Best for | Aggregating Trustpilot reviews into the SocialAPI inbox alongside other platforms. | ## Capabilities | Feature | Status | Notes | | -------- | ----------- | --------------------------------- | | Reviews | Coming soon | List, reply, delete reply | | Posts | Not planned | Trustpilot has no posting surface | | Comments | Not planned | | | DMs | Not planned | | | Mentions | Not planned | | ## Notify me [Contact us](mailto:support@social-api.ai) to be notified when Trustpilot support launches. ## Related * [Platform support](/guides/platforms) * [Connectors overview](/connectors/overview) # X / Twitter Source: https://docs.social-api.ai/connectors/twitter Connect and interact with X (Twitter) accounts via the X API v2. Available **BYOK connector** - Twitter requires you to bring your own Twitter Developer app. Setup takes about ten minutes. See [Platform credentials](/guides/platform-credentials) for why Twitter is the only platform that works this way. ## At a glance | Field | Value | | ------------- | ------------------------------------------------------------ | | Platform slug | `twitter` | | Auth type | OAuth 2.0 (BYOK) | | API | X API v2 | | Best for | Real-time conversation: tweets, replies, DMs, mentions on X. | ## Setup Before you can connect any Twitter account, register a Twitter Developer app and paste your Client ID and Client Secret into your SocialAPI dashboard. See the [Twitter BYOK setup guide](/connectors/twitter-byok) for the ten-minute walkthrough. ## Capabilities | Feature | Supported | Notes | | ------------------------------ | --------- | -------------------------------------------------------------------- | | List posts | Yes | Returns user tweets | | Comments | Yes | Replies to a specific tweet | | Reply to comment | Yes | | | Comment replies (thread) | Yes | | | Moderate comment (hide/delete) | Yes | Hide a reply, or delete your own reply | | Like comment | No | X removed like/unlike writes from self-serve tiers (Enterprise only) | | Private reply | No | Not applicable | | DMs | Yes | Direct Messages via X API v2 | | Send DM | Yes | | | Reviews | No | Not applicable | | Mentions | Yes | Tweets mentioning the authenticated user | | Create post | Yes | Text tweets only (no media upload today) | | Update post | No | Edits are gated to Enterprise; not available to self-serve apps | | Delete post | Yes | | ## Connecting ```bash theme={null} curl -X POST https://api.social-api.ai/v1/accounts/connect \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "platform": "twitter", "redirect_uri": "https://app.example.com/oauth/callback", "state": "session_abc123" }' ``` Response: ```json theme={null} { "auth_url": "https://x.com/i/oauth2/authorize?client_id=...&state=...", "state": "4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2" } ``` Redirect your user to `auth_url`. After they authorize, SocialAPI exchanges the code with X and redirects the user to your `redirect_uri` with the connection result. See [OAuth flows](/guides/oauth#step-3-receive-the-result-at-your-redirect_uri) for the redirect parameters. ## Reading samples ### List posts ```bash theme={null} curl "https://api.social-api.ai/v1/posts?account_ids={id}&limit=10" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ### List comments on a post ```bash theme={null} curl "https://api.social-api.ai/v1/accounts/{id}/posts/{pid}/comments?limit=10" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ### List DMs ```bash theme={null} curl "https://api.social-api.ai/v1/inbox/conversations?account_id={id}" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ### List mentions ```bash theme={null} curl "https://api.social-api.ai/v1/accounts/{id}/mentions?limit=10" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ## Publishing Create a tweet with optional X-specific settings: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Hello from SocialAPI!", "targets": [ { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform_data": { "reply_settings": "following" } } ] }' ``` ### Platform data fields Pass these inside the target's `platform_data` when creating a post. All fields are optional. | Field | Type | Description | | ------------------------------ | --------------- | ----------------------------------------------------------------------------------------------------- | | `reply_settings` | string | Who can reply to this tweet. One of `"following"`, `"mentionedUsers"`, `"subscribers"`, `"verified"`. | | `geo_place_id` | string | Attach a location by X place ID. | | `community_id` | string | Post into an X Community. | | `for_super_followers_only` | boolean | Restrict the post to Super Followers. | | `nullcast` | boolean | Promoted-only post (does not appear in the timeline). | | `made_with_ai` | boolean | Flag the post as AI-generated content. | | `paid_partnership` | boolean | Disclose branded content. | | `in_reply_to_tweet_id` | string | Publish the post as a reply to an existing tweet. | | `auto_populate_reply_metadata` | boolean | Auto-include @mentions from the tweet being replied to. Only meaningful with `in_reply_to_tweet_id`. | | `exclude_reply_user_ids` | array of string | User IDs to exclude from auto-populated @mentions. Only meaningful with `in_reply_to_tweet_id`. | | `thread` | array of string | Continuation tweets chained after the root, posted in order. See [Threads](#threads). | Invalid values (a bad `reply_settings` enum, a wrong field type, an empty or over-length thread entry) are rejected by `POST /v1/posts/validate` and by create with a validation error, before any tweet is published. Unrecognized `platform_data` keys are ignored with a warning. ### Threads Set `thread` to an array of strings to publish a chained thread. The target's `text` is the root tweet (tweet 1) and each `thread` entry is posted as a reply to the one before it. Continuation tweets are text-only: the `platform_data` settings above apply to the root tweet only. ```json theme={null} { "text": "1/ A thread about our launch.", "targets": [ { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform_data": { "thread": [ "2/ First, the backstory.", "3/ And here is what is next." ] } } ] } ``` The published post's ID is the root tweet's ID. Continuation tweet IDs are returned in the post metadata under `thread_post_ids`. If a continuation tweet fails partway through, publishing stops: the tweets posted so far stay live (X has no batch rollback), the metadata records `thread_failed_at_index`, and the request returns an error. Each tweet in a thread is a separate billable X write. Posts containing a URL cost significantly more per X's pay-per-use pricing, so keep links minimal. ## Limitations and gotchas * **Text-only posts.** Posts carry text plus the `platform_data` settings above, and can be chained into threads. Media upload is not implemented yet, so image and video attachments are ignored. Polls and quote tweets are also unsupported. * **No post editing.** X's edit capability (`edit_options.previous_post_id`) is gated to Enterprise on the pay-per-use tier, so it is not available to self-serve BYOK apps. `UpdatePost` returns `501`. Delete and re-create if needed. * **No webhook handler.** X does not push real-time events via this integration. Posts and comments are fetched on demand. * **Comment moderation.** Hide toggles reply visibility. Delete removes your own replies. Liking is not available: X removed like/unlike writes from all self-serve tiers (April 2026), so `can_like` is `false`. * **Token refresh.** X OAuth 2.0 tokens expire after two hours. SocialAPI proactively refreshes the access token when fewer than 5 minutes remain on the current token, so most requests complete without interruption. If you receive `401` with `code: "invalid_token"`, the refresh itself failed (for example, the refresh token was revoked): reconnect the account. * **Required scopes.** Your X Developer App must have the appropriate OAuth 2.0 scopes for reading tweets, posting, DMs, and user lookup. ## Permissions Because Twitter is BYOK, you control the OAuth scopes on your own Twitter Developer app. We recommend enabling these scopes for full functionality: read and write tweets, read and send DMs, user lookup. See the [Twitter BYOK setup guide](/connectors/twitter-byok) for the exact scope list to select in the X Developer Portal. ## Related * [Twitter BYOK setup guide](/connectors/twitter-byok) * [Platform credentials](/guides/platform-credentials) * [OAuth flows](/guides/oauth) * [Errors](/guides/errors) * [Interaction IDs](/guides/interaction-ids) # Twitter BYOK setup Source: https://docs.social-api.ai/connectors/twitter-byok Bring your own Twitter Developer app to connect Twitter accounts to SocialAPI. Twitter requires each SocialAPI user to supply their own Twitter Developer app. Twitter's free API tier is scoped per-app, so using a single shared app across tenants hits rate limits immediately. BYOK (Bring Your Own Keys) gives each user their own rate-limit bucket. When you create the app, X immediately shows you a **Bearer Token** and **OAuth 1.0 Keys** (Consumer Key, Access Token). These are *not* the credentials SocialAPI uses. SocialAPI uses **OAuth 2.0**, which you have to enable separately by completing step 2 below. The OAuth 2.0 Client ID and Client Secret will appear in the app panel only after you finish that setup. ## 1. Create a Twitter Developer project and app 1. Sign in at [developer.x.com](https://developer.x.com) and create a Free or Basic project. 2. Inside the project, create a new **App**. 3. Click your app in the **Apps** sidebar to open its detail panel on the right side of the console. ## 2. Set up User authentication settings In the app's detail panel, scroll to **User authentication settings** and click **Set up**. On the form that opens, configure all of the following on the same screen: * **App permissions:** Read and write and Direct message * **Type of App:** Web App, Automated App or Bot * **Callback URI / Redirect URL:** copy the exact URL from the SocialAPI setup dialog (format: `https://api.social-api.ai/oauth/callback/twitter`) * **Website URL:** your own site or `https://social-api.ai` * **Scopes** (sometimes shown on the same form, sometimes in a sub-step): enable ``` tweet.read tweet.write tweet.moderate.write users.read dm.read dm.write like.write offline.access ``` Missing any of these will cause the OAuth flow to succeed but API calls to fail with permission errors afterwards. Click **Save**. ## 3. Copy your OAuth 2.0 Client ID and Client Secret After saving, an **OAuth 2.0** section appears in the app's detail panel. Copy both the **Client ID** and **Client Secret**. Store the secret somewhere safe, X only shows it once on first generation. ## 4. Paste into SocialAPI In the SocialAPI dashboard, go to **Settings → Twitter integration**. Paste the Client ID and Client Secret, click **Test credentials** to verify, then **Save**. You can now click Connect on any Twitter platform card to complete the OAuth flow. ## Brand overrides If you manage multiple brands (agency use case), you can add a different Twitter Developer app per brand from the same settings page. Brand-scoped credentials take precedence over the default when connecting a Twitter account to that brand. ## Rotation Rotating the Client Secret in Twitter's developer portal invalidates all tokens issued by the app. After rotation: 1. Paste the new secret into SocialAPI. 2. Reconnect any Twitter accounts that were using the old credentials (they will be marked as needing reconnection on the next API call). ## Troubleshooting * **Pasted credentials don't work / `invalid_client` on save:** Double-check you're pasting the **OAuth 2.0** Client ID and Client Secret from the app panel, not the Consumer Key, Secret Key, or Bearer Token shown right after app creation. Those are OAuth 1.0a and app-only credentials and won't work with SocialAPI's OAuth 2.0 flow. * **`invalid_client` on connect:** The secret was rotated in Twitter's dashboard without updating SocialAPI. Copy the current pair from developer.x.com. * **OAuth flow succeeds but API calls return 403:** Scopes are missing. Enable all scopes listed in step 2 and reconnect. * **Callback URL mismatch error:** The Twitter app's callback URL doesn't match what SocialAPI sent. Copy the URL from the setup dialog exactly, no trailing slash, no environment suffix. * **Hitting rate limits immediately:** Check which environment your X app is in (development, staging, or production) and what your X developer plan tier is. The OAuth 2.0 setup mechanics are identical across all three environments, but the rate-limit quotas attached to each environment depend on your paid tier. The Free tier is heavily throttled; Basic and above unlock higher quotas in production. ## Security * Your Client Secret is encrypted at rest with AES-256-GCM and is never returned by any API endpoint. Only the first six characters of the Client ID are stored in plaintext for UI preview. * Credential CRUD endpoints (`/v1/platforms/twitter/credentials`) are dashboard-only: they require a Dashboard JWT and reject Bearer API keys, so a leaked API key cannot read or write BYOK credentials. ## Endpoints These endpoints are **dashboard-only** (Dashboard JWT required; Bearer API keys are rejected): | Method | Path | Description | | -------- | --------------------------------------------------- | ------------------------------------------------------- | | `GET` | `/v1/platforms/twitter/credentials` | List stored credential sets (default + brand overrides) | | `PUT` | `/v1/platforms/twitter/credentials` | Create or update the default credential set | | `DELETE` | `/v1/platforms/twitter/credentials` | Delete the default credential set | | `PUT` | `/v1/platforms/twitter/credentials/brand/:brand_id` | Create or update a brand-scoped credential override | | `DELETE` | `/v1/platforms/twitter/credentials/brand/:brand_id` | Delete a brand-scoped credential override | | `POST` | `/v1/platforms/twitter/credentials/test` | Test a credential pair before saving | These routes are not included in the public OpenAPI spec. They are used exclusively by the SocialAPI dashboard. # WhatsApp Source: https://docs.social-api.ai/connectors/whatsapp Connect a WhatsApp Business phone number to receive and reply to direct messages through SocialAPI. Beta **Messaging connector.** WhatsApp is a DM-only connector: you connect a WhatsApp Business phone number through Meta's Embedded Signup, then use our unified inbox to receive and reply to the messages that number gets. There are no posts, comments, reviews, or mentions. ## At a glance | Field | Value | | ------------- | ------------------------------------------------------------------------ | | Platform slug | `whatsapp` | | Auth type | Embedded Signup (OAuth), Meta-hosted | | API | WhatsApp Cloud API | | Access | Beta (request access) | | Best for | A unified inbox for the messages your WhatsApp Business number receives. | ## Capabilities | Feature | Supported | Notes | | ------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------- | | Receive DMs | Yes | Inbound messages arrive in real time via webhooks | | Free-form reply | Yes | Text only, and only inside the open 24-hour service window (see below) | | Template messages | Yes | Create, list, edit, delete, and validate templates via the API, and send approved templates (including outside the 24-hour window) | | Media / interactive free-form replies | No | Free-form inbox replies are plain text; buttons and media are delivered through templates instead | | Conversation list / history | No | There is no history API; the inbox is built from webhooks received after you connect | | Phone-number management | Yes | Request a code, verify, register, deregister, and check status of the business number | | Business profile | Yes | Read and update the WhatsApp business profile | | Publishing (posts) | No | WhatsApp has no posts concept | | Comments / reviews / mentions | No | Not applicable | ## Requesting access WhatsApp is in beta. Your account must have `whatsapp` in its `allowed_platforms` list before you can connect a number. Request access from the dashboard, or contact us. Once we approve the request, you can connect a WhatsApp Business number. ## What you need * A **WhatsApp Business phone number** that is not currently registered on the consumer WhatsApp or WhatsApp Business apps. During Embedded Signup you can select an existing number or create one. * **Admin access to a Meta Business** (business.facebook.com). You do **not** need to submit your own Meta App Review or complete business verification to connect. We are the Tech Provider, so the app review and provider-side setup are handled on our side. The person connecting signs in with their Meta Business account, picks their WhatsApp Business Account and phone number, and the number is connected. ## Connecting WhatsApp uses Meta's Embedded Signup, a three-leg flow that you drive from your own backend and frontend with your API key. Your customer only ever interacts with Meta's hosted popup; your API key stays server-side the whole time. (You can also connect from our dashboard, which runs this same flow for you.) ### Leg 1: start the connection (your backend) ```bash theme={null} curl -X POST https://api.social-api.ai/v1/accounts/connect \ -H "Authorization: Bearer $SOCIALAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "platform": "whatsapp" }' ``` Response (HTTP 202): ```json theme={null} { "auth_url": "", "state": "", "metadata": { "app_id": "...", "config_id": "...", "solution_id": "..." } } ``` The `metadata` is exactly what Meta's JavaScript SDK needs to launch the popup. Pass `state` through to your frontend, hold onto it for leg 3. ### Leg 2: launch the Meta popup (browser) Load Facebook's JavaScript SDK and call `FB.login` with the `config_id` and `solution_id` from leg 1: ```javascript theme={null} FB.login( (response) => { const code = response.authResponse?.code; // send code (plus waba_id + phone_number_id from the event below) to your backend }, { config_id: CONFIG_ID, response_type: "code", override_default_response_type: true, extras: { setup: { solutionID: SOLUTION_ID } }, }, ); ``` As the customer completes the popup, Meta fires the `WA_EMBEDDED_SIGNUP` message event carrying their `waba_id` and `phone_number_id`. Capture the `code` and both IDs, and send them to your backend. See Meta's [Embedded Signup](https://developers.facebook.com/docs/whatsapp/embedded-signup) docs for the exact SDK and event wiring. ### Leg 3: complete the connection (your backend) ```bash theme={null} curl -X POST https://api.social-api.ai/v1/oauth/exchange \ -H "Authorization: Bearer $SOCIALAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "platform": "whatsapp", "code": "", "metadata": { "state": "", "waba_id": "", "phone_number_id": "" } }' ``` We validate `state` against the one we issued to your API key, exchange the `code` for the customer's token, store the account, and return HTTP 201 with the connected account. The number's messages now flow into your inbox. ### Registering your domain The popup runs against our Meta app, and Meta's Login-for-Business SDK enforces an allowlist of domains that are permitted to launch it. To run `FB.login` from your own domain (for example, `app.yourcompany.com`), we have to register that domain in our Meta app first, so contact us to add it. If you would rather not host the popup yourself, connect from our dashboard instead. It runs the flow on our domain and works for any customer without a per-domain step. ## The 24-hour service window WhatsApp only lets a business send free-form messages within **24 hours** of the customer's most recent message. Each new inbound message from that customer resets the 24-hour timer. * **Inside the window:** you can send a free-form text reply from the inbox. * **Outside the window:** free-form replies are rejected with `whatsapp.window_closed`. To reach the customer, send an approved message template with `send-template` (see [Message templates](#message-templates) below), which is allowed at any time. ## Message templates Templates are pre-approved messages you can send outside the 24-hour window (order updates, authentication codes, and the like). You create, manage, and send them entirely through the API, scoped to a connected account. ### Create a template ```bash theme={null} curl -X POST https://api.social-api.ai/v1/platforms/whatsapp/accounts/{account_id}/templates \ -H "Authorization: Bearer $SOCIALAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "order_confirmation", "category": "UTILITY", "language": "en_US", "components": [ { "type": "BODY", "text": "Hello {{1}}, your order {{2}} is confirmed.", "example": { "body_text": [["Jordan", "A1234"]] } } ] }' ``` A new template starts in `PENDING`; Meta reviews it and the status moves to `APPROVED` or `REJECTED` asynchronously. `category` is one of `UTILITY`, `MARKETING`, or `AUTHENTICATION` (marketing content in a `UTILITY` template is auto-rejected). Call `POST /v1/platforms/whatsapp/templates/validate` first to catch field-level problems before you submit. ### Send a template ```bash theme={null} curl -X POST https://api.social-api.ai/v1/platforms/whatsapp/accounts/{account_id}/send-template \ -H "Authorization: Bearer $SOCIALAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "15551234567", "template_name": "order_confirmation", "language": "en_US", "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "Jordan" }, { "type": "text", "text": "A1234" } ] } ] }' ``` `to` is E.164 without the leading `+`. The template must already be `APPROVED` and `language` must match an approved variant. This send is allowed whether or not the 24-hour window is open, and the message is recorded in the inbox thread. Listing, editing, and deleting templates, plus phone-number management (`request-code`, `verify-code`, `register`, `deregister`, `status`) and the business profile, are also available. See the [API reference](/api-reference/introduction) for the full set of `/v1/platforms/whatsapp` endpoints and their schemas. ## Receiving and replying WhatsApp has no message-history API, so we build the inbox from inbound webhook events from connect time onward. There is no backfill of messages sent before you connected. ### List DM conversations ```bash theme={null} curl "https://api.social-api.ai/v1/inbox/conversations?account_id={id}" \ -H "Authorization: Bearer $SOCIALAPI_KEY" ``` ### Reply to a message ```bash theme={null} curl -X POST https://api.social-api.ai/v1/inbox/conversations/{conversation_id}/messages \ -H "Authorization: Bearer $SOCIALAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "account_id": "{account_id}", "text": "Thanks for reaching out!" }' ``` `account_id` is required. The reply is sent as plain text and only succeeds while the 24-hour service window is open. ## Errors | HTTP | Code | When it occurs | | ---- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | | 400 | `whatsapp.window_closed` | The 24-hour service window has closed. Free-form replies are no longer allowed; send an approved template with `send-template` instead. | | 404 | `whatsapp.template_not_found` | The named template does not exist on this account's WABA. | | 400 | `whatsapp.template_paused` | The template is paused for quality reasons and cannot be sent. | | 400 | `whatsapp.undeliverable` | The recipient is not a WhatsApp user. | | 403 | `whatsapp.user_opted_out` | The recipient has opted out of messages from this business. | | 429 | `whatsapp.frequency_cap` | The recipient has hit their daily marketing-message limit. | | 429 | `whatsapp.pair_rate_limit` | Too many messages sent to this same recipient. | | 429 | `whatsapp.throughput_exceeded` | The number's messaging throughput limit was exceeded. | | 401 | `platform.whatsapp.auth` | The connection is no longer authorized. Reconnect the account. | See [Errors](/guides/errors) for the full catalog. ## Related * [Platform support](/guides/platforms) * [Inbox](/guides/inbox) * [Errors](/guides/errors) # YouTube Source: https://docs.social-api.ai/connectors/youtube Connect and interact with YouTube channels via the YouTube Data API. Available **Managed connector** - SocialAPI handles the Google Cloud project and YouTube Data API access for you. No customer-side Google Cloud setup required. See [Platform credentials](/guides/platform-credentials). ## At a glance | Field | Value | | ------------- | ---------------------------------------------------------- | | Platform slug | `youtube` | | Auth type | OAuth 2.0 (Google) | | API | YouTube Data API v3 | | Best for | Comment moderation and metadata on owned YouTube channels. | ## Capabilities | Feature | Supported | Notes | | ------------------------------ | --------- | ------------------------------------------------------------ | | List posts | Yes | Returns uploaded videos | | Comments | Yes | Scoped to a specific video | | Reply to comment | Yes | | | Comment replies (thread) | Yes | | | Moderate comment (hide/delete) | Yes | Hide sets moderation status; delete removes permanently | | Like comment | No | Not supported by YouTube API | | Private reply | No | Not applicable | | DMs | No | Not supported by platform | | Reviews | No | Not applicable | | Mentions | No | Not supported via YouTube API | | Create post | Yes | Upload and publish videos | | Update post | Yes | Update title, description, tags, privacy, and other metadata | | Delete post | Yes | | | Edit own comment | Yes | PATCH /v1/platforms/youtube/comments/ | ## Connecting ```bash theme={null} curl -X POST https://api.social-api.ai/v1/accounts/connect \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "platform": "youtube", "redirect_uri": "https://app.example.com/oauth/callback", "state": "session_abc123" }' ``` Response: ```json theme={null} { "auth_url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&state=...", "state": "4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2" } ``` Redirect your user to `auth_url`. After they authorize, SocialAPI exchanges the code with YouTube and redirects the user to your `redirect_uri` with the connection result. See [OAuth flows](/guides/oauth#step-3-receive-the-result-at-your-redirect_uri) for the redirect parameters. ## Reading samples ### List posts ```bash theme={null} curl "https://api.social-api.ai/v1/posts?account_ids=acc_01HZ9X3Q4R5M6N7P8V2K0W1J" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ### List comments on a post ```bash theme={null} curl "https://api.social-api.ai/v1/inbox/comments/{videoId}" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Sample response: ```json theme={null} { "data": [ { "id": "sapi_cmt_eW91dHViZTpVZ3h...", "platform": "youtube", "type": "comment", "author": { "id": "UCxyz123", "name": "Jane Smith", "avatar_url": "https://yt3.ggpht.com/..." }, "content": { "text": "Great video!" }, "metadata": { "post_id": "dQw4w9WgXcQ", "like_count": 5, "has_replies": true }, "created_at": "2026-03-15T10:30:00Z", "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform_id": "Ugxyz..." } ], "count": 1 } ``` ## Publishing Create a video post by providing a video URL and optional metadata via `platform_data`. See the [API reference](/api-reference/introduction) for the full request schema. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Check out our latest video!", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "visibility": "public", "targets": [ { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform_data": { "category_id": "22", "tags": ["tutorial", "demo"], "made_for_kids": false, "embeddable": true, "notify_subscribers": true } } ] }' ``` ### PlatformData fields YouTube platform data can be supplied in two forms, which are merged before delivery (per-target values override top-level values on conflict): * **Top-level:** `platform_data.youtube.` applies to all YouTube targets in the request. * **Per-target:** `targets[].platform_data.` (flat keys, no nesting) overrides the top-level value for that target only. The example above uses this form. Fields supported in either form: | Field | Type | Default | Description | | -------------------------- | --------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `title` | string | none | Video title. If omitted, the title is inferred from the filename or URL. | | `category_id` | string | `"22"` (People & Blogs) | YouTube video category ID. See [YouTube category list](https://developers.google.com/youtube/v3/docs/videoCategories/list). | | `tags` | string\[] | none | Video tags for discoverability. | | `default_language` | string | none | BCP-47 language code (e.g. `"en"`, `"fr"`). | | `made_for_kids` | bool | `false` | Whether the video is made for children (COPPA). | | `embeddable` | bool | `true` | Whether the video can be embedded on other sites. | | `license` | string | `"youtube"` | `"youtube"` for standard YouTube license, `"creativeCommon"` for CC BY. | | `public_stats_viewable` | bool | `true` | Whether view counts and likes are publicly visible. | | `publish_at` | string | none | RFC 3339 timestamp for scheduled publish (e.g. `"2026-04-10T15:00:00Z"`). Only applies when `visibility` is `"private"`. | | `recording_date` | string | none | Date the video was recorded, in `YYYY-MM-DD` format. | | `contains_synthetic_media` | bool | `false` | Whether the video contains AI-generated or synthetic content. | | `notify_subscribers` | bool | `true` | Whether to send a notification to channel subscribers. | | `playlist_id` | string | none | YouTube playlist ID. The video is added to this playlist after publishing. | ## Channel summary Retrieve aggregate stats for a connected YouTube channel. ```bash theme={null} curl "https://api.social-api.ai/v1/platforms/youtube/accounts/{id}/summary" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Response: ```json theme={null} { "title": "Acme Corp", "handle": "@acmecorp", "thumbnail_url": "https://yt3.ggpht.com/...", "subscriber_count": 12400, "view_count": 980000, "video_count": 47 } ``` ## Force sync Force an immediate re-sync of uploaded videos for an account. ```bash theme={null} curl -X POST "https://api.social-api.ai/v1/platforms/youtube/accounts/{id}/sync" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Returns `{"success": true}` on success. ## Edit own comment To edit a comment you own, use the YouTube-specific PATCH endpoint. All three body fields are required. ```bash theme={null} curl -X PATCH "https://api.social-api.ai/v1/platforms/youtube/comments/{commentId}" \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "post_id": "dQw4w9WgXcQ", "text": "Updated comment text" }' ``` ## Limitations and gotchas * **No DMs or mentions.** YouTube does not expose a messaging or mention API. These endpoints return `501`. * **No webhook handler.** YouTube does not push real-time events. Posts and comments are fetched on demand. * **Comment moderation.** Hide sets the comment moderation status to rejected (removes it from public view). Delete removes it permanently. The dedicated moderation endpoint (`POST /v1/platforms/youtube/comments/{commentId}/moderate`) can also set `held_for_review` explicitly. Like is not supported. * **Token refresh.** Google OAuth tokens expire. SocialAPI handles refresh automatically. If you receive `401` with `code: "platform.youtube.auth"`, reconnect the account. ## Permissions SocialAPI's managed Google Cloud OAuth app already requests YouTube Data API v3 scopes for reading and managing comments on your owned channels. You don't need to set up a Google Cloud project. See [Platform credentials](/guides/platform-credentials). ## Related * [Platform credentials](/guides/platform-credentials) * [OAuth flows](/guides/oauth) * [Errors](/guides/errors) * [Interaction IDs](/guides/interaction-ids) # Announcements Source: https://docs.social-api.ai/guides/announcements Read scheduled maintenance windows, incidents, and product updates, and track which ones a user has seen. ## What announcements are Announcements are service notices we publish: scheduled maintenance, incidents, product updates, and breaking changes. They are the same for every customer, so there is no per-account targeting and nothing to subscribe to. The dashboard shows each announcement once as a modal. If you have built your own integration, poll the endpoint below so your users hear about a maintenance window before it starts rather than when calls begin failing. Announcements are **read-only**. There is no API for creating, editing, or deleting one. Both operations below are free and consume no credits. ## List active announcements Returns every announcement currently inside its visibility window, newest first. ```bash theme={null} curl https://api.social-api.ai/v1/announcements \ -H "Authorization: Bearer $SOCIALAPI_KEY" ``` ```json theme={null} { "data": [ { "id": "9c1e7f2a-3b4d-4e5f-8a9b-0c1d2e3f4a5b", "kind": "maintenance", "title": "Scheduled maintenance", "body": "We are upgrading our database infrastructure. Publishing and inbox reads will be briefly unavailable.", "link_url": "https://status.social-api.ai", "link_label": "Status page", "window_start": "2026-08-02T02:00:00Z", "window_end": "2026-08-02T04:00:00Z", "seen": false } ], "count": 1 } ``` ### Fields | Field | Notes | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | `kind` | One of `maintenance`, `incident`, `product_update`, `breaking_change`. | | `window_start`, `window_end` | The announced downtime, in UTC. Present for `maintenance`, absent for other kinds. Render these in the reader's local timezone. | | `link_url`, `link_label` | Optional. A place to send people for detail, usually the status page. | | `seen` | Whether **the user who owns this API key** has marked it seen. | An empty `data` array is the normal state and means there is nothing to announce. `window_start` and `window_end` describe the downtime being announced, not how long the announcement itself is displayed. An announcement is typically published days before its maintenance window opens. The visibility window is internal and is not exposed by the API. ## Mark an announcement as seen Records that the current user has seen an announcement so it is not shown again. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/announcements/9c1e7f2a-3b4d-4e5f-8a9b-0c1d2e3f4a5b/seen \ -H "Authorization: Bearer $SOCIALAPI_KEY" ``` ```json theme={null} { "ok": true } ``` The call is idempotent, so repeating it succeeds and changes nothing. Seen state is stored per user, not per API key, so dismissing an announcement in the dashboard also clears it for your API key and the other way around. Returns `404` if the ID is not a valid announcement, including the case where the announcement expired or was withdrawn between your list call and your dismissal. That is safe to ignore. # Authentication Source: https://docs.social-api.ai/guides/authentication How to authenticate requests using API keys or OAuth 2.1 access tokens. Every request to the SocialAPI `/v1` endpoints requires a Bearer token in the `Authorization` header. The API accepts two token types. ## Token types | Token type | Who uses it | Format | | -------------------------- | ------------------------------------- | ------------------------------------------ | | **API key** | External developers, API clients | `sapi_key_` prefix | | **OAuth 2.1 access token** | MCP clients (Claude, ChatGPT, Cursor) | Short-lived token issued by the OAuth flow | Most integrations use an API key. The OAuth flow is only for MCP clients. Tokens that start with `sapi_key_` are treated as API keys; any other token is treated as an OAuth 2.1 access token. ## API keys API keys are the primary authentication method for external integrations. ### Format ``` sapi_key_<62 hex characters> ``` ### Passing the key Include the key in the `Authorization` header: ```bash theme={null} curl https://api.social-api.ai/v1/accounts \ -H "Authorization: Bearer sapi_key_your_key_here" ``` If the key is missing or invalid, the API returns: ```json theme={null} { "error": { "code": "auth.invalid_key", "message": "invalid or inactive API key" }, "request_id": "a1b2c3d4" } ``` ### Creating keys Keys are created in the dashboard under **Keys** > **New Key**, or via the API: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/keys \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Production App"}' ``` The full key is returned **once** in `raw_key`. Store it immediately. Subsequent reads only show a truncated preview like `sapi_key_a1b2c...`. By default a key has full access to every brand and operation. To hand out a least-privilege or client-scoped key, pass `scopes` and `brand_ids` when you create it. See [Scoped API keys](/guides/scoped-keys). ### Rotating keys Issue a new secret for an existing key without changing its id, name, or restrictions: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/keys//rotate \ -H "Authorization: Bearer $SOCAPI_KEY" ``` The new `raw_key` is shown once, and the old secret stops working immediately. To change a key's name or restrictions in place without issuing a new secret, use `PATCH /v1/keys/`. ### Revoking keys ```bash theme={null} curl -X DELETE https://api.social-api.ai/v1/keys/ \ -H "Authorization: Bearer $SOCAPI_KEY" ``` A revoked key stops working immediately for all requests. ### Listing keys ```bash theme={null} curl https://api.social-api.ai/v1/keys \ -H "Authorization: Bearer $SOCAPI_KEY" ``` The response includes `preview` (suffix only), `is_active`, `last_used_at`, and each key's `scopes` and `brand_ids` restrictions (both empty means full access). See [Scoped API keys](/guides/scoped-keys). ## OAuth 2.1 access tokens (MCP clients) MCP clients (Claude, ChatGPT, Cursor) authenticate through the API's built-in OAuth 2.1 authorization server instead of a static API key. After the user completes the OAuth flow (see the [OAuth guide](/guides/oauth)), the client receives an access token to send as its Bearer token. Access tokens expire after **1 hour**. The client uses its refresh token to obtain a new access token without asking the user to re-authorize. ## Authentication error codes The API returns a consistent set of error codes when a request cannot be authenticated. Use `error.code` for programmatic handling. | HTTP | Code | When it occurs | | ---- | ------------------------- | --------------------------------------------------------------------------------------------------------------------- | | 401 | `auth.missing_header` | `Authorization` header is absent | | 401 | `auth.empty_token` | `Authorization: Bearer` with no token value | | 401 | `auth.invalid_key` | Token has the `sapi_key_` prefix but does not match any active key | | 401 | `auth.invalid_token` | OAuth access token is invalid or expired | | 401 | `auth.user_not_found` | Token resolves to a user that no longer exists | | 403 | `auth.dashboard_only` | Endpoint is available only in the dashboard, not to API keys | | 403 | `auth.insufficient_scope` | A [scoped key](/guides/scoped-keys) lacks the scope for this operation, or a restricted key called an admin operation | See [Error reference](/guides/errors#authentication) for the complete list including OAuth-specific codes. ## Security best practices * **Never expose keys in frontend code.** Keys must only be used server-side. * **Use environment variables.** Do not hardcode keys in source code. * **Create one key per service.** Revoke individual keys if compromised without disrupting others. * **Grant least privilege.** Restrict a key to the [scopes and brands](/guides/scoped-keys) it actually needs, so a leaked key cannot cross clients or perform actions it never should. * **Monitor `last_used_at`.** Unused keys should be revoked. * **Rotate OAuth refresh tokens.** The API enforces refresh token rotation. Each refresh grants a new token pair. # Core concepts Source: https://docs.social-api.ai/guides/concepts The five nouns SocialAPI's API exposes (Brand, Account, Post, Interaction, Capability) and how they fit together. SocialAPI.ai is a unified social media API: one REST endpoint and MCP server to read and respond to comments, direct messages, mentions, and reviews across Instagram, Facebook, TikTok, LinkedIn, YouTube, X, Threads, WhatsApp, Telegram, Google Business Profile, and Pinterest. Every platform returns the same `Interaction` schema. SocialAPI's REST API is shaped around five nouns. Once you know what each one means, every endpoint slots into place: brands group accounts, accounts hold posts and interactions, and capabilities tell you which actions are valid for any given interaction. This page is a short tour. The [API Reference](/api-reference/introduction) has the exhaustive field-by-field schema for each one. ## Brand A **Brand** (called a *social profile* in pricing) is the top-level grouping in SocialAPI. One brand corresponds to one business, client, or location. All connected social accounts, posts, and interactions live under a brand. Brands matter for two reasons: 1. **Billing.** Plans are sized in social profiles (brands). Free includes 2, Starter 10, Pro 50, Business 200, Enterprise unlimited. 2. **Isolation.** Accounts under one brand cannot see accounts under another. Use brands to keep agency clients separated, or to split staging from production. ### Profile units The numbers above are unit limits, not a strict brand count. Billing counts **profile units**: each brand costs one unit. Profile units track brand count only, Facebook Pages do not add units. Facebook Pages have their own limit instead: each brand can connect up to 5 Facebook Pages (see [Facebook: one login, many brands](/connectors/facebook#one-login-many-brands)), accumulated one at a time through the [Facebook logins endpoints](/connectors/facebook#managing-pages-after-connecting). This is a hard cap, not a billed allowance: there is no charge for staying under it, and assigning a Page beyond it returns `403 platform.facebook.page_cap` rather than a unit-limit error. `GET /v1/usage` reports where you stand: ```json theme={null} { "profile_units_used": 4, "profile_units_limit": 10, "page_allowance": 5, "page_billing_exempt": false } ``` | Field | Meaning | | --------------------- | ----------------------------------------------------------------------------------------------- | | `profile_units_used` | Units consumed so far: brand count. | | `profile_units_limit` | Your plan's total unit allowance (`-1` for unlimited, Enterprise). | | `page_allowance` | Facebook Pages allowed per brand before further assignment is refused (`5`). | | `page_billing_exempt` | Legacy flag from the earlier page-billing model. Page assignment is not billed for any account. | ```json theme={null} { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "Acme Corp", "created_at": "2026-04-12T09:30:00Z" } ``` See [`/v1/brands`](/api-reference/introduction) for the full schema and CRUD endpoints. Treat IDs as opaque strings. Brands, posts, and invites are UUIDs; accounts are prefixed with `acc_`. Do not parse them or assume a format, and store them as text. See [IDs are opaque strings](/guides/reliability#ids-are-opaque-strings). Brand creation is not idempotent: there is no `external_id` or idempotency key, and brand names are not unique, so a retried `POST /v1/brands` creates a duplicate. Persist the returned brand ID immediately, and check for an existing brand before creating another. ## Account An **Account** is a connected social account under a brand. One brand can hold many accounts, across any mix of platforms. SocialAPI stores the OAuth tokens (encrypted at rest) and refreshes them automatically. You connect an account by calling `POST /v1/accounts/connect` and walking the user through OAuth. After connection, the account ID (prefixed `acc_`) is what you pass to every account-scoped endpoint. ```json theme={null} { "id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform": "instagram", "name": "Acme Corp", "username": "acmecorp", "profile_picture_url": "https://scontent.cdninstagram.com/v/...", "bio": "Sustainable apparel. Shipping worldwide." } ``` The full Account schema, including the per-platform `metadata` object, is documented in [Connectors overview](/connectors/overview#account-object). See also: [Authentication](/guides/authentication), [OAuth](/guides/oauth). ## Post A **Post** is one piece of outbound content. A post can target multiple accounts at publishing time, so a single `POST /v1/posts` call can fan out to Instagram, Facebook, and Threads in one shot. Internally a post has two parts: the post itself (text, media, scheduled time) and a per-platform delivery row that tracks status, errors, and engagement metrics for each target. That split is what lets a single post object report "published on Instagram, failed on Facebook" cleanly. ```json theme={null} { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "text": "Spring collection is live.", "status": "published", "scheduled_at": null, "targets": [ { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform": "instagram", "status": "published", "platform_post_id": "17895695668004550", "permalink": "https://www.instagram.com/p/ABC123/", "metrics": { "like_count": 42, "comments_count": 5 } } ], "created_at": "2026-04-15T11:00:00Z" } ``` Posts move through a state machine: `draft`, `scheduled`, `publishing`, then `published` (or `partial`, `failed`). See the [Posts tab](/posts/overview) for per-platform field references. ## Interaction An **Interaction** is anything inbound: a comment on one of your posts, a DM, a review, or a mention. Comments, reviews, and mentions are proxied live from the platform; DM conversations and messages are stored in SocialAPI's database to support threading and pagination. The API mints stable IDs so your app can reference any interaction across calls. Interaction IDs encode their type as a prefix: | Prefix | Type | | ----------- | ------- | | `sapi_cmt_` | Comment | | `sapi_dm_` | DM | | `sapi_rev_` | Review | | `sapi_mnt_` | Mention | ```json theme={null} { "id": "sapi_cmt_GXRM4BNTW7KFYSD3HAEV6QJZ2CLP9U", "platform": "instagram", "type": "comment", "author": { "id": "17841405793187218", "name": "Jane Smith" }, "content": { "text": "Love this product!" }, "created_at": "2026-04-15T14:30:00Z", "capabilities": { "can_reply": true, "can_delete": true, "can_hide": true, "can_like": false, "can_private_reply": true } } ``` The prefix lets the API dispatch a reply without a database lookup. See [Interaction IDs](/guides/interaction-ids) for the encoding details. ## Capability A **Capability** is a per-platform per-feature flag returned on every interaction so your code knows which actions are valid right now. Instead of branching on platform names ("if instagram, can like; if linkedin, can not"), you read the `capabilities` object on the interaction and disable UI controls accordingly. ```json theme={null} { "capabilities": { "can_reply": true, "can_delete": true, "can_hide": true, "can_like": false, "can_private_reply": true } } ``` If you call an action whose flag is `false`, the API returns `501` with `error.code: "resource.not_supported"`. Treat 501 as a permanent platform limitation, not a bug. See [Platform support](/guides/platforms) for the full per-platform breakdown. # Debugging with request IDs Source: https://docs.social-api.ai/guides/debugging Use X-Request-ID to correlate API calls with server-side logs. Every API response includes an `X-Request-ID` header. The same value appears in every error response body as `request_id`. Include this ID when reporting an issue to support and we can find the full server-side trace for your call in seconds. ## Supplying your own ID If your application already generates correlation IDs (for tracing across your own services), send them on the `X-Request-ID` request header. The server uses your ID when it matches `^[a-zA-Z0-9_-]{1,64}$` and falls back to generating an 8-char hex ID otherwise. ## Example ```bash theme={null} curl -i -H "X-Request-ID: my-trace-abc-123" https://api.social-api.ai/v1/accounts ``` The response includes `X-Request-ID: my-trace-abc-123` on success and on error. ## Error response shape ```json theme={null} { "error": { "code": "auth.invalid_token", "message": "Bearer token is invalid" }, "request_id": "my-trace-abc-123" } ``` When something goes wrong, copy the `request_id` from the response body (or the `X-Request-ID` response header) and include it in your support email or message. That lets us pull the full log trace for the failing request without you having to re-run anything. ## Logging request IDs in your application It is good practice to log the `X-Request-ID` from every response alongside your own trace context. Here is a minimal example in TypeScript: ```typescript theme={null} const resp = await fetch("https://api.social-api.ai/v1/accounts", { headers: { Authorization: `Bearer ${apiKey}`, "X-Request-ID": myCorrelationId, // your own trace ID }, }); const requestId = resp.headers.get("X-Request-ID"); if (!resp.ok) { const body = await resp.json(); console.error("API error", { code: body.error.code, message: body.error.message, request_id: requestId, // same as body.request_id }); } ``` # Error Handling Source: https://docs.social-api.ai/guides/errors Error response format and the catalog of error codes returned by the API. All errors return JSON with a consistent shape: ```json theme={null} { "error": { "code": "namespace.specific_code", "message": "human-readable message", "meta": { "field": "..." } }, "request_id": "a1b2c3d4" } ``` Use `error.code` for programmatic handling; use `error.message` for logging and display. Some errors include an `error.meta` object with structured context (for example `meta.field` for a validation failure, `meta.plan` and `meta.max` for billing limits). Codes are stable and namespaced with dot-separated segments, so you can match on a prefix (for example `platform.*` for any upstream platform failure, or `byok.*` for any bring-your-own-key issue). The `request_id` field is present on every error response. The same value appears in the `X-Request-ID` response header. Include it when reporting an issue to support. See [Debugging with request IDs](/guides/debugging) for details on how to supply your own correlation ID. ## Code namespaces The first segment of the code tells you where the failure originated. | Namespace | Meaning | | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | | `auth.*` | Authentication and authorization at the SocialAPI layer | | `account.*` | Connected account issues (linking, state, supported features) | | `platform..*` | The upstream platform (Instagram, TikTok, etc.) returned an error | | `validation.*` | Request body, query parameter, or path parameter is invalid | | `resource.*` | Generic resource lookup, conflict, or capability error | | `billing.*` | Plan limit reached or feature gated by tier | | `brand.*`, `media.*`, `post.*`, `conversation.*`, `invite.*` | Resource-specific errors on those object types | | `byok.*` | Bring-your-own-key credentials (Twitter app, etc.) | | `publishing.delivery.*` | A platform delivery worker failed mid-publish | | `whatsapp.*` | WhatsApp Cloud API specific failures (window, frequency, template state) | | `telegram.*` | Telegram Bot API specific failures (e.g. the bot cannot message a user who has not started it) | | `system.internal` | Unexpected server error; safe to retry idempotent requests | | `http.` | Fiber framework HTTP error (for example, `http.404` for an unknown route). Not returned by application logic. | ## Authentication | HTTP | Code | When it occurs | | ---- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 401 | `auth.missing_header` | `Authorization` header is absent | | 401 | `auth.empty_token` | `Authorization: Bearer` with no token value | | 401 | `auth.invalid_key` | API key does not match any active key | | 401 | `auth.invalid_token` | Bearer JWT or OAuth access token is invalid or expired | | 403 | `auth.invalid_secret` | Provided client secret does not match | | 401 | `auth.not_authenticated` | Caller is unauthenticated for an endpoint that requires it | | 401 | `auth.not_provisioned` | Dashboard user has no `users` row yet; call `POST /v1/users/provision` first | | 401 | `auth.user_not_found` | Token resolves to a user that no longer exists | | 403 | `auth.dashboard_only` | Endpoint accepts only dashboard JWTs (raw API keys are rejected) | | 403 | `auth.requires_dashboard_auth` | Same as above for endpoints behind brand-level UI | | 403 | `auth.insufficient_scope` | A [scoped API key](/guides/scoped-keys) lacks the required scope, or a restricted key called an admin operation. `meta.required_scope` names the scope when applicable | | 403 | `auth.state_mismatch` | OAuth callback `state` does not match the issued value | | 400 | `auth.oauth.invalid_client` | OAuth 2.1 client lookup failed during MCP token exchange | | 400 | `auth.oauth.invalid_redirect` | `redirect_uri` is not in the client's whitelist | | 400 | `auth.oauth.request_expired` | Authorization request expired before exchange | | 401 | `auth.oauth.user_missing` | OAuth grant references a user that does not exist | ## Accounts | HTTP | Code | When it occurs | | ---- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | 404 | `account.not_found` | The account ID does not belong to your workspace | | 409 | `account.already_linked` | This social account is already connected to a workspace | | 403 | `account.reconnection_required` | OAuth token is no longer usable; rerun the OAuth flow | | 400 | `account.unsupported_platform` | Platform value is not recognized | | 400 | `account.no_active_page` | Account has no active Page (Facebook, LinkedIn) selected | | 400 | `account.page_inactive` | The requested Page is currently disabled | | 400 | `account.pages_not_supported` | Pages do not apply to this platform | | 409 | `account.different_login` | Brand's Facebook account is tied to a different login (shared credential); disconnect it before connecting a new one | ## Validation | HTTP | Code | When it occurs | | ---- | ----------------------------------- | ---------------------------------------------------------------------------------------------- | | 400 | `validation.field_required` | A required field is missing from the request | | 400 | `validation.field_invalid` | A field has the wrong type, format, or value | | 400 | `validation.body_invalid` | Request body could not be parsed as JSON | | 400 | `validation.redirect_uri_duplicate` | Redirect URI is already registered for this client | | 400 | `validation.scope_invalid` | A [key](/guides/scoped-keys) was created or edited with an unknown scope; the message names it | | 400 | `validation.brand_invalid` | A key's `brand_ids` references a brand that does not exist or that you do not own | The `code` always starts with `validation.`. The specific sub-code after the dot identifies the field or rule that failed; `error.meta.field` in the response points to the offending field name when applicable. ## Resources | HTTP | Code | When it occurs | | ---- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | 404 | `resource.not_found` | Generic resource lookup miss; check the `error.meta.resource` and `error.meta.id` fields | | 409 | `resource.conflict` | Resource is in a state that conflicts with the requested action | | 410 | `resource.gone` | Resource existed but was permanently removed | | 501 | `resource.not_supported` | Operation does not apply to this platform | | 404 | `brand.not_found`, `media.not_found`, `conversation.not_found`, `post.not_found` | Specific lookups failed | | 409 | `media.in_use` | Cannot delete media that is still referenced by a post | | 410 | `invite.expired` | Brand invite token is past its expiry | | 400 | `invite.invalid` | Brand invite token is malformed or already consumed | | 409 | `post.state_invalid` | Action does not apply to a post in this state (e.g. retry on a `published` post) | | 400 | `post.no_retryable_deliveries` | No failed deliveries on this post to retry | ### Invite conflicts and validation `POST /v1/invites` returns `409 resource.conflict` for two different situations, distinguished by the `message`: * `an active invite already exists for this brand and platform` * `brand already has a connected account for this platform` Both share the `resource.conflict` code. Note that `GET /v1/invites` and `GET /v1/accounts` query different objects, so an empty invite list does not guarantee that a create will succeed: the brand may already have a connected account for that platform. Check both before treating a create as safe. `expires_in_days` is required on invite creation and must be one of `1`, `3`, `7`, `14`, or `30`. Any other value (including omitting it) returns `400 validation.field_invalid` with the message `expires_in_days must be one of: 1, 3, 7, 14, 30`. ## Billing and plan limits | HTTP | Code | When it occurs | | ---- | --------------------------------------------- | ---------------------------------------------------------------------------- | | 403 | `billing.brands_limit`, `brand.limit_reached` | Brand count would exceed your plan's allowance | | 403 | `billing.profile_units_limit` | Profile unit count would exceed your plan's allowance (creating a new brand) | | 429 | `billing.post_limit` | Monthly post allowance is exhausted | | 413 | `billing.storage_quota` | Media upload would exceed your storage limit | | 403 | `billing.platform_not_available` | Platform requires a higher plan tier | | 429 | `billing.export_limit` | Daily export allowance is exhausted | | 429 | `billing.export_concurrent` | Too many exports running at once for this plan | | 429 | `billing.export_cooldown` | Cooldown active between exports | Limit responses include `error.meta.plan` and `error.meta.max` so clients can prompt the user to upgrade. ## Platform errors When the upstream platform (Meta, TikTok, Google, etc.) rejects a call, the error code has the form `platform..`. The HTTP status is forwarded when possible; otherwise it is `502 Bad Gateway`. | HTTP | Code | When it occurs | | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | | 401 | `platform..auth` | Platform rejected the stored OAuth token; the account needs to be reconnected | | 429 | `platform..rate_limit` | Upstream platform rate limit hit. This does **not** consume your SocialAPI quota | | 502 | `platform..api_error` | Generic upstream API failure; check the `error` message for detail | | 400 | `platform.tiktok.media_required` | TikTok requires media for this post type | | 403 | `platform.instagram.permission_denied`, `platform.facebook.permission_denied`, `platform.whatsapp.permission_denied` | OAuth scopes do not include the action | | 400 | `platform.instagram.window_closed`, `platform.facebook.window_closed` | The 24-hour messaging window has closed; the user must message you first to reopen it | | 404 | `platform.facebook.no_pages`, `platform.linkedin.no_pages`, `platform.google.no_locations`, `platform.youtube.no_channels`, `platform.youtube.no_uploads_playlist` | The connected account exposes no usable target | | 403 | `platform.facebook.page_cap` | Brand already has the maximum of 5 Facebook Pages; this is a hard cap, not billed | ## BYOK (bring-your-own-key) For platforms that require a customer-supplied OAuth app (currently Twitter / X). | HTTP | Code | When it occurs | | ---- | ---------------------------- | -------------------------------------------------------------------------- | | 412 | `byok.credentials_missing` | No credentials configured for the user or brand; show the BYOK setup modal | | 401 | `byok.credentials_invalid` | Stored credentials were rejected by the platform | | 403 | `byok.credentials_forbidden` | Caller does not own the brand they are configuring | | 429 | `byok.test_rate_limited` | Too many credential test calls within a minute | ## Publishing delivery These are emitted by the post delivery workers and surface in the inbox / events stream rather than as direct HTTP responses, but they appear in `event.error_code` for `post.failed` and `post.partial` events. | Code | Meaning | | ----------------------------------------- | --------------------------------------------------------- | | `publishing.delivery.account_load_failed` | Could not load the connected account at delivery time | | `publishing.delivery.page_resolve_failed` | Could not resolve the target Page (Facebook, LinkedIn) | | `publishing.delivery.timeout` | Delivery exceeded the per-platform timeout | | `publishing.duplicate_target` | Two deliveries targeting the same destination were queued | ## WhatsApp WhatsApp Cloud API has a richer error surface than other platforms because of its messaging window, frequency caps, and template state machine. | HTTP | Code | When it occurs | | ---- | ------------------------------ | ---------------------------------------------------------------------------- | | 400 | `whatsapp.window_closed` | 24-hour customer service window is closed; send an approved template instead | | 429 | `whatsapp.frequency_cap` | Per-user marketing cap reached | | 429 | `whatsapp.pair_rate_limit` | Per phone-number-pair rate limit hit | | 429 | `whatsapp.throughput_exceeded` | Account-level throughput exhausted | | 403 | `whatsapp.user_opted_out` | Recipient has opted out of marketing messages | | 400 | `whatsapp.undeliverable` | Message could not be delivered (cold number, region block, etc.) | | 404 | `whatsapp.template_not_found` | Referenced template does not exist for this WABA | | 409 | `whatsapp.template_paused` | Template is paused due to quality drop | | 404 | `whatsapp.no_account` | No WhatsApp account is connected for this request | | 500 | `whatsapp.unknown` | Unmapped WhatsApp error; check the message for raw detail | ## Telegram | HTTP | Code | When it occurs | | ---- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | 403 | `telegram.cannot_message` | The bot cannot message this chat. The user must start the bot first, or the bot was blocked or removed. There is no 24-hour window once started. | ## System | HTTP | Code | When it occurs | | ---- | ----------------- | ---------------------------------------------------------------------------------- | | 500 | `system.internal` | Unexpected server error. Retry idempotent requests; if persistent, contact support | ## Handling common cases ### Reconnect when a token dies `account.reconnection_required` and `platform..auth` both mean the stored OAuth token is no longer usable. Send the user back through `GET /v1/oauth/url`. ```python theme={null} err = response.json()["error"] code = err["code"] if code == "account.reconnection_required" or \ (code.startswith("platform.") and code.endswith(".auth")): redirect_to_reconnect(account_id) ``` ### Storage quota `billing.storage_quota` (HTTP 413) means the upload would push you past your plan's storage limit. Check current usage with `GET /v1/media/storage` and delete unused media or upgrade. ### Monthly post or interaction limit `billing.post_limit` (HTTP 429) means your monthly allowance is exhausted. Limits reset at the start of your next billing period. The free plan uses a rolling 30-day window from your account anniversary. ### Platform throttling `platform..rate_limit` (HTTP 429) is the upstream platform throttling you, not SocialAPI. It does not consume your quota. Wait a few minutes and retry with backoff. ### Validation failures Any `validation.*` code includes an `error.meta.field` pointing to the offending input. Surface the field name to the user. ## Example error handling ```typescript theme={null} const resp = await fetch(`https://api.social-api.ai/v1/accounts/${accountId}/comments`, { headers: { Authorization: `Bearer ${apiKey}` } }); if (!resp.ok) { const body = await resp.json(); const { code, message, meta } = body.error; const requestId = body.request_id; // include in support reports // Reconnect dead OAuth tokens if (code === "account.reconnection_required" || (code.startsWith("platform.") && code.endsWith(".auth"))) { await reconnectAccount(accountId); return; } // Upstream platform throttling: retry with backoff if (code.startsWith("platform.") && code.endsWith(".rate_limit")) { await sleep(60_000); return retry(); } // Plan limits: prompt to upgrade if (code.startsWith("billing.")) { throw new Error(`Plan limit hit: ${message}`); } // BYOK setup needed if (code === "byok.credentials_missing") { showByokSetupModal(meta?.platform); return; } // Operation not supported on this platform if (code === "resource.not_supported") { console.warn(`Not supported: ${message}`); return null; } throw new Error(`API error: ${message} (${code}) [request_id: ${requestId}]`); } ``` # Unified inbox Source: https://docs.social-api.ai/guides/inbox Read and respond to comments, DMs, reviews, and mentions across platforms through a single social media inbox API. The inbox is a unified social inbox API: a single endpoint that returns comments, direct messages, mentions, and reviews from every connected platform in one normalized `Interaction` shape, so you build inbox logic once instead of per platform. SocialAPI provides a unified inbox API for reading and responding to all inbound engagement across your connected accounts. Comments, DMs, reviews, and mentions are accessed through consistent endpoints with the same response shape regardless of platform. SocialAPI proxies comments, reviews, and mentions live from the platform API, so you always see the latest state. DM conversations and messages are stored in SocialAPI's database to support threading and pagination. ## Comments List comments across all connected accounts, reply, and moderate. ### Endpoints | Method | Path | Description | | -------- | ----------------------------------------------------- | ------------------------------------------------- | | `GET` | `/v1/inbox/comments` | List posts that received comments across accounts | | `GET` | `/v1/inbox/comments/:postId` | List comments on a specific post | | `GET` | `/v1/inbox/comments/:postId/:commentId/replies` | List replies to a specific comment | | `POST` | `/v1/inbox/comments/:postId` | Reply to a comment on a post | | `DELETE` | `/v1/inbox/comments/:postId/:commentId` | Delete a comment | | `POST` | `/v1/inbox/comments/:postId/:commentId/hide` | Hide a comment | | `DELETE` | `/v1/inbox/comments/:postId/:commentId/hide` | Unhide a comment | | `POST` | `/v1/inbox/comments/:postId/:commentId/like` | Like a comment | | `DELETE` | `/v1/inbox/comments/:postId/:commentId/like` | Unlike a comment | | `POST` | `/v1/inbox/comments/:postId/:commentId/private-reply` | Send a private DM in reply to a comment | `:postId` accepts a platform post ID (e.g., `17895695668004550`). `:commentId` accepts a SocialAPI interaction ID (`sapi_cmt_...`). See [Interaction IDs](/guides/interaction-ids) for the encoding. Comment likes are supported on Facebook and LinkedIn Page only (see the matrix below); other platforms return `501` with code `resource.not_supported`. Private replies (Instagram and Facebook) are limited by Meta to one per comment, within 7 days of the comment. On Facebook the commenter's response continues as a normal Messenger conversation; on Instagram a private reply does not open a standard 24-hour messaging window. ### Platform capabilities | Platform | Reply | Hide | Like | Delete | Private reply | | ------------- | ----- | ----------- | ---- | ----------- | ------------- | | Instagram | Yes | Yes | No | Yes | Yes | | Facebook | Yes | Yes | Yes | Yes | Yes | | Threads | Yes | Conditional | No | Conditional | No | | Google | Yes | No | No | No | No | | LinkedIn Page | Yes | No | Yes | Yes | No | | LinkedIn | Soon | No | No | No | No | | YouTube | Yes | Yes | No | Yes | No | | X / Twitter | Yes | No | No | No | No | | TikTok | No | No | No | No | No | For Threads, Hide is available on comments not owned by the user; Delete is available on own replies. LinkedIn Page (`linkedin_page`) is the organization page connector and is available in beta. Each comment in the response includes a `capabilities` object so your code can check what is actually allowed for that specific item. ### Example: list and reply ```bash theme={null} # List posts that have comments curl https://api.social-api.ai/v1/inbox/comments \ -H "Authorization: Bearer $SOCAPI_KEY" # List comments on one post curl "https://api.social-api.ai/v1/inbox/comments/17895695668004550" \ -H "Authorization: Bearer $SOCAPI_KEY" # Reply to a specific comment on a post (postId is the platform post ID) curl -X POST "https://api.social-api.ai/v1/inbox/comments/17895695668004550" \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "comment_id": "sapi_cmt_GXRM4BNTW7KFYSD3HAEV6QJZ2CLP9U", "text": "Thanks for the feedback!" }' # Hide a comment curl -X POST "https://api.social-api.ai/v1/inbox/comments/{postId}/{commentId}/hide" \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }' ``` ## Direct messages Read conversations and send messages across platforms that support DMs. ### Endpoints | Method | Path | Description | | ------- | -------------------------------------- | ------------------------------------------------------------ | | `GET` | `/v1/inbox/conversations` | List DM conversations | | `GET` | `/v1/inbox/conversations/:id` | Get a single conversation | | `PATCH` | `/v1/inbox/conversations/:id` | Update a conversation (set status to `active` or `archived`) | | `POST` | `/v1/inbox/conversations/:id/read` | Mark a conversation as read | | `GET` | `/v1/inbox/conversations/:id/messages` | Get messages in a conversation | | `POST` | `/v1/inbox/conversations/:id/messages` | Send a message | ### List conversations query parameters | Parameter | Type | Default | Description | | ------------ | ------- | -------- | -------------------------------------------------- | | `account_id` | string | | Filter by connected account ID | | `platform` | string | | Filter by platform (e.g., `instagram`, `facebook`) | | `status` | string | `active` | Conversation status: `active` or `archived` | | `limit` | integer | `25` | Page size (1-100) | | `cursor` | string | | Pagination cursor from the previous response | ### Platform support | Platform | DMs supported | Notes | | --------- | ------------- | ----------------------------------------------- | | Instagram | Yes | 24-hour messaging window for non-followers | | Facebook | Yes | Via Messenger Send API, 24-hour response window | | LinkedIn | No | Platform API does not expose DMs | | TikTok | No | Not available via API | | Google | No | Not applicable | | Threads | No | Not available via API | | YouTube | No | Not available via API | ### Sending text and attachments A message can carry text, an attachment, or both. Provide `text`, `attachment_url`, or both: at least one is required. | Field | Required | Description | | ---------------- | ----------- | ------------------------------------------------------------------------------- | | `account_id` | Yes | Connected account that owns the conversation | | `text` | Conditional | Message body. Required unless `attachment_url` is set | | `attachment_url` | Conditional | Publicly reachable URL of a media file to attach. Required unless `text` is set | On Facebook and Instagram, `attachment_url` is delivered as a Messenger attachment. The attachment type (image, video, audio, or file) is inferred from the URL file extension, so use a direct media link with the correct extension (for example `.jpg`, `.mp4`, `.mp3`). URLs without a recognized media extension are sent as a generic file. The media must be hosted on a publicly reachable URL (max 25 MB); the platform fetches it directly. ### Example: list conversations and send a message ```bash theme={null} # List conversations curl https://api.social-api.ai/v1/inbox/conversations \ -H "Authorization: Bearer $SOCAPI_KEY" # Send a text message in a conversation curl -X POST https://api.social-api.ai/v1/inbox/conversations/sapi_dm_GXRM4BNTW7KFYSD3HAEV6QJZ2CLP9U/messages \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "account_id": "acc_abc123", "text": "Your order has shipped!" }' # Send an image attachment (text optional) curl -X POST https://api.social-api.ai/v1/inbox/conversations/sapi_dm_GXRM4BNTW7KFYSD3HAEV6QJZ2CLP9U/messages \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "account_id": "acc_abc123", "text": "Here is your receipt", "attachment_url": "https://cdn.example.com/receipt.jpg" }' ``` ## Reviews Read and reply to reviews on platforms that support them. ### Endpoints | Method | Path | Description | | ------ | ----------------------------- | ----------------- | | `GET` | `/v1/inbox/reviews` | List reviews | | `POST` | `/v1/inbox/reviews/:id/reply` | Reply to a review | ### Platform support | Platform | Reviews | | ---------- | ----------------------------- | | Google | Yes (Google Business Profile) | | All others | No | ### Example ```bash theme={null} # List reviews curl https://api.social-api.ai/v1/inbox/reviews \ -H "Authorization: Bearer $SOCAPI_KEY" # Reply to a review curl -X POST https://api.social-api.ai/v1/inbox/reviews/sapi_rev_GXRM4BNTW7KFYSD3HAEV6QJZ2CLP9U/reply \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "text": "Thank you for the review!" }' ``` ## Mentions Read mentions of a connected account, and reply to them as a public comment. ### Endpoints | Method | Path | Description | | ------ | --------------------------------- | -------------------------------------- | | `GET` | `/v1/accounts/:id/mentions` | List mentions for a connected account | | `POST` | `/v1/accounts/:id/mentions/reply` | Reply to a mention as a public comment | Mentions are scoped to a single connected account, not the workspace. Replying works even when the mention is on someone else's post, where the normal comment-reply endpoint is not allowed. Pass `media_id` (the post the mention is on, or the `sapi_mnt_` id from the list response) and `text`. Set `comment_id` only when the mention was inside a comment; omit it to reply to a mention in the post caption. A reply consumes 1 interaction credit. ### Platform support | Platform | List mentions | Reply to mention | | ----------- | ------------- | ---------------- | | Instagram | Yes | Yes | | Facebook | Yes | Yes | | X / Twitter | Yes | No | | All others | No | No | ### Example ```bash theme={null} # List mentions curl "https://api.social-api.ai/v1/accounts/acc_01HZ9X3Q4R5M6N7P8V2K0W1J/mentions" \ -H "Authorization: Bearer $SOCAPI_KEY" # Reply to a comment mention as a public comment curl -X POST https://api.social-api.ai/v1/accounts/acc_01HZ9X3Q4R5M6N7P8V2K0W1J/mentions/reply \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"media_id": "17895695668004550", "comment_id": "17858893269577012", "text": "Thanks for the shout-out!"}' ``` ## Interaction IDs Every interaction returned by the inbox has a stable ID with a type prefix: | Prefix | Type | | ----------- | ------- | | `sapi_cmt_` | Comment | | `sapi_dm_` | DM | | `sapi_rev_` | Review | | `sapi_mnt_` | Mention | The suffix encodes the platform and the platform-native ID, so you can route actions without a database lookup. See [Interaction IDs](/guides/interaction-ids) for the full encoding scheme. ## Webhooks SocialAPI can notify your server when new inbound interactions arrive (new comment, new DM, new review). Register a webhook endpoint and subscribe to the event types you care about. See [Webhooks](/guides/webhooks) for setup and event reference. ## Related * [Platform support](/guides/platforms) for the full capability matrix * [Interaction IDs](/guides/interaction-ids) for the ID encoding scheme * [Webhooks](/guides/webhooks) for real-time event delivery * [API Reference](/api-reference/introduction) for complete endpoint schemas # Interaction IDs Source: https://docs.social-api.ai/guides/interaction-ids How SocialAPI IDs encode type and platform. Every comment, DM, review, and mention returned by the API has a stable, opaque `id` field. These IDs are designed to be stored and reused across calls. ## Format ``` sapi_{type}_{32 character payload} ``` The 32-character payload encodes `platform:platformID` using a SocialAPI custom base-33 alphabet. It is reversible: given the ID, the API recovers the original platform and the platform's native ID without a database lookup. The prefix tells you the interaction type: | Prefix | Type | | ----------- | -------------- | | `sapi_cmt_` | Comment | | `sapi_rev_` | Review | | `sapi_dm_` | Direct message | | `sapi_mnt_` | Mention | Examples: * `sapi_cmt_GXRM4BNTW7KFYSD3HAEV6QJZ2CLP9U` — a comment * `sapi_rev_GXRM4BNTW7KFYSD3HAEV6QJZ2CLP9U` — a review * `sapi_dm_GXRM4BNTW7KFYSD3HAEV6QJZ2CLP9U` — a DM The payload alphabet uses `A-Z` and `1-9` (excluding `0`, `O`, and `8`) in a shuffled, non-standard order, so do not assume the payload is base64url or hex. ## Why this matters The prefix lets handlers route requests without a lookup. Each inbox endpoint accepts the appropriate prefix in its path parameter: * Comment routes (`POST /v1/inbox/comments/:postId`, `POST /v1/inbox/comments/:postId/:commentId/hide`, etc.) accept a platform post ID in `:postId` and a `sapi_cmt_` interaction ID in `:commentId`. * Review routes (`POST /v1/inbox/reviews/:id/reply`) accept `sapi_rev_` IDs in `:id`. * Conversation routes (`POST /v1/inbox/conversations/:id/messages`) accept `sapi_dm_` thread IDs in `:id`. REST routes do not enforce a prefix type check: passing a mismatched prefix ID (for example, a `sapi_rev_` ID on a comment route) decodes silently and the raw platform ID is forwarded to the connector. MCP tools, by contrast, validate the prefix and return an error if the type does not match the operation. ## Stability IDs are deterministic. The same `(type, platform, platform_id)` tuple always produces the same SocialAPI ID, so you can safely store IDs in your own database and join against them across calls. ## Using IDs List comments on a connected account, then reply to one using the comment's `id`: ```bash theme={null} # 1. List commented posts for the account curl "https://api.social-api.ai/v1/inbox/comments" \ -H "Authorization: Bearer $SOCAPI_KEY" # 2. Reply to a specific comment on a post (postId is the platform post ID) curl -X POST \ "https://api.social-api.ai/v1/inbox/comments/17895695668004550" \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "comment_id": "sapi_cmt_GXRM4BNTW7KFYSD3HAEV6QJZ2CLP9U", "text": "Thank you!" }' ``` Hide a comment: ```bash theme={null} curl -X POST \ "https://api.social-api.ai/v1/inbox/comments/{postId}/sapi_cmt_GXRM4BNTW7KFYSD3HAEV6QJZ2CLP9U/hide" \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J"}' ``` Reply to a review: ```bash theme={null} curl -X POST \ "https://api.social-api.ai/v1/inbox/reviews/sapi_rev_GXRM4BNTW7KFYSD3HAEV6QJZ2CLP9U/reply" \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "text": "Thanks for the feedback!" }' ``` See the [Inbox guide](/guides/inbox) for the full list of routes that take interaction IDs. # Media uploads Source: https://docs.social-api.ai/guides/media Upload images and videos, then attach them to posts with a media ID. SocialAPI is upload-first for media. You upload a file once, receive a `media_id` (a UUID), then reference that ID in the `media_ids` array when you create a post. There is no `image_url` or `video_url` field on a post: a raw public URL placed in `media_ids` is silently ignored, and the post publishes with no media. Uploading and verifying media does not cost credits. A credit is charged only when the post publishes or is scheduled. ## Choosing an upload path There are two ways to get a file into SocialAPI. Both return a `media_id` that you use the same way. | | Server-side upload | Presigned URL | | ------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | | Endpoint(s) | `POST /v1/media/upload` | `GET /v1/media/upload-url`, then `PUT`, then `POST /v1/media/{id}/verify` | | Calls | One | Three | | Max file size | 50 MB | No API limit (plan storage quota applies) | | Best for | Small files, or clients that cannot reach S3 directly (for example AI sandboxes) | Large files, especially video | If a file is larger than 50 MB, use the presigned-URL path. ## Server-side upload Send the file as multipart form data in a single request. The `file` field is required. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/media/upload \ -H "Authorization: Bearer $SOCAPI_KEY" \ -F "file=@photo.jpg" ``` Response: ```json theme={null} { "media_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479" } ``` The returned media is immediately `ready`. Pass the `media_id` in `media_ids` on your next `POST /v1/posts` call (see [Using media in posts](#using-media-in-posts)). ## Presigned URL upload Use this path for large files. It has three steps: request a signed URL, upload the bytes to it, then verify. **Step 1: Request a signed upload URL.** Both `media_type` (the file's MIME type) and `filename` are required. ```bash theme={null} curl "https://api.social-api.ai/v1/media/upload-url?media_type=video/mp4&filename=reel.mp4" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Response: ```json theme={null} { "media_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "upload_url": "https://storage.example.com/media/...?X-Amz-Signature=...", "expires_at": "2026-07-07T09:15:00Z" } ``` The `upload_url` is valid for 15 minutes. **Step 2: Upload the file** with an HTTP `PUT` to `upload_url`. The `Content-Type` must match the `media_type` you declared in step 1. ```bash theme={null} curl -X PUT "https://storage.example.com/media/...?X-Amz-Signature=..." \ -H "Content-Type: video/mp4" \ --data-binary @reel.mp4 ``` **Step 3: Verify the upload.** This step is required. It confirms the bytes landed, records the file size, and flips the media from `pending` to `ready`. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/media/f47ac10b-58cc-4372-a567-0e02b2c3d479/verify \ -H "Authorization: Bearer $SOCAPI_KEY" ``` If you skip verification, the media stays `pending` and publishing fails with `Media not found`. ## Using media in posts Pass one or more `media_id` values in the `media_ids` array on `POST /v1/posts`. Order is preserved. A single entry produces an image or video; multiple entries produce a carousel, subject to each platform's limits. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Behind the scenes from our studio shoot.", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"] }' ``` Per-platform media limits (image versus video, carousel counts, maximum items per post) are listed in the [Posts overview](/posts/overview) capability matrix. A raw public URL in `media_ids` is ignored. Always upload first and pass the returned `media_id`. ## Managing your library **List media.** Returns your `ready` files, newest first, with cursor pagination (default 50 per page, max 100). Each item includes a short-lived `url` for previewing the file. ```bash theme={null} curl "https://api.social-api.ai/v1/media?limit=50" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` **Delete media.** ```bash theme={null} curl -X DELETE https://api.social-api.ai/v1/media/f47ac10b-58cc-4372-a567-0e02b2c3d479 \ -H "Authorization: Bearer $SOCAPI_KEY" ``` **Check storage usage.** Returns bytes used, the plan limit, and file count. The same values come back as `X-Storage-Used` and `X-Storage-Limit` response headers on media requests (`-1` means unlimited). ```bash theme={null} curl https://api.social-api.ai/v1/media/storage \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ```json theme={null} { "used_bytes": 52428800, "limit_bytes": 104857600, "count": 12 } ``` ### Storage limits | Plan | Storage | | ---------------------------------- | --------- | | Free | 100 MB | | Starter, Pro, Business, Enterprise | Unlimited | An upload that would exceed your quota returns `413`. On the presigned path the object is removed and the media is marked `failed`. ## Constraints and gotchas * A raw public URL in `media_ids` is silently ignored. Upload first, then pass the `media_id`. * The verify step is mandatory on the presigned path. Without it, media stays `pending` and publishing fails with `Media not found`. * The server-side upload endpoint caps files at 50 MB. Use the presigned path for anything larger. * Media has three states: `pending` (created, bytes not yet confirmed), `ready` (usable in posts), and `failed` (quota exceeded, or the object was missing at verify). * Video duration is detected automatically at verify or upload time. You do not need to send it. * Per-platform format, size, and count rules are enforced when you publish. See each platform's posts page. # OAuth Flows Source: https://docs.social-api.ai/guides/oauth Connecting social accounts via platform OAuth and authenticating MCP clients via OAuth 2.1. SocialAPI uses OAuth in two distinct contexts: 1. **Connecting platform accounts** (Instagram, Facebook, etc.) to your SocialAPI account so you can manage them through the API. 2. **Authenticating MCP/API clients** (Claude, ChatGPT, Cursor) via the built-in OAuth 2.1 authorization server so they can call the API on your behalf. ## Connecting platform accounts SocialAPI manages platform OAuth on your behalf. Your application kicks off the connection, sends the user to the platform's consent screen, and receives a redirect back to a URL of your choice with the result. You never see or store platform access tokens — SocialAPI persists them encrypted and proxies all subsequent calls. This is a managed connection pattern, not an OAuth flow you implement against SocialAPI. For OAuth 2.1 (used by MCP and third-party API clients calling SocialAPI), see the section below. ### Step 1: Initiate the connection Call `POST /v1/accounts/connect` with the platform slug, your post-connection `redirect_uri`, and an optional `state` value for correlation: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/accounts/connect \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "platform": "instagram", "redirect_uri": "https://app.example.com/oauth/callback", "state": "session_abc123" }' ``` Fields: | Field | Location | Required | Notes | | -------------- | --------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `platform` | top-level | yes | Platform slug (`instagram`, `facebook`, `threads`, `tiktok`, `linkedin`, `twitter`, `youtube`, `google`). | | `redirect_uri` | top-level | recommended | Your post-connection callback. If you have registered any redirect URIs, this must match one of them (see [Whitelisting redirect URIs](#whitelisting-redirect-uris)). If omitted, the user sees a built-in HTML success page instead of being redirected. | | `state` | top-level | optional | Opaque correlation value, max 512 chars, echoed back verbatim in the final redirect. Use it to tie the result to a session on your side. | | `brand_id` | top-level | optional | Attach the account to an existing brand. If omitted, a new brand is auto-created. | | `metadata` | top-level | optional | Platform-specific connection data only (for example, TikTok BYOK uses this for `apikey`). For standard managed OAuth this is empty or omitted. | Response (HTTP 202): ```json theme={null} { "auth_url": "https://www.instagram.com/oauth/authorize?client_id=...&state=...", "state": "4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2", "message": "Redirect the user to auth_url" } ``` The `state` in the response is SocialAPI's internal CSRF token used between our connect call and our platform callback. Your application does not need to read or store it; just send the user to `auth_url`. ### Step 2: User authorizes on the platform Redirect your user to the `auth_url`. They log in to the platform and grant permissions. The platform redirects back to **SocialAPI's** callback (not yours). SocialAPI exchanges the code for an access token, persists it encrypted, and then 302-redirects the user to **your** `redirect_uri`. ### Step 3: Receive the result at your `redirect_uri` **Success:** ``` https://app.example.com/oauth/callback ?status=success &platform=instagram &state=session_abc123 &account_id=acc_01HZ9X3Q4R5M6N7P8V2K0W1J ``` One connection produces exactly one `account_id`, with one exception: Facebook never returns `status=success` directly. Every Facebook connect pauses for Page selection, described below in [Facebook: choosing which Pages to connect](#facebook-choosing-which-pages-to-connect). Google logins that manage more than one Business Profile follow the same pending-connection pattern; see the next section. **Error** (the user denied consent, the platform returned an error, or the link expired): ``` https://app.example.com/oauth/callback ?status=error &error=access_denied &error_description=The+user+denied+the+request &platform=instagram &state=session_abc123 ``` Always check `status` first. On `success`, persist the `account_id` against your user record and call `/v1/accounts/{id}` for full account details. On `error`, surface a retry path in your UI — the customer can restart from Step 1. ### Pending connections: choosing among multiple candidates Some platforms cannot resolve a single account from an OAuth grant alone, so the connect flow pauses and asks you to choose before an account is created. Google takes this path only when a login manages more than one Business Profile. Facebook takes it on every connect, because one Facebook login can serve many brands and Pages must be assigned to a brand explicitly. When this happens, your `redirect_uri` (or the exchange response, in the proxy flow) receives `status: selection_required` and a `connection_id` instead of `status: success`. No account exists yet. Finish the connection with `POST /v1/accounts/pending/{connection_id}/select`, shaped differently per platform as shown below. ### Google: connecting a login with multiple Business Profiles A Google login with a single Profile connects like any other platform, in a single request, with no extra step. Google accounts can manage several Business Profiles under one login (for example, a chain with multiple locations). When that happens, SocialAPI cannot know which Profile you want to connect, so it does not create an account yet. Instead it stores a pending connection and asks you to choose. **Server-callback flow.** If the Google login you just authorized manages more than one Profile, your `redirect_uri` receives `status=selection_required` instead of `status=success`, along with a `connection_id`: ``` https://app.example.com/oauth/callback ?status=selection_required &connection_id=pc_4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2 &state=session_abc123 ``` **Proxy flow.** If your server exchanges the authorization code itself via `POST /v1/oauth/exchange`, the same fork happens in the response body. A single Profile still returns the usual `ConnectAccountResponse` (HTTP 201). More than one Profile returns HTTP 200 instead: ```json theme={null} { "status": "selection_required", "connection_id": "pc_4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2", "platform": "google", "profiles": [ { "platform_account_id": "accounts/123/locations/456", "display_name": "Joe's Pizza (Downtown)", "username": "", "profile_picture_url": "", "bio": "Wood-fired pizza since 1998" }, { "platform_account_id": "accounts/123/locations/789", "display_name": "Joe's Pizza (Uptown)", "username": "", "profile_picture_url": "", "bio": "" } ] } ``` **Resolving the selection.** In both cases no account exists yet. Finish the connection with the `connection_id`: 1. `GET /v1/accounts/pending/{connection_id}` lists the candidate Profiles. This is usually optional since the callback redirect or the exchange response already includes them, but it is useful to re-fetch the list or check whether the pending connection is still valid. ```bash theme={null} curl https://api.social-api.ai/v1/accounts/pending/pc_4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2 \ -H "Authorization: Bearer $SOCAPI_KEY" ``` 2. `POST /v1/accounts/pending/{connection_id}/select` with the `platform_account_id` of exactly one Profile connects it: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/accounts/pending/pc_4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2/select \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"platform_account_id": "accounts/123/locations/456"}' ``` This returns the same `ConnectAccountResponse` (HTTP 201) as a normal single-Profile connect. A pending connection is single-use and expires 30 minutes after it is created. The first successful selection consumes it: a second `select` call, or one made after expiry, returns `404`. Selecting a `platform_account_id` that is not in the returned candidate set returns `400`. ### Facebook: choosing which Pages to connect Every Facebook connect returns a pending connection, never `status: success` directly, even when the login manages a single Page. Facebook Pages are shared across brands: one login can serve several brands through one stored credential, and Pages must be assigned to a brand explicitly rather than attaching automatically. **Server-callback flow.** Your `redirect_uri` receives `status=selection_required` and a `connection_id`, the same shape as Google: ``` https://app.example.com/oauth/callback ?status=selection_required &connection_id=pc_4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2 &state=session_abc123 ``` **Proxy flow.** `POST /v1/oauth/exchange` returns the pending connection in the body, with Facebook-specific fields alongside the shared `status` and `connection_id`: ```json theme={null} { "status": "selection_required", "connection_id": "pc_4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2", "platform": "facebook", "login_id": "cred_01J8X3Q4R5M6N7P8V2K0W1J", "pages": [ { "platform_page_id": "17841400000000000", "name": "Acme Bakery", "assigned_brand_id": "", "assignable": true }, { "platform_page_id": "17841400000000111", "name": "Acme Cafe", "assigned_brand_id": "b_01HZ9X3Q4R5M6N7P8V2K0W1J", "assignable": false } ], "lost_access": [] } ``` Fields: | Field | Meaning | | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `login_id` | Identifies the shared Facebook credential behind this connection. Use it later with the [Facebook logins endpoints](/connectors/facebook#managing-pages-after-connecting) to list or assign Pages without a new OAuth round trip. | | `pages[].platform_page_id` | The Facebook Page ID. | | `pages[].name` | The Page's display name. | | `pages[].assigned_brand_id` | Empty when the Page is unassigned and available. Set to an existing brand ID when one of your other brands already owns the Page, in which case `assignable` is `false`. | | `pages[].assignable` | Whether this Page can be assigned to the brand you are connecting into right now. | | `lost_access[]` | Pages this authorization revoked access to, app-wide. Empty on a first connect; see below. | **Resolving the selection.** As with Google, `GET /v1/accounts/pending/{connection_id}` re-fetches the candidate list, and `POST /v1/accounts/pending/{connection_id}/select` completes the connection, but the body carries `page_ids` instead of a single `platform_account_id`: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/accounts/pending/pc_4a8f2c1e9b3d7f06a5c2e8b4d1f3a7e2/select \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"page_ids": ["17841400000000000"]}' ``` You do not have to assign every candidate Page at connect time. Pages you leave out stay unassigned and can be picked up later, from this brand or another one you own, via `GET /v1/platforms/facebook/logins/{login_id}/pages` and `POST /v1/platforms/facebook/logins/{login_id}/pages/{page_id}/assign`. See [Facebook connector: managing Pages after connecting](/connectors/facebook#managing-pages-after-connecting). **Lost access.** Facebook grants access per app, not per brand. When a user re-authenticates and unchecks a Page they had previously granted, that Page's access is revoked everywhere, including on brands other than the one being reconnected. `lost_access[]` names every Page this happened to, along with the brand that was using it: ```json theme={null} "lost_access": [ { "platform_page_id": "17841400000000111", "name": "Acme Cafe", "brand_id": "b_01HZ9X3Q4R5M6N7P8V2K0W1J", "brand_name": "Acme Cafe LLC" } ] ``` Show the affected user this warning, filling in the Page and brand names from the entry: > Reconnecting removed access to , used by . To restore it, reconnect Facebook and choose Edit previous settings, keeping checked. The brand's assignment to a lost Page is preserved, not deleted. The next time the same login is re-granted access to that Page (by keeping it checked on Facebook's consent screen during a later reconnect), it reactivates in place, on the same brand, with no further steps. A Facebook pending connection is single-use and expires 30 minutes after it is created, the same rule as Google. Selecting a `platform_page_id` you have no access to, or that another brand already owns, returns `400`. ### Fallback when `redirect_uri` is omitted If your connect call did not include `redirect_uri`, SocialAPI renders a built-in HTML success or error page in place of the redirect. This is intended for ad-hoc connections (for example, the SocialAPI dashboard itself); production integrations should always supply `redirect_uri`. ### Whitelisting redirect URIs Redirect URI enforcement is optional until you register your first URI. If your workspace has no redirect URIs registered, any valid `redirect_uri` (https, or http for localhost) is accepted when starting a connect flow. As soon as you register one or more URIs, every `redirect_uri` must match an entry in your list, and unmatched values are rejected with a `400` error. Deleting all registered URIs returns your workspace to open mode. Register URIs from the SocialAPI dashboard under **Settings → OAuth redirect URIs**. The management API exists at `/v1/oauth/redirect-uris` but is dashboard-only (requires the dashboard JWT, not an API key). We recommend registering your production redirect URIs to lock down where users can be sent after OAuth. ### Correlation `state` (customer-controlled) The top-level `state` field on `POST /v1/accounts/connect` is **your** correlation token, not a security primitive enforced by SocialAPI. It is echoed back unchanged in the final redirect. Common uses: * Tie the redirect to the originating session (set `state` to a server-side session ID). * CSRF defense for your own application — generate a random value, store it in the user's session, and verify it matches when the redirect lands. SocialAPI does not validate `state`. If you do not pass one, the final redirect will include `state=` with an empty value. ### Listing connected accounts ```bash theme={null} curl https://api.social-api.ai/v1/accounts \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ### Reconnecting an account If you call `POST /v1/accounts/connect` for an account that is already connected, the flow runs again and the stored token is upserted. This is safe to call when tokens expire and is the supported path for token refresh on platforms that lack long-lived tokens. ### Disconnecting an account `DELETE /v1/accounts/{id}` removes the account and the tokens we hold for it. Where the platform offers a token-revocation endpoint, we also revoke the grant on the platform during disconnect: Facebook, Google, Instagram, Threads, X, TikTok, and YouTube all support this. LinkedIn, Pinterest, and Zalo do not offer a revocation API, so disconnect removes only the token stored on our side. On those platforms the user still sees the app authorized in their account settings and must remove it manually. If you offboard users under GDPR or similar rules, account for this manual step on those three platforms. This is separate from `POST /oauth/revoke` below, which revokes SocialAPI's own OAuth 2.1 tokens issued to MCP clients, not a platform grant. *** ## OAuth 2.1 for MCP and API clients SocialAPI includes a built-in OAuth 2.1 authorization server that enables MCP clients (Claude, ChatGPT, Cursor) and other third-party applications to authenticate users and call the API on their behalf. The implementation follows these RFCs: * **RFC 8414** for authorization server metadata discovery * **RFC 7591** for dynamic client registration * **RFC 7636** for PKCE (required) * **RFC 7009** for token revocation ### Discovery Clients can discover the authorization server configuration at: ``` GET /.well-known/oauth-authorization-server ``` Response: ```json theme={null} { "issuer": "https://api.social-api.ai", "authorization_endpoint": "https://api.social-api.ai/oauth/authorize", "token_endpoint": "https://api.social-api.ai/oauth/token", "registration_endpoint": "https://api.social-api.ai/oauth/register", "revocation_endpoint": "https://api.social-api.ai/oauth/revoke", "response_types_supported": ["code"], "grant_types_supported": ["authorization_code", "refresh_token"], "token_endpoint_auth_methods_supported": ["client_secret_post"], "code_challenge_methods_supported": ["S256"], "scopes_supported": ["social:all"] } ``` ### Step 1: Register a client Register your application using dynamic client registration: ```bash theme={null} curl -X POST https://api.social-api.ai/oauth/register \ -H "Content-Type: application/json" \ -d '{ "client_name": "My MCP Client", "redirect_uris": ["https://app.example.com/callback"] }' ``` Response (HTTP 201): ```json theme={null} { "client_id": "a1b2c3d4e5f6...", "client_secret": "s3cr3t...", "client_id_issued_at": 1719000000, "client_secret_expires_at": 0, "client_name": "My MCP Client", "redirect_uris": ["https://app.example.com/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "client_secret_post" } ``` The `client_secret` is returned once. Store it securely. It cannot be retrieved again. ### Step 2: Authorize the user Redirect the user to the authorization endpoint with PKCE parameters: ``` GET /oauth/authorize ?client_id=a1b2c3d4e5f6... &redirect_uri=https://app.example.com/callback &response_type=code &scope=social:all &state=random_csrf_value &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256 ``` The API validates the client and redirect URI, then redirects the user to the SocialAPI dashboard consent screen. After the user approves, they are redirected back to your `redirect_uri` with `?code=...&state=...`. PKCE is required. The only supported challenge method is `S256`. Requests without a `code_challenge` are rejected with `400`. **Single scope.** The only supported scope is `social:all`, which grants full access to the user's account. This is the default if no scope is specified. ### Step 3: Exchange the code for tokens ```bash theme={null} curl -X POST https://api.social-api.ai/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=AUTH_CODE_HERE" \ -d "client_id=a1b2c3d4e5f6..." \ -d "client_secret=s3cr3t..." \ -d "code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" \ -d "redirect_uri=https://app.example.com/callback" ``` Response: ```json theme={null} { "access_token": "eyJhbGciOiJFZERTQSIs...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "rt_a1b2c3d4...", "scope": "social:all" } ``` The access token is an EdDSA/Ed25519 signed JWT valid for **1 hour**. Use it as a Bearer token for all `/v1` endpoints. Per RFC 6749 section 2.3.1, the token endpoint also accepts client credentials via HTTP Basic authentication. Send `Authorization: Basic base64(client_id:client_secret)` instead of including `client_id` and `client_secret` in the request body. Body parameters take precedence if both are present. ### Step 4: Refresh the access token When the access token expires, use the refresh token to get a new pair: ```bash theme={null} curl -X POST https://api.social-api.ai/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token" \ -d "refresh_token=rt_a1b2c3d4..." \ -d "client_id=a1b2c3d4e5f6..." ``` The response contains a new access token and a new refresh token. The old refresh token is invalidated (rotation is enforced). Refresh tokens expire after **30 days**. ### Revoking tokens To revoke a refresh token (for example, on user logout): ```bash theme={null} curl -X POST https://api.social-api.ai/oauth/revoke \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "token=rt_a1b2c3d4..." \ -d "client_id=a1b2c3d4e5f6..." ``` Per RFC 7009, this endpoint always returns `200`, even if the token is already invalid. ### Supporting endpoints | Endpoint | Method | Purpose | | ----------------------------------------- | ------ | ------------------------------------------------------------------------------ | | `/.well-known/oauth-authorization-server` | GET | RFC 8414 metadata discovery | | `/oauth/register` | POST | RFC 7591 dynamic client registration | | `/oauth/authorize` | GET | Authorization with PKCE | | `/oauth/authorize/request` | GET | Fetch a pending authorization request by `request_id` (used by the consent UI) | | `/oauth/token` | POST | Code exchange and refresh token rotation | | `/oauth/revoke` | POST | RFC 7009 token revocation | ### MCP server card MCP clients can discover SocialAPI as a tool provider via the server card: ``` GET /.well-known/mcp/server-card.json ``` The protected resource metadata (RFC 9470) is available at: ``` GET /.well-known/oauth-protected-resource GET /.well-known/oauth-protected-resource/mcp ``` # Pagination Source: https://docs.social-api.ai/guides/pagination Page through large result sets using cursors. ## Cursor-based pagination All list endpoints return paginated results. The response includes a `next_cursor` field; pass it as the `cursor` query parameter on the next call to fetch the next page. ```bash theme={null} # First page GET /v1/inbox/comments?limit=25 # Next page (use next_cursor from previous response) GET /v1/inbox/comments?limit=25&cursor=eyJpZCI6IjE3ODQx... ``` When there are no more results, the response omits `next_cursor` (or returns it as an empty string). This pattern applies to all list endpoints: `/v1/inbox/comments`, `/v1/inbox/conversations`, `/v1/inbox/reviews`, `/v1/posts`, `/v1/media`, and others. ## Filtering Most list endpoints support filtering parameters. For example: ```bash theme={null} # Posts with comments, filtered by account and platform GET /v1/inbox/comments?account_id=acc_01HZ...&platform=instagram&limit=25 # Published posts, sorted by date descending GET /v1/posts?status=published&sort=created_desc&limit=25 ``` See the [API Reference](/api-reference) for the full set of query parameters on each endpoint. ## Parameters | Parameter | Type | Default | Description | | --------- | ------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `limit` | integer | varies | Results per page. Min 1, max 100. Default varies by endpoint: most endpoints default to 25, the shared validator defaults to 20, `/v1/media` defaults to 50, and `/v1/inbox/comments/{postId}/{commentId}/replies` defaults to 10. | | `cursor` | string | - | Opaque pagination cursor from the previous response's `next_cursor` field. | # Platform credentials Source: https://docs.social-api.ai/guides/platform-credentials Whose OAuth app do you authenticate through? SocialAPI provides managed apps for every connector except Twitter. SocialAPI provides managed platform apps for every connector except Twitter/X. You don't need to create a Meta App, register with TikTok for Developers, set up a Google Cloud project, or apply for app review. Click Connect, complete OAuth, you're done. ## Who provides the OAuth app | Platform | OAuth app provided by | What you do | What SocialAPI does | App review needed? | | ----------- | --------------------- | ------------------------------------------------------------ | ---------------------------------------- | ---------------------------------------------- | | Instagram | SocialAPI | Click Connect | Run OAuth, manage tokens | No | | Facebook | SocialAPI | Click Connect | Run OAuth, manage tokens | No | | Threads | SocialAPI | Click Connect | Run OAuth, manage tokens | No | | Google | SocialAPI | Click Connect | Run OAuth, manage tokens | No | | TikTok | SocialAPI | Click Connect | Run OAuth, manage tokens | No | | LinkedIn | SocialAPI | Click Connect | Run OAuth, manage tokens | No | | YouTube | SocialAPI | Click Connect | Run OAuth, manage tokens | No | | Twitter / X | **You (BYOK)** | Create a Twitter Developer app, paste keys into SocialAPI | Run OAuth, manage tokens | No (you just need a Twitter Developer account) | | Telegram | **You (bot token)** | Create a bot with @BotFather, paste the token into SocialAPI | Validate the token, register the webhook | No (no app review, no OAuth) | ## Why Twitter is different Twitter rate-limits per-app. A single shared SocialAPI app would hit the per-app limit immediately and throttle every customer at once. To avoid that, every customer brings their own Twitter Developer app, which gives them their own rate-limit bucket. Setup takes about ten minutes - see the [Twitter BYOK setup guide](/connectors/twitter-byok). ## Telegram uses a static bot token Telegram is the one connector that does not use OAuth at all. Instead of clicking Connect and authorizing through a consent screen, you create a bot with [@BotFather](https://t.me/BotFather) (send `/newbot`, choose a name and a username ending in `bot`), copy the token it gives you, and paste it when connecting. The token never expires and is revoked only through @BotFather. There is no developer account, business verification, or app review. See the [Telegram connector](/connectors/telegram) for the full flow. ## What permissions does SocialAPI request For transparency, here are the OAuth scopes SocialAPI's pre-approved app requests on your behalf for each platform. You don't need to do anything with this list - it's informational. | Platform | Scopes | | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Instagram | `instagram_business_basic`, `instagram_business_manage_comments`, `instagram_business_manage_messages`, `instagram_business_content_publish` | | Facebook | `pages_show_list`, `pages_read_engagement`, `pages_read_user_content`, `pages_manage_engagement`, `pages_manage_metadata`, `pages_messaging`, `pages_manage_posts`, `public_profile`, `business_management` | | Threads | `threads_basic`, `threads_content_publish`, `threads_delete`, `threads_read_replies` | | Google | `https://www.googleapis.com/auth/business.manage` | | TikTok | `user.info.basic`, `user.info.profile`, `video.publish`, `video.upload` | | LinkedIn | `r_liteprofile`, `rw_organization_admin`, `r_organization_social`, `w_organization_social`, `r_organization_social_feed`, `w_organization_social_feed` | | YouTube | `https://www.googleapis.com/auth/youtube.force-ssl` | ## FAQ **Do I need to apply for Meta App Review?** No. SocialAPI's app is already reviewed and approved with Meta. You connect through it. **Whose brand appears on the OAuth consent screen?** Today, end users see SocialAPI's brand on the platform consent screen when they authorize a connection. White label consent is on our roadmap. If brand control matters for your use case, contact us . **Can I bring my own Meta / TikTok / Google app instead?** Not today. BYOA for non-Twitter platforms is on the roadmap. If this is critical for your use case, [contact us](https://www.) so we can prioritize it. **Does the plan I'm on affect any of this?** No. Managed platform apps are included on every plan, including the Free tier. Plans differ only by [resource limits](/guides/rate-limits). **What happens if SocialAPI's platform app gets revoked or rate-limited?** We monitor app health continuously. In the rare case of a platform-side issue, all customers on that platform are affected at once and we communicate via [status.social-api.ai](https://status.social-api.ai). Twitter customers (BYOK) are isolated from this because each customer has their own app. # Platform Support Source: https://docs.social-api.ai/guides/platforms Which platforms support which capabilities. ## Capability matrix | Platform | Posts | Comments | Reply | Hide | Like | Delete | Private Reply | DMs | Reviews | Mentions | Publishing | | --------------------- | ----- | -------- | ----- | ----------- | ---- | ----------- | ------------- | ---- | ------- | -------- | ---------- | | Instagram | yes | yes | yes | yes | - | yes | yes | yes | - | yes | yes | | Facebook | yes | yes | yes | yes | yes | yes | yes | yes | - | yes | yes | | Threads | yes | yes | yes | Conditional | - | Conditional | - | - | - | - | yes | | TikTok | yes | - | - | - | - | - | - | - | - | - | yes | | YouTube | yes | yes | yes | yes | - | yes | - | - | - | - | yes | | X / Twitter | yes | yes | yes | yes | - | Conditional | - | yes | - | yes | yes | | LinkedIn Personal | yes | - | - | - | - | - | - | - | - | - | yes | | LinkedIn Organization | Beta | Beta | Beta | Beta | Beta | Beta | - | - | - | - | Beta | | Google | yes | - | - | - | - | - | - | - | yes | - | yes | | Telegram | - | - | - | - | - | - | - | yes | - | - | - | | WhatsApp | - | - | - | - | - | - | - | Soon | - | - | - | | Trustpilot | - | - | - | - | - | - | - | - | Soon | - | - | | Pinterest | Soon | - | - | - | - | - | - | - | - | - | Soon | **Legend:** yes = supported · Conditional = supported under specific conditions (e.g., on own replies only) · Beta = live in beta, access on request (see the [LinkedIn connector](/connectors/linkedin#linkedin-organization-pages-beta)) · Soon = coming soon · - = not supported by platform All platforms and capabilities are available on every plan, including the Free tier. Plans differ only by [resource limits](/guides/rate-limits) (brands, posts, interactions, storage). ## Comment moderation Each comment returned from the inbox includes a `capabilities` object indicating which actions are available for that platform: ```json theme={null} { "capabilities": { "can_reply": true, "can_delete": true, "can_hide": true, "can_like": false, "can_private_reply": true } } ``` Use these flags to conditionally render UI controls or decide which actions to attempt. ## Media library All plans support media uploads for post publishing. Uploaded media is stored in your personal media library and can be reused across posts. | Plan | Storage limit | | -------- | ------------- | | Free | 100 MB | | Starter+ | Unlimited | Manage your media library via the `/v1/media` endpoints: list, delete, and check storage usage. See the [API Reference](/api-reference) for details. ## Unsupported operations When you call an endpoint for a capability a platform does not support, [SocialAPI.AI](https://social-api.ai) returns `501`: ```json theme={null} { "error": { "code": "resource.not_supported", "message": "platform does not support this operation" } } ``` Your code should handle `501` gracefully. It's not an error in your integration, just a platform limitation. # Plans & Limits Source: https://docs.social-api.ai/guides/rate-limits Resource limits, billing periods, export quotas, and how to monitor your usage. ## How plans work [SocialAPI.AI](https://social-api.ai) uses **resource-based** limits. Every plan gives you a fixed number of brands, posts per month, interactions per month, media storage, and export quotas. A brand groups all social accounts for one business, client, or project. Connect Instagram, TikTok, LinkedIn, and more under one brand, and it counts as one unit. All plans have access to all platforms, DMs, and features. Plans differ only by resource limits. Upgrade at [app.social-api.ai](https://app.social-api.ai) → **Billing**. Compare every tier on the [plans and pricing](https://social-api.ai/pricing) page. **All plans include managed platform apps** for Meta (Instagram, Facebook, Threads), Google, TikTok, LinkedIn, and YouTube - no developer registration or app review on your side. Twitter is BYOK on every plan ([setup guide](/connectors/twitter-byok)). See [Platform credentials](/guides/platform-credentials) for details. ## Plan catalog endpoint To read the full plan catalog programmatically instead of hardcoding the table below, call: ```bash theme={null} curl https://api.social-api.ai/v1/billing/plans \ -H "Authorization: Bearer $SOCIALAPI_KEY" ``` The response lists every tier, ordered cheapest to most expensive, with its display metadata, prices, and resource limits (trimmed to two plans here): ```json theme={null} { "plans": [ { "tier": "free", "name": "Hobby", "tagline": "Kick the tires, no card required", "monthly_price_usd": 0, "annual_price_usd": 0, "popular": false, "limits": { "profiles": 2, "posts_per_month": 10, "interactions_per_month": 50, "storage_bytes": 104857600, "event_retention_days": 1, "exports_per_month": 2 } }, { "tier": "pro", "name": "Full Send", "tagline": "For growing teams and products", "monthly_price_usd": 109, "annual_price_usd": 1044, "popular": true, "limits": { "profiles": 50, "posts_per_month": -1, "interactions_per_month": -1, "storage_bytes": -1, "event_retention_days": 30, "exports_per_month": 20 } } ] } ``` `profiles` is the count of social profiles (brands) the plan allows. Prices are in USD. A value of `-1` means unlimited for any field under `limits`, and custom pricing for `monthly_price_usd` / `annual_price_usd` (shown on the Enterprise tier). `popular` flags the tier highlighted by default on pricing surfaces. ## Plan limits | Plan | Display Name | Brands | Posts / month | Interactions / month | Storage | Price | | ---------- | ------------ | --------- | ------------- | -------------------- | --------- | ------ | | Free | Hobby | 2 | 10 | 50 | 100 MB | \$0 | | Starter | Side Hustle | 10 | Unlimited | Unlimited | Unlimited | \$29 | | Pro | Full Send | 50 | Unlimited | Unlimited | Unlimited | \$109 | | Business | Empire | 200 | Unlimited | Unlimited | Unlimited | \$349 | | Enterprise | Enterprise | Unlimited | Unlimited | Unlimited | Unlimited | Custom | ## Export and analytics limits Each plan includes different export quotas for analytics reports. | Feature | Free | Starter | Pro | Business | Enterprise | | -------------------------- | ------ | --------- | --------- | --------- | ---------- | | Exports per month | 2 | 10 | 20 | Unlimited | Unlimited | | Max videos per export | 30 | 200 | 500 | Unlimited | Unlimited | | Max transcripts per export | 10 | Unlimited | Unlimited | Unlimited | Unlimited | | Vision analysis | No | Yes | Yes | Yes | Yes | | Event retention (days) | 1 | 7 | 30 | 90 | 90 | | Report expiry (days) | 7 | 30 | 30 | 90 | 90 | | Export cooldown | 7 days | 24 hours | 1 hour | 1 hour | None | "Unlimited" in the table above means the feature is not capped. In the API response, unlimited values are represented as `-1`. ## What counts as a post or interaction **Post operations** (each consumes 1 post from your monthly allowance): * `create_post` - publishing or scheduling a new post (saving a **draft** is free; the credit is charged when you publish it) * `publish_post` - publishing an existing draft or scheduled post (`POST /v1/posts/{pid}/publish`) * `retry_post` - retrying a failed post **Interaction operations** (each consumes 1 interaction from your monthly allowance): * `reply` - replying to a comment, review, or mention * `send_dm` - sending a direct message **All other operations are free.** Listing accounts, fetching comments, reading DMs, checking usage, managing API keys, and similar read operations do not consume any quota. Only the Free plan enforces post and interaction limits. All paid plans have unlimited posts and interactions. The counters are tracked but never block requests. ## Billing periods * **Paid plans:** Aligned with your Stripe subscription cycle. The `period_start` and `period_end` come directly from Stripe. * **Free plan:** Rolling 30-day window from your account creation anniversary date. Counters reset to zero at the start of each new period. ## When you hit a limit When you exceed your monthly post or interaction allowance, the API returns HTTP `429`: ```json theme={null} { "error": { "code": "validation.post_limit_exhausted", "message": "Monthly post limit exhausted" }, "request_id": "a1b2c3d4" } ``` The interaction-limit response uses the same shape with `error.code` set to `validation.interaction_limit_exhausted`. Your counters reset at the start of your next billing period. To get more capacity immediately, upgrade your plan. The `billing.post_limit` code is emitted by the bulk-import endpoint (`POST /v1/posts/import`) when the batch size exceeds your remaining post allowance. When you exceed your storage quota, the API returns HTTP `413`: ```json theme={null} { "error": { "code": "billing.storage_quota", "message": "Storage quota exceeded. Free plan is limited to 100MB. Upgrade your plan for more storage." }, "request_id": "a1b2c3d4" } ``` ## Checking your usage ```bash theme={null} curl https://api.social-api.ai/v1/usage \ -H "Authorization: Bearer $SOCIALAPI_KEY" ``` ```json theme={null} { "brands_used": 2, "brands_limit": 10, "posts_used": 7, "posts_limit": -1, "interactions_used": 23, "interactions_limit": -1, "period_start": "2026-02-15T00:00:00Z", "period_end": "2026-03-15T00:00:00Z" } ``` A limit value of `-1` means unlimited (all paid plans). ### Checking storage usage ```bash theme={null} curl https://api.social-api.ai/v1/media/storage \ -H "Authorization: Bearer $SOCIALAPI_KEY" ``` ```json theme={null} { "used_bytes": 52428800, "limit_bytes": 104857600, "count": 12 } ``` A `limit_bytes` value of `-1` means unlimited storage. ### Response headers Post and interaction operations include usage headers in the response: | Header | Description | | -------------------------- | --------------------------------------- | | `X-Posts-Limit` | Monthly post limit for your plan | | `X-Posts-Remaining` | Posts remaining this period | | `X-Interactions-Limit` | Monthly interaction limit for your plan | | `X-Interactions-Remaining` | Interactions remaining this period | | `X-Storage-Used` | Current storage usage in bytes | | `X-Storage-Limit` | Storage limit in bytes for your plan | These headers are only set for Free plan post/interaction operations. Paid plans with unlimited resources omit them. ## Platform rate limits Platforms (Instagram, Facebook, etc.) have their own rate limits independent of your plan limits. When a platform rejects a request due to its own throttling, you receive: ```json theme={null} { "error": { "code": "platform.instagram.rate_limit", "message": "Platform rate limit exceeded" }, "request_id": "a1b2c3d4" } ``` The `instagram` segment is replaced with the actual platform name (`facebook`, `tiktok`, etc.). Match on the `platform.*.rate_limit` shape to handle all platforms. This does **not** consume your post or interaction quota. Retry after a short wait (typically a few minutes). ### Live inbox reads Comments, reviews, and mentions are proxied live from the platform on every request, with no server-side cache. Each page of comments for one post is a single platform call (`limit` accepts up to 100, so a few hundred comments is a handful of calls), and listing commented posts is served from SocialAPI's database without touching the platform. Read operations consume no SocialAPI quota and are not throttled by us, so the only ceiling is the platform's own limit, which for Meta scales with your account's reach and is far above operator-paced browsing. If your UI re-fetches the same view often (tab switches, refreshes), cache pages client-side for 30 to 60 seconds and back off for a few minutes on any `platform.*.rate_limit` response. ### Platform publishing ceilings Each platform sets its own publishing limits, which apply in addition to your plan quota. These are the platforms' own published figures: | Platform | Publishing ceiling (set by the platform) | | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | Instagram | 25 posts per 24 hours | | Threads | 250 posts and 1,000 replies per 24 hours | | YouTube | A daily upload quota (Google's default is 10,000 quota units per day, roughly 100 video uploads per day), reset at midnight Pacific time | | Google Business Profile | 300 requests per minute per API, and about 10 edits per location per minute | | Facebook, X | Enforced per app and per user through limits the platform reports in response headers; there is no single fixed number | We monitor each platform's reported usage and pace requests to stay within the platform's ceiling. SocialAPI itself adds no per-request throttle beyond your monthly plan quota, and read operations (inbox, reviews, comments) are not throttled on our side. If you run scheduled, unattended publishing across many accounts, design against the platform ceilings above. ## Handling 429s ```python theme={null} import time import requests def call_with_retry(url, headers, max_retries=3): for attempt in range(max_retries): resp = requests.get(url, headers=headers) if resp.status_code == 429: time.sleep(2 ** attempt) # exponential backoff continue return resp raise Exception("rate limit exceeded after retries") ``` If the rate-limit enforcement backend (Redis) is unavailable, the API **fails open**. Requests are allowed through rather than blocked. This prevents infrastructure downtime from taking down your integration. # Publishing behavior and reliability Source: https://docs.social-api.ai/guides/reliability How to tell what actually happened: response codes, media verification, delete semantics, and treating IDs as opaque strings. A successful HTTP status tells you a request was accepted, not that every part of it did what you intended. This page collects the behaviors that are easy to miss when you build against examples alone, so you can tell what actually happened. ## A 201 confirms creation, not every field When you create a post, a `201` means the post was created. It is not a guarantee that every field you sent was recognized. Fields we do not recognize are not applied, so a small naming mistake (for example sending `media` instead of `media_ids`, or a misspelled key) can produce a post that publishes without the media you intended. Two habits keep you safe: * Send only the fields documented for the endpoint, with their exact names. Validate your request body against the [API Reference](/api-reference/introduction) before you rely on a field. * When a dropped field would be costly, read the object back with `GET /v1/posts/{id}` and confirm the field is present before you depend on it. ## Verify media before you publish The presigned upload path has four steps: request a signed URL, `PUT` the bytes, verify, then attach. Media that has not been verified stays in a `pending` state, and a `pending` media reference does not publish silently: on a validated publish it returns `400` (`media ... is not ready or not found`), and on delivery it fails that target. Always confirm your media is `ready` before you attach it. See [Media uploads](/guides/media) for the full flow. ## Response codes Creating or publishing a post can return three statuses. Read them, and read the per-target detail: | Status | Meaning | | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `201` | The post was created (or, with `publish_now`, accepted for delivery). Delivery runs in the background, so the target status is often `publishing` at this point. | | `207` | Partial success. Some targets published, others failed. This is not a total failure. | | `422` | Every target failed. | When a target fails, the reason is on that target under `targets[].error`, with the shape `{ "category", "caused_by", "code", "message" }`. Surface `targets[].error.message` to your users rather than a generic retry prompt. See [Error handling](/guides/errors) for the code catalog. ## Delete semantics Delete does not mean the same thing on every resource. All three return `204 No Content`, but what they leave behind differs: | Endpoint | Effect | | -------------------------- | --------------------------------------------------------------------------------------------------- | | `DELETE /v1/invites/{id}` | Soft. The invite is marked inactive and still appears in `GET /v1/invites` with `is_active: false`. | | `DELETE /v1/accounts/{id}` | Hard. The account is removed and disappears from `GET /v1/accounts`. | | `DELETE /v1/brands/{id}` | Cascade. The brand and its connected accounts and pending invites are all removed. | So you detect a revoked invite from a field value, but you detect a disconnected account from its absence in the list. Two different code paths. `DELETE /v1/brands/{id}` is destructive. It disconnects every account under the brand and kills its pending connect links, so `GET /v1/invites?brand_id=` afterward returns `404`. Keep brand deletion behind a deliberate action, never in a retry or cleanup path. The brand delete takes the ID in the path with an empty body: ```bash theme={null} curl -X DELETE https://api.social-api.ai/v1/brands/3fa85f64-5717-4562-b3fc-2c963f66afa6 \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ## IDs are opaque strings Treat every ID we return as an opaque string. Do not parse it, and do not assume a format. Today, posts, brands, and invites are UUIDs, while accounts are prefixed with `acc_`, and interaction IDs carry a type prefix such as `sapi_cmt_`. These formats can differ per object type and may change, so persist IDs as text, not as a typed `uuid` column. # Scoped API keys Source: https://docs.social-api.ai/guides/scoped-keys Restrict an API key to specific scopes and brands for least-privilege automations and agency client isolation. By default, every API key has **full access**: it can perform any operation on every [brand](/guides/concepts#brand) in your account. You can narrow a key along two independent dimensions when you create it: | Dimension | Field | Controls | | ---------- | ----------- | -------------------------------------------------------------------------- | | **Scopes** | `scopes` | Which actions the key may perform (`posts:write`, `inbox:read`, and so on) | | **Brands** | `brand_ids` | Which brands' resources the key may see and touch | The two dimensions combine. A key can be scoped, brand-restricted, both, or neither. This supports two common patterns: * **Least-privilege automations.** A bot that only replies to comments holds a key with exactly `inbox:read` and `inbox:write`, nothing else. * **Agency and client isolation.** A key limited to one client's brand cannot read or touch any other client, so a leaked or shared key stays contained. **Empty means unrestricted.** An empty `scopes` array grants every scope. An empty `brand_ids` array grants every brand. A key is restricted only when at least one of the two arrays is non-empty. Every key created before this feature keeps full access with no change. ## Scopes Scopes are a stable, public vocabulary in the form `resource:action`. A scope-restricted key may only perform operations covered by the scopes it holds. | Group | Scope | Grants | | ------------- | ----------------- | ------------------------------------------------------------------------------------ | | Accounts | `accounts:read` | List brands, connected accounts, and their metadata | | Accounts | `accounts:manage` | Connect, configure, and disconnect accounts | | Posts | `posts:read` | Read posts and their per-platform status and metrics | | Posts | `posts:write` | Create, edit, publish, and delete posts | | Media | `media:read` | Browse the media library | | Media | `media:write` | Upload and delete media | | Inbox | `inbox:read` | Read comments, reviews, and mentions | | Inbox | `inbox:write` | Reply to and moderate comments, reviews, and mentions | | Messages | `dms:read` | Read direct message conversations | | Messages | `dms:send` | Send direct messages | | Account-level | `webhooks:manage` | Manage webhook endpoints and deliveries (not available to brand-restricted keys) | | Account-level | `analytics:read` | Read the events log, summaries, and exports (not available to brand-restricted keys) | Fetch the catalog live from the API so your integration always renders the current list: ```bash theme={null} curl https://api.social-api.ai/v1/keys/scopes \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ```json theme={null} { "data": [ { "name": "posts:write", "description": "Create, edit, publish, and delete posts", "group": "Posts" } ], "count": 12 } ``` `GET /v1/keys/scopes` is available to any valid key, including restricted ones. ### Always available A few identity and introspection endpoints stay available to any valid key regardless of its scopes, because they only ever return the caller's own data or static reference material: * `GET /v1/users/me` (your profile) * `GET /v1/usage` (your credit usage) * `GET /v1/keys/scopes` (this scope catalog) * `GET /v1/webhooks/events` (the static webhook event catalog) * the platform capability catalog A scoped key never needs to hold a scope to reach these. ### Admin operations Some operations are never available to a restricted key. They require a full-access key or the dashboard: * Managing API keys (create, list, edit, rotate, revoke) * Editing or deleting your user profile * Creating, updating, or deleting brands (listing brands is allowed with `accounts:read`) * Managing brand invites The reason is escalation: a key that could call `POST /v1/keys` could mint itself an unrestricted key. Denying the whole admin tier to restricted keys closes that path completely. Create and manage keys from the dashboard, then hand the scoped keys to your machines. A restricted key **cannot create, edit, rotate, or revoke any key**, including itself. Rotate or revoke a scoped key with a full-access key or from the dashboard. ## Brand restrictions A brand-restricted key behaves as if the allowed brands are the only brands that exist. Filtering is **silent**: * **Reads** of an out-of-scope account, brand, conversation, or interaction return the ordinary `404` (`account.not_found`, `brand.not_found`, and so on). Never a `403`, and never any confirmation that the resource exists. * **List endpoints** (accounts, brands, posts, conversations, inbox, mentions) return only resources inside the allowed brands. `GET /v1/brands` returns only the allowed brands. ### Posts Posts are not stored with a brand directly; they reach a brand through their target accounts. So the rule is expressed per target account: * **Read.** A post is visible to a brand-restricted key if at least one of its target accounts belongs to an allowed brand. A draft with no targets is therefore invisible to a brand-restricted key. * **Write** (create, edit, publish, retry, delete). Every target account must belong to an allowed brand. A violation returns the same `404 account.not_found` as a nonexistent account, so there is no information leak. ### Media stays shared `media:read` and `media:write` remain available to a brand-restricted key. The media library is a single account-level asset pool. An upload is inert until it is attached to a post, and post writes are brand-checked, so a brand-scoped key cannot use media to reach another client's brand. ### Deleting a brand never widens a key When you delete a brand that appears in a key's `brand_ids`, the now-stale ID stays in the array and simply matches nothing. It is deliberately not pruned: emptying the array would flip the key back to unrestricted, which would be a silent privilege escalation. ## Account-level scopes and brand restriction `webhooks:manage` and `analytics:read` govern data that is not partitioned by brand today. Webhook endpoints and deliveries, and the events log and exports, all span every brand in the account. Because of that, a **brand-restricted** key is denied these two scopes even when the scope is granted. A key that is scope-restricted but **not** brand-restricted may hold them normally. In the dashboard, selecting one or more brands disables the `webhooks:manage` and `analytics:read` checkboxes for this reason. ## Creating a scoped key Pass `scopes` and `brand_ids` to `POST /v1/keys`. Both are optional; omit or leave empty for full access on that dimension. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/keys \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme client bot", "scopes": ["inbox:read", "inbox:write", "dms:send"], "brand_ids": ["1c9f6f0e-2b3a-4c5d-8e7f-0a1b2c3d4e5f"] }' ``` The full key is returned **once** in `raw_key`. The restriction fields are echoed back so you can confirm what was applied: ```json theme={null} { "id": "b7e2...", "name": "Acme client bot", "raw_key": "sapi_key_...", "message": "Store this key securely. It will not be shown again.", "scopes": ["inbox:read", "inbox:write", "dms:send"], "brand_ids": ["1c9f6f0e-2b3a-4c5d-8e7f-0a1b2c3d4e5f"] } ``` Creating a key is itself an admin operation, so only a full-access key or the dashboard can mint keys. ### Validation * An unknown scope returns `400 validation.scope_invalid`, naming the offending value. * A brand ID that does not exist or that you do not own returns `400 validation.brand_invalid`. ## Reading a key's restrictions `GET /v1/keys` returns the `scopes` and `brand_ids` on every key. Both arrays are always present. Two empty arrays mean full access. ```json theme={null} { "id": "b7e2...", "name": "Acme client bot", "preview": "sapi_key_a1b2c...", "is_active": true, "last_used_at": "2026-07-12T12:00:00Z", "created_at": "2026-07-10T09:00:00Z", "scopes": ["inbox:read", "inbox:write", "dms:send"], "brand_ids": ["1c9f6f0e-2b3a-4c5d-8e7f-0a1b2c3d4e5f"] } ``` ## Changing a key's restrictions Restrictions are editable after creation. Use a full-access key or the dashboard. **Edit in place** with `PATCH /v1/keys/{id}`. This replaces the name, scopes, and brands. The key value is unchanged, so live automations keep working with their existing secret: ```bash theme={null} curl -X PATCH https://api.social-api.ai/v1/keys/ \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"scopes": ["inbox:read", "inbox:write"], "brand_ids": []}' ``` **Rotate the secret** with `POST /v1/keys/{id}/rotate`. This issues a new `raw_key` while keeping the same id, name, scopes, and brands. The old secret stops working immediately. Use it when a key may have leaked: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/keys//rotate \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ## Error responses When a restricted key is refused, the response is `403` with code `auth.insufficient_scope`. The exact message depends on the reason: | Reason | Message | `meta.required_scope` | | --------------------------------------------- | ---------------------------------------------------------------- | --------------------- | | Missing scope | `This API key does not have the required scope: posts:write` | the required scope | | Account-level scope on a brand-restricted key | `This operation is not available to brand-restricted API keys` | the scope | | Admin operation | `This operation requires a full-access API key or the dashboard` | absent | | Operation the key cannot perform | `This API key cannot perform this operation` | absent | ```json theme={null} { "error": { "code": "auth.insufficient_scope", "message": "This API key does not have the required scope: posts:write", "meta": { "required_scope": "posts:write" } }, "request_id": "a1b2c3d4" } ``` A resource outside the key's allowed brands returns its ordinary `404` (for example `account.not_found`), never a `403`. This is deliberate: a scoped key gets no signal about whether resources it cannot reach exist. Enforcement is **fail-closed**. A new operation that has not been mapped to a scope is denied to restricted keys rather than left open. Full-access keys, dashboard JWTs, and OAuth 2.1 access tokens (used by MCP clients) are never scope-restricted. ## Configuring from the dashboard In the dashboard, **Keys** > **New Key** offers a permissions step: choose **Full access** (default) or **Restricted**. When restricted, pick scopes grouped by resource and select one or more brands (default is all brands). The keys table shows each key's restrictions at a glance. # Use cases Source: https://docs.social-api.ai/guides/use-cases Common ways developers and AI agents use the SocialAPI.ai unified social media API: unified inbox, management, publishing, and AI agents. SocialAPI.ai is one REST API and MCP server across every social network. Here are the four most common ways teams use it. ## Unified inbox Read and respond to comments, DMs, mentions, and reviews across platforms via one endpoint that returns the same `Interaction` schema, so your inbox logic is written once. ```bash theme={null} curl https://api.social-api.ai/v1/inbox/conversations \ -H "Authorization: Bearer $SOCAPI_KEY" ``` See the [Unified Inbox API](https://social-api.ai/solutions/unified-inbox-api) overview. ## Social media management Schedule posts, moderate comments, and reply to DMs across networks from one API, without per-platform glue code. ```bash theme={null} curl https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" ``` See the [Social Media Management API](https://social-api.ai/solutions/social-media-management-api) overview. ## Content publishing Publish and schedule photos, videos, carousels, stories, and reels across networks via one endpoint, with per-platform delivery status. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Hello from SocialAPI!", "publish_now": true, "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }] }' ``` See the [Content Publishing API](https://social-api.ai/solutions/content-publishing-api) overview and the [Posts](/posts/overview) reference. ## AI agents (MCP) Every capability is a typed Model Context Protocol tool, so agents in Claude, ChatGPT, and Cursor manage social media with no platform-specific code. See the [Social Media API for AI Agents](https://social-api.ai/solutions/social-media-api-for-ai-agents) overview and the [Integrations](/integrations/overview) guides. # Webhooks Source: https://docs.social-api.ai/guides/webhooks Receive real-time notifications when interactions arrive on your connected accounts. SocialAPI.ai webhooks deliver real-time social media events (new comments, direct messages, and mentions) across every connected network through one signed, retried webhook stream, so you do not poll each platform separately. Instead of polling the API, register a webhook endpoint and SocialAPI will POST a signed payload to your server whenever a new comment, DM, review, or mention arrives, or whenever one of your posts changes lifecycle state (scheduled, published, deleted, etc.). ## Register an endpoint Endpoints can be registered in the dashboard under **Webhooks** → **Add Endpoint**, or via the API: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/webhooks \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://app.example.com/webhooks/socapi", "events": ["comment.received", "dm.received", "review.received"] }' ``` Response (HTTP 201): ```json theme={null} { "id": "wh_01HZ9X3Q4R5M6N7P8V2K0W1J", "url": "https://app.example.com/webhooks/socapi", "events": ["comment.received", "dm.received", "review.received"], "secret": "a3f8c2e1b9d4f7a6c5e0b3d2f1a8e7c4b9d6f3a2e5c8b1d4f7a0e3c6b9d2f5a8", "message": "Store the secret securely. It will not be shown again." } ``` The `secret` is returned **once**. Store it immediately in your environment. It cannot be retrieved again. You use it to verify incoming signatures. Endpoint URLs must use HTTPS. HTTP URLs are rejected with `400`. The `events` array is required. Sending an empty array or omitting it returns `400 field_invalid`. When you create an endpoint, SocialAPI sends a verification ping to your URL. Your server must respond with a `2xx` status code or the endpoint will not be saved. Make sure your server is running and reachable before registering. The verification ping arrives as a `webhook.test` event, signed with the endpoint's new secret. Because the secret is only revealed to you after verification succeeds, your endpoint cannot verify this first signature. Respond `2xx` to requests with `X-SocialAPI-Event: webhook.test` without checking the signature, or registration will fail with `webhook_verification_failed`. ## Event types Events are grouped into three categories: **inbox** (incoming interactions), **posts** (post lifecycle transitions), and **accounts** (connection state). | Event | Category | When it fires | | ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `comment.received` | inbox | A new comment arrives on any connected account | | `dm.received` | inbox | A new direct message arrives (may include `metadata.referral` if the DM originated from an ad) | | `dm.sent` | inbox | Your account sent a direct message (echo confirmation from Meta). Fires for every echo the platform delivers, including replies sent from the native app or Meta Business Suite, not only messages sent through the API. On Facebook the echo's `raw_payload` carries `app_id`, which lets you tell the two apart | | `dm.referral` | inbox | A user clicked an ad or referral link without sending a message. The interaction has empty content and includes `metadata.referral` | | `dm.postback` | inbox | A user tapped a button or quick reply in a DM | | `review.received` | inbox | A new review arrives (Google Business Profile) | | `mention.received` | inbox | Your account is @mentioned in a comment or a post caption (`metadata.mention_type` is `comment` or `caption`). Instagram story mentions are delivered as `dm.received` with an attachment of type `story_mention`, not as this event | | `dm.status.sent` | inbox | A direct message you sent was accepted by the platform (Instagram, Facebook, WhatsApp) | | `dm.status.delivered` | inbox | A direct message you sent was delivered to the recipient (Facebook and WhatsApp; Instagram sends no delivery receipts) | | `dm.status.read` | inbox | A direct message you sent was read by the recipient (Instagram, Facebook, WhatsApp) | | `dm.status.failed` | inbox | A direct message you sent failed to deliver (WhatsApp only) | | `post.scheduled` | posts | A post was created with a future `scheduled_at` and entered the `scheduled` state | | `post.published` | posts | A post finished publishing successfully on all targets | | `post.partial` | posts | A post published on some targets but failed on others | | `post.failed` | posts | A post failed to publish on all targets | | `post.updated` | posts | A draft, scheduled, or failed post was edited | | `post.deleted` | posts | A post was deleted. The payload includes `was_status` so you know what state the post was in before removal | | `post.unpublished` | posts | A published post was removed from one or more platforms (the post row stays unless every target is unpublished) | | `post.retried` | posts | A failed or partial post is being retried. A subsequent `post.published`, `post.partial`, or `post.failed` event fires once the retry completes | | `account.connected` | accounts | A social account finished connecting. Payload includes username, display name, profile picture, bio, and a public profile link | | `account.disconnected` | accounts | A connected account was revoked by the user on the platform side (Meta deauthorize webhook, TikTok deauthorize webhook), or flagged for reconnection because all of its pages were removed or its access was revoked. When the payload includes `reconnect_required: true`, the account still exists but needs to be reconnected | | `page.removed` | accounts | A page on a multi-page account (for example a Facebook Page) was detected as removed during a periodic refresh and was deactivated. Other pages on the same account are unaffected | You can subscribe to any combination. Fetch the authoritative list with `GET /v1/webhooks/events` and subscribe to everything by passing the full set. The authoritative list of event types is available at `GET /v1/webhooks/events`. It returns each event's description and category, so dashboard UIs and integrations can render the catalog without hard-coding it. ### Post lifecycle payloads Each post lifecycle event carries a small payload with the `post_id` and event-specific fields. ```json theme={null} { "event": "post.scheduled", "data": { "post_id": "post_01HZ9X3Q4R5M6N7P8V2K0W1J", "status": "scheduled", "scheduled_at": "2026-06-01T14:00:00Z", "targets": [ { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform": "instagram" }, { "account_id": "acc_02HZ9X3Q4R5M6N7P8V2K0W1J", "platform": "facebook" } ] } } ``` ```json theme={null} { "event": "post.updated", "data": { "post_id": "post_01HZ9X3Q4R5M6N7P8V2K0W1J", "status": "scheduled", "changed_fields": ["text", "scheduled_at"] } } ``` ```json theme={null} { "event": "post.deleted", "data": { "post_id": "post_01HZ9X3Q4R5M6N7P8V2K0W1J", "was_status": "published" } } ``` ```json theme={null} { "event": "post.unpublished", "data": { "post_id": "post_01HZ9X3Q4R5M6N7P8V2K0W1J", "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "unpublished_count": 1 } } ``` When `account_id` is `null` on a `post.unpublished` event, the post was unpublished from every target. ```json theme={null} { "event": "post.retried", "data": { "post_id": "post_01HZ9X3Q4R5M6N7P8V2K0W1J", "retried_count": 2 } } ``` `post.published`, `post.partial`, and `post.failed` share a `{ post_id, status, results }` payload, where `results` is the same per-target outcome array returned by `POST /v1/posts`. ### `account.connected` payload Fires once per connected account after a successful OAuth exchange or direct connection. Use this to render the connected profile in your UI or kick off post-connect work. ```json theme={null} { "event": "account.connected", "data": { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "brand_id": "br_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform": "instagram", "platform_user_id": "17841405793187218", "username": "acmecorp", "display_name": "ACME Corp", "profile_picture_url": "https://scontent.cdninstagram.com/...", "bio": "Official ACME account", "profile_url": "https://www.instagram.com/acmecorp/", "user_email": "owner@acme.com", "connected_at": "2026-05-25T14:30:00Z" } } ``` `profile_url` is built per platform from `username` (Instagram, Threads, TikTok, X) or `platform_user_id` (Facebook, LinkedIn, YouTube, Google Business Profile). It is empty when neither is available. ### `page.removed` payload Fires when a single page on a multi-page account (for example a Facebook Page) is detected as removed during a periodic refresh. The page is deactivated and stops appearing in page listings. Other pages on the same account stay active. ```json theme={null} { "event": "page.removed", "data": { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform_page_id": "102938475610293", "name": "ACME Storefront", "platform": "facebook", "reason": "page_not_found" } } ``` ### `account.disconnected` payload Fires in two cases. A platform-side revocation (the user removes the app) carries no `reconnect_required` flag and the account is deleted. A reconnection prompt carries `reconnect_required: true`: the account row still exists but its status is set so that calls signal it needs reconnecting. The `reason` is `all_pages_removed` (the last remaining page was removed) or `access_revoked` (the account token or permissions were revoked, which fails every page at once). ```json theme={null} { "event": "account.disconnected", "data": { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform": "facebook", "reason": "all_pages_removed", "reconnect_required": true } } ``` ## Payload format Every webhook POST has a JSON body with an `event` field and a `data` field containing the full interaction object: ```json theme={null} { "event": "comment.received", "data": { "id": "sapi_cmt_aW5zdGFncmFtOjE3ODQxNDA1", "type": "comment", "platform": "instagram", "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "author": { "id": "10215054824", "name": "Jane Smith", "avatar_url": "https://example.com/avatar.jpg" }, "content": { "text": "Love this product!", "media": [] }, "received_at": "2026-03-01T14:30:00Z", "metadata": {} } } ``` The `data` object is the same `Interaction` shape returned by `GET /accounts/{id}/comments` (and the equivalent DM, review, and mention endpoints). The `id` field is a stable SocialAPI interaction ID - see [Interaction IDs](/guides/interaction-ids). For platform-originated events, the body also includes a top-level `raw_payload` field alongside `data`: the verbatim per-event webhook fragment as the platform sent it to us. Use it when you need a platform detail the normalized `data` object does not carry (for example, `app_id` on Facebook message echoes to distinguish API-sent messages from replies typed in the Page inbox). The shape of `raw_payload` is defined by the platform and can change with platform API versions, so prefer `data` whenever it has what you need. The `dm.status.*` events use a smaller payload instead of the full interaction object: `data` contains `status`, `mids` (the platform message IDs the receipt covers), and `recipient_id`. ### Referral metadata When a DM originates from an ad click (Instagram CTD or Facebook CTM ads), the interaction includes referral context in `metadata.referral`: ```json theme={null} { "event": "dm.received", "data": { "id": "sapi_dm_aW5zdGFncmFtOm1pZC5yZWYx", "type": "dm", "platform": "instagram", "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "author": { "id": "17846744073097661" }, "content": { "text": "I saw your ad!" }, "received_at": "2026-03-01T14:30:00Z", "metadata": { "referral": { "ad_id": "120210572164750432", "source": "ADS", "type": "OPEN_THREAD", "ads_context_data": { "ad_title": "Spring Sale 25% Off", "photo_url": "https://scontent.xx.fbcdn.net/..." } } } } } ``` Only non-empty referral fields are included. `dm.referral` events (standalone ad clicks without a message) have empty `content.text` and always include `metadata.referral`. ## Request headers Every webhook request includes: | Header | Value | | -------------------------- | -------------------------------------------------------------------------------------- | | `Content-Type` | `application/json` | | `X-SocialAPI-Signature` | `sha256=` of the raw body (v1, kept for backward compatibility) | | `X-SocialAPI-Signature-V2` | `sha256=` of `.`, for replay protection | | `X-SocialAPI-Timestamp` | Unix timestamp (seconds) of when the delivery attempt was sent | | `X-SocialAPI-Delivery` | Unique delivery ID, stable across retries of the same delivery. Use it for idempotency | | `X-SocialAPI-Event` | The event type, e.g. `comment.received` | `X-SocialAPI-Delivery` is present on real event deliveries. Verification pings and test deliveries carry the signature, timestamp, and event headers but no delivery ID. ## Verifying signatures Always verify the `X-SocialAPI-Signature` header before processing a webhook. This confirms the payload came from SocialAPI and was not tampered with. The signature is HMAC-SHA256 of the raw request body, using your endpoint secret as the key, prefixed with `sha256=`. ```javascript theme={null} import crypto from "crypto"; function verifySignature(secret, rawBody, signatureHeader) { const expected = "sha256=" + crypto .createHmac("sha256", secret) .update(rawBody) .digest("hex"); return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(signatureHeader) ); } // Express example app.post("/webhooks/socapi", express.raw({ type: "application/json" }), (req, res) => { const sig = req.headers["x-socialapi-signature"]; if (!verifySignature(process.env.SOCAPI_WEBHOOK_SECRET, req.body, sig)) { return res.status(401).send("Invalid signature"); } const event = JSON.parse(req.body); // handle event.event and event.data res.sendStatus(200); }); ``` ```python theme={null} import hmac import hashlib def verify_signature(secret: str, raw_body: bytes, signature_header: str) -> bool: expected = "sha256=" + hmac.new( secret.encode(), raw_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature_header) # Flask example @app.route("/webhooks/socapi", methods=["POST"]) def webhook(): sig = request.headers.get("X-SocialAPI-Signature", "") if not verify_signature(os.environ["SOCAPI_WEBHOOK_SECRET"], request.data, sig): abort(401) event = request.json # handle event["event"] and event["data"] return "", 200 ``` ```go theme={null} func verifySignature(secret string, body []byte, sigHeader string) bool { mac := hmac.New(sha256.New, []byte(secret)) mac.Write(body) expected := "sha256=" + hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(expected), []byte(sigHeader)) } ``` Use a constant-time comparison (`timingSafeEqual` / `compare_digest` / `hmac.Equal`) to prevent timing attacks. A simple string equality check is not safe. ## Replay protection The v1 signature proves authenticity but not freshness: a captured request could be replayed later and would still verify. To bound replay, use the v2 scheme: 1. Read `X-SocialAPI-Timestamp` and reject the request if it is older than your tolerance window (5 minutes is a sensible default). 2. Compute HMAC-SHA256 over the string `.` (the timestamp header value, a literal dot, then the raw request body) with your endpoint secret, prefix with `sha256=`, and compare it to `X-SocialAPI-Signature-V2` in constant time. Because the timestamp is inside the signed message, an attacker cannot extend the window by editing the header. ```javascript theme={null} function verifySignatureV2(secret, rawBody, timestampHeader, signatureHeader, toleranceSec = 300) { const age = Math.abs(Date.now() / 1000 - Number(timestampHeader)); if (!Number.isFinite(age) || age > toleranceSec) return false; const expected = "sha256=" + crypto .createHmac("sha256", secret) .update(timestampHeader + "." + rawBody) .digest("hex"); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader)); } ``` For exact once-only processing, additionally record `X-SocialAPI-Delivery` IDs you have already handled and skip duplicates. The ID is stable across retries of the same delivery, so this also deduplicates legitimate retries after a timeout. The timestamp reflects the current delivery attempt, so a retry of an old event carries a fresh timestamp and still passes the window check. Use the delivery ID for deduplication; use the timestamp to reject replays of captured requests. ## Responding to webhooks Your endpoint must return an HTTP `2xx` status within **10 seconds**. The webhook HTTP client enforces a 10-second timeout per delivery attempt. Any non-2xx response or a timeout is treated as a delivery failure. Return `200` immediately and process the event asynchronously if your handler does heavier work. ## Retry behavior Failed deliveries are automatically retried up to **5 attempts** with exponential backoff: | Attempt | Delay | | ------- | ------------ | | 1 | Immediate | | 2 | \~30 seconds | | 3 | \~5 minutes | | 4 | \~30 minutes | | 5 | \~3 hours | After 5 failed attempts, the delivery is marked `failed` and no further retries occur. ## Managing endpoints ### List endpoints ```bash theme={null} curl https://api.social-api.ai/v1/webhooks \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ### Get endpoint details Retrieve a single endpoint with delivery statistics (delivered/failed counts for the last 24 hours and 7 days). The secret is shown as a hint (last 4 characters only). ```bash theme={null} curl https://api.social-api.ai/v1/webhooks/{id} \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ### Update an endpoint Change the URL, subscribed events, or toggle delivery on/off. All fields are optional; provide at least one. ```bash theme={null} curl -X PATCH https://api.social-api.ai/v1/webhooks/{id} \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"is_active": false}' ``` ### Delete an endpoint ```bash theme={null} curl -X DELETE https://api.social-api.ai/v1/webhooks/{id} \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Deleting an endpoint stops all future deliveries immediately. In-flight jobs already queued may still attempt delivery once. ## Delivery monitoring Every webhook delivery is recorded with its status, HTTP response code, and duration. You can inspect delivery history, view individual attempts, send test payloads, and retry failed deliveries. | Endpoint | Description | | ----------------------------------------------- | --------------------------------------------------------------------------- | | `GET /v1/webhooks/{id}/deliveries` | List deliveries (filterable by `status` and `event_type`, cursor-paginated) | | `GET /v1/webhooks/{id}/deliveries/{did}` | Full delivery detail including payload and all retry attempts | | `POST /v1/webhooks/{id}/deliveries/{did}/retry` | Re-enqueue a failed delivery | | `POST /v1/webhooks/{id}/test` | Send a test payload to verify your endpoint is working | See the [API Reference](/api-reference) for full request and response schemas. ## Security best practices * **Always verify signatures** - never trust a webhook payload without checking `X-SocialAPI-Signature` (or, preferably, `X-SocialAPI-Signature-V2`) * **Bound replay** - prefer the v2 signature with a timestamp tolerance window (see [Replay protection](#replay-protection)) * **Store your secret in an environment variable** - never hardcode it or commit it to source control * **Use HTTPS** - HTTP endpoints are rejected at registration time * **Respond quickly** - return `200` before doing heavy processing to avoid timeouts and spurious retries * **Make handlers idempotent** - retries mean the same event may arrive more than once; use `X-SocialAPI-Delivery` (stable across retries) or the interaction `id` to deduplicate # SocialAPI.AI Source: https://docs.social-api.ai/index Unified social media API for developers. Manage your inbox, publish content, and monitor engagement across Instagram, Facebook, Threads, TikTok, YouTube, and X (Twitter) through a single REST API. Google Business and LinkedIn coming soon. ## What is SocialAPI.AI? SocialAPI.ai is a unified social media API for developers and AI agents. With one REST API and a native MCP server you can build a unified inbox (comments, DMs, mentions, reviews), manage and schedule content, publish across networks, and give AI agents social media access, all across Instagram, Facebook, TikTok, LinkedIn, YouTube, X, and Threads. You don't need to set up developer apps with Meta, TikTok, or YouTube - [SocialAPI](https://social-api.ai)'s apps are already approved and we handle the OAuth. Twitter is the one exception ([why](/guides/platform-credentials#why-twitter-is-different)). See [plans and pricing](https://social-api.ai/pricing) for what each tier includes. ## Supported platforms | Platform | Inbox | Publishing | Status | | ----------------------- | -------- | ---------- | ---------------- | | Instagram | ✅ | ✅ | Available | | Facebook | ✅ | ✅ | Available | | Threads | ✅ | ✅ | Available | | TikTok | - | ✅ | Available | | YouTube | ✅ | ✅ | Available | | LinkedIn Personal | - | ✅ | Available | | LinkedIn Organization | Soon | Soon | Coming soon | | Google Business Profile | ✅ | ✅ | Available | | X / Twitter | ✅ | ✅ | Available (BYOK) | | Telegram | DMs only | - | Available | | WhatsApp | DMs only | - | Beta | | Trustpilot | Soon | - | Coming soon | For per-feature support (reply, hide, like, delete, private reply, DMs, reviews, mentions), see [Platform support](/guides/platforms). Make your first API call in 5 minutes. Capabilities by platform. Connect to Claude, ChatGPT, Cursor, and more. Full reference for every endpoint. Get notified of new comments, messages, and events. Resource limits and billing. ## Core capabilities ### Unified inbox Use this when your app needs to read or respond to anything inbound across platforms (comments, DMs, reviews) without writing per-platform glue. Comments, DMs, and reviews are organized under `/v1/inbox` with consistent response shapes across all platforms. ```bash theme={null} # List posts with comments curl https://api.social-api.ai/v1/inbox/comments \ -H "Authorization: Bearer $SOCAPI_KEY" # Reply to a comment curl -X POST https://api.social-api.ai/v1/inbox/comments/POST_ID \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"text": "Thanks for your feedback!", "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J"}' # List DM conversations curl https://api.social-api.ai/v1/inbox/conversations \ -H "Authorization: Bearer $SOCAPI_KEY" # List reviews curl https://api.social-api.ai/v1/inbox/reviews \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ### Publishing Use this when your users publish from your app and you want one API call to fan out to multiple connected accounts. Create, schedule, and manage posts across platforms; track delivery status per platform; monitor engagement metrics. ```bash theme={null} # Create and schedule a post to Instagram and Facebook curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Hello from SocialAPI!", "targets": [ {"account_id": "acc_instagram"}, {"account_id": "acc_facebook"} ], "scheduled_at": "2026-04-01T12:00:00Z" }' # Check post metrics curl https://api.social-api.ai/v1/posts/POST_ID/metrics \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ### Media library Use this when you upload media in advance (content calendar, batch uploads) and reference it from later post creates. Presigned upload URLs keep your media pipeline fast and secure. ```bash theme={null} # Get a presigned upload URL curl "https://api.social-api.ai/v1/media/upload-url?filename=photo.jpg&media_type=image/jpeg" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ### AI assistant integrations Use this when an AI agent (Claude, ChatGPT, Cursor) should manage social accounts directly via tool calls. SocialAPI.AI exposes a full MCP server with 78 tools and OAuth 2.1 with PKCE for secure agent access. [Set up AI integrations →](/integrations/overview) ## Authentication All requests require a Bearer token: ```bash theme={null} Authorization: Bearer sapi_key_... ``` See [Authentication](/guides/authentication) for details on API keys, JWTs, and OAuth tokens. # ChatGPT Source: https://docs.social-api.ai/integrations/chatgpt Connect SocialAPI.AI to ChatGPT on web, desktop, and mobile. **MCP Server URL:** `https://api.social-api.ai/mcp` ## Web & Desktop ### Prerequisites * A [SocialAPI.AI](https://social-api.ai) account with at least one connected social account * Any ChatGPT plan (Apps are supported on all plans, including Free, Plus, Pro, Business, Enterprise, and Education) ### 1. Enable Developer Mode Go to **Settings → Apps & Connectors → Advanced settings** and toggle **Developer Mode** on. ChatGPT Apps & Connectors advanced settings ### 2. Create a connector Go to **Settings → Connectors → Create** and fill in: * **Name:** `SocialAPI.AI` * **Description:** `Manage social media accounts, posts, comments, DMs, reviews, and mentions` * **Connector URL:** `https://api.social-api.ai/mcp` ChatGPT will test the connection and display the available tools if successful. ChatGPT create connector dialog ### 3. Authorize with your account When you first use a [SocialAPI.AI](https://social-api.ai) tool, ChatGPT initiates an OAuth 2.1 flow with PKCE. A browser window opens to the [SocialAPI.AI](https://social-api.ai) authorization page. Log in and click **Authorize**. ChatGPT OAuth authorization for SocialAPI.AI ChatGPT supports OAuth 2.1 with PKCE and Dynamic Client Registration. It auto-discovers your authorization server via `/.well-known/oauth-protected-resource` - no manual auth configuration is needed. ### 4. Enable in a conversation Open a new conversation, click the **+** button near the message composer, select **More**, and toggle your [SocialAPI.AI](https://social-api.ai) connector on. As of December 2025, OpenAI renamed "connectors" to "apps" in parts of the UI. The functionality is the same. *** ## Mobile (iOS / Android) Connectors created on ChatGPT web are available on the ChatGPT mobile apps (iOS and Android). You cannot create new connectors from the mobile app - set them up on the web first. 1. Add [SocialAPI.AI](https://social-api.ai) as a connector on [chatgpt.com](https://chatgpt.com) (see [Web & Desktop](#web--desktop) above) 2. Open the ChatGPT mobile app - the connector is already available 3. Enable it in a conversation via the **+** menu # Claude Source: https://docs.social-api.ai/integrations/claude Connect SocialAPI.AI to Claude on web, desktop, CLI, and mobile. **MCP Server URL:** `https://api.social-api.ai/mcp` ## Web (claude.ai) ### Prerequisites * A [SocialAPI.AI](https://social-api.ai) account with at least one connected social account * Claude Free (1 custom connector), Pro, Max, Team, or Enterprise plan ### 1. Open Connectors Click your **profile icon**, then go to **Settings** → **Connectors**. Claude Settings page with Connectors section ### 2. Add a custom connector Scroll to the bottom and click **Add custom connector**. Add custom connector button in Claude Enter the server name and URL: ``` SocialAPI.ai ``` ``` https://api.social-api.ai/mcp ``` Click **Add**. Add custom connector dialog in Claude ### 3. Authorize with your account Claude will initiate an OAuth flow. A browser window will open to the [SocialAPI.AI](https://social-api.ai) authorization page. [SocialAPI.AI](https://social-api.ai) OAuth authorization screen Log in with your [SocialAPI.AI](https://social-api.ai) credentials and click **Authorize**. The window closes automatically when done. This uses OAuth 2.1 with PKCE. No API key is required - your session is handled automatically. ### 4. Enable in a conversation Open a new Claude conversation. Click the **+** icon at the bottom-left of the input, select **Connectors**, and ensure **SocialAPI.AI** is toggled on. Claude conversation using [SocialAPI.AI](https://social-api.ai) tools to list accounts Try asking: > "List my connected social accounts" > "Show me the latest comments on my Instagram posts" > "Reply to comment sapi\_cmt\_xxx with 'Thanks for your feedback!'" **Team / Enterprise:** Organization owners add connectors via **Organization settings → Connectors**. Members then go to **Settings → Connectors** and click **Connect** on the connector the owner configured. *** ## Desktop App ### Prerequisites * Claude Desktop installed ([download](https://claude.ai/download)) * Claude Free (1 custom connector), Pro, Max, Team, or Enterprise plan ### Setup via the app UI (recommended) Remote MCP servers are added through the app's **Settings → Connectors** - the same flow as claude.ai. Claude Desktop and claude.ai share the same account settings, so a connector added on either one appears on both. 1. Open Claude Desktop 2. Go to **Settings → Connectors** 3. Click **Add custom connector** 4. Enter `SocialAPI.ai` as the name and `https://api.social-api.ai/mcp` as the URL 5. Complete the OAuth authorization in the browser window that opens Claude Desktop OAuth authorization flow The `claude_desktop_config.json` file only supports **local stdio servers** (via `command` + `args`). Remote MCP servers cannot be added directly to this file - use Settings → Connectors instead. **Config file locations** (for local stdio servers only): * **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` * **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` *** ## Claude Code (CLI) ### Add via command line ```bash theme={null} claude mcp add socialapi --transport http https://api.social-api.ai/mcp ``` Claude Code will open your browser for OAuth authorization on first use. ### Scopes By default, the server is added to the **local** scope (current project only). Use `--scope user` for all projects or `--scope project` to share via `.mcp.json`: ```bash theme={null} claude mcp add socialapi --transport http --scope user https://api.social-api.ai/mcp ``` ### Manage servers ```bash theme={null} claude mcp list # list configured servers claude mcp remove socialapi # remove a server ``` Claude Code uses its own subscription (Max plan or Claude Code subscription). No separate Pro plan is required. ### Troubleshooting **Authorization does not open browser** - Ensure your system default browser is configured. On headless/remote systems, Claude Code will print the authorization URL for you to open manually. *** ## Mobile (iOS / Android) Remote MCP servers work on Claude mobile. You **cannot add new servers from the mobile app** - configure them first on [claude.ai](https://claude.ai) via **Settings → Connectors**, and they automatically become available on mobile. 1. Add [SocialAPI.AI](https://social-api.ai) as a connector on claude.ai (see [Web](#web-claudeai) above) 2. Open the Claude mobile app - the connector is already available 3. Enable it in a conversation via the **+** menu → **Connectors** Requires Claude Free (1 connector), Pro, Max, Team, or Enterprise plan. Same plan as your web account. # Cursor Source: https://docs.social-api.ai/integrations/cursor Connect SocialAPI.AI to Cursor via config file or Settings UI. **MCP Server URL:** `https://api.social-api.ai/mcp` ## Prerequisites * [Cursor](https://cursor.com) installed * A [SocialAPI.AI](https://social-api.ai) account with at least one connected social account ## Config file Add to your project config (`.cursor/mcp.json`) or global config (`~/.cursor/mcp.json`): ```json theme={null} { "mcpServers": { "SocialAPI": { "url": "https://api.social-api.ai/mcp" } } } ``` No tokens or credentials are needed in the config - Cursor handles OAuth automatically. When you first use a [SocialAPI.AI](https://social-api.ai) tool, Cursor discovers the OAuth metadata, opens your browser for authorization, and exchanges the code using PKCE. The token is stored and refreshed automatically. Cursor supports both **SSE** and **Streamable HTTP** transports for remote servers. OAuth with Dynamic Client Registration and PKCE is handled automatically. ## Settings UI 1. Open **Cursor Settings** (gear icon) 2. Go to **Tools & Integrations** in the left sidebar 3. Under **MCP Tools**, click **Add Custom MCP** 4. Enter the URL: `https://api.social-api.ai/mcp` 5. Click the **Needs login** button to start the OAuth flow 6. Authorize in the browser window that opens Cursor MCP Tools settings ## Using in chat Open **Agent** mode in Cursor. [SocialAPI.AI](https://social-api.ai) tools appear under **Available Tools** and are invoked automatically when relevant. You can also mention a tool by name in your prompt. ## Debugging If the connection fails, open the **Output** panel (`Cmd+Shift+U` / `Ctrl+Shift+U`) and select **MCP Logs** from the dropdown. # Custom MCP clients Source: https://docs.social-api.ai/integrations/custom Connect any MCP client to SocialAPI.AI using an API key, for headless agents, server-side scripts, and CI jobs. **MCP Server URL:** `https://api.social-api.ai/mcp` If your MCP client does not implement OAuth 2.1, or you want to run an agent in a non-interactive environment (server, container, CI job, cron), authenticate with an API key on the standard `Authorization: Bearer` header. ## Prerequisites * A [SocialAPI.AI](https://social-api.ai) account with at least one connected social account * An API key created in the [dashboard](https://app.social-api.ai/settings/keys) (format: `sapi_key_...`) API keys are long-lived credentials. Store them in a secret manager or environment variable, never in source control. ## Config file Most MCP clients accept a custom `Authorization` header in the server config. Set it to your API key prefixed with `Bearer`: ```json theme={null} { "mcpServers": { "SocialAPI": { "url": "https://api.social-api.ai/mcp", "headers": { "Authorization": "Bearer sapi_key_xxxxxxxxxxxxxxxx" } } } } ``` The exact field name varies by client. Some use `headers`, others use `requestInit.headers` or a top-level `auth` block - check your client's MCP documentation. ## Raw HTTP example For a fully custom integration, the MCP endpoint speaks Streamable HTTP. Send tool calls as JSON-RPC over POST: ```bash theme={null} curl https://api.social-api.ai/mcp \ -H "Authorization: Bearer sapi_key_xxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }' ``` The same `Authorization` header is required on every request. API-key auth skips the OAuth dance entirely, so there is no `/oauth/authorize` redirect and no PKCE exchange. ## Scopes and permissions API keys grant access to every tool the owning user can call - the same scope as a `/v1` REST request authenticated with that key. Plan tier and per-platform beta gates apply identically. To restrict an agent to a subset of operations, create a dedicated user with a narrower role and issue the key from that account. ## Rotating keys Rotate by creating a new key, updating the agent config, then revoking the old key in the [dashboard](https://app.social-api.ai/settings/keys). Revocation is immediate; in-flight tool calls with the revoked key return `401`. ## Troubleshooting **`401 invalid or inactive API key`** - The key is wrong, revoked, or missing the `sapi_key_` prefix. Verify it in the [dashboard](https://app.social-api.ai/settings/keys). **`401 missing Authorization header`** - Your client is not forwarding the header. Confirm the field name in its MCP config and that the header survives the transport layer. **Tool not found** - Run `tools/list` first to confirm the tool name. Tool names match the [REST API](/api-reference/introduction) operation IDs. # Mistral / Le Chat Source: https://docs.social-api.ai/integrations/mistral Connect SocialAPI.AI to Le Chat on web and mobile. **MCP Server URL:** `https://api.social-api.ai/mcp` ## Web (chat.mistral.ai) ### Prerequisites * A [SocialAPI.AI](https://social-api.ai) account with at least one connected social account * Any Le Chat plan (MCP connectors are available on the **free plan**) ### Setup 1. Open Le Chat at [chat.mistral.ai](https://chat.mistral.ai) 2. Open the **side panel** and expand the **Intelligence** menu 3. Click **Connectors** 4. Click **+ Add Connector** on the right side 5. Select the **Custom MCP Connector** tab Le Chat Custom MCP Connector configuration 6. Fill in: * **Connector name:** `SocialAPI` * **Connection server:** `https://api.social-api.ai/mcp` * **Description** (optional): `Manage social media accounts, posts, comments, DMs, and more` 7. Click **Connect** Le Chat auto-detects the OAuth 2.1 authentication and guides you through the authorization flow. Le Chat OAuth authorization for SocialAPI.AI ### Using the connector In a conversation, click the **Tools** button (four squares icon) below the chat input and check the box for **SocialAPI**. You can also type `/SocialAPI` in the input to activate it. Le Chat will ask for approval before executing each tool call. You can select **Always Allow** to skip future prompts for that tool. The Connectors feature is currently in **Beta**. MCP connectors are also available on the Le Chat mobile apps (iOS and Android) - connectors configured on the web sync automatically. Le Chat conversation using [SocialAPI.AI](https://social-api.ai) tools ### Troubleshooting **Connector not auto-detected** - Make sure the URL does not have a trailing slash. Enter exactly `https://api.social-api.ai/mcp`. *** ## Mobile (iOS / Android) Le Chat mobile apps support MCP connectors. Configure them on the web at [chat.mistral.ai](https://chat.mistral.ai) and they sync automatically to your mobile app. 1. Add SocialAPI as a connector on the web (see [Web](#web-chatmistralai) above) 2. Open the Le Chat mobile app - the connector is already available 3. Enable it via the **Tools** menu in a conversation # Use with AI Assistants Source: https://docs.social-api.ai/integrations/overview Connect the social media API for AI agents to Claude, ChatGPT, Mistral, Cursor, VS Code, and other AI tools as an MCP server. [SocialAPI.AI](https://social-api.ai) is a social media API for AI agents, exposed as an [MCP](https://modelcontextprotocol.io) (Model Context Protocol) server. Once connected, your AI assistant can list accounts, fetch comments, reply, send DMs, publish posts, and more through natural language. **MCP Server URL:** `https://api.social-api.ai/mcp` ## Authentication The MCP endpoint accepts two credential types on the `Authorization: Bearer` header: | Method | Best for | How to obtain | | ----------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- | | **OAuth 2.1 with PKCE** | Claude, ChatGPT, Cursor, VS Code, Le Chat, and other MCP clients with built-in OAuth | Handled automatically by the client on first tool use | | **API key** | Custom MCP clients, headless agents, server-side scripts, CI jobs | Create one in the [dashboard](https://app.social-api.ai), prefix `sapi_key_` | OAuth-capable clients discover the authorization server via `/.well-known/oauth-protected-resource`, so no credentials are needed in their config files. For everything else, see [Custom MCP clients](/integrations/custom). ## Choose your platform Web, Desktop, Claude Code, Mobile Web, Desktop, Mobile Web, Mobile Config file or Settings UI Config file or Command Palette ## Available tools Your AI assistant has access to **78 tools** covering all [SocialAPI.AI](https://social-api.ai) functionality: | Category | What the assistant can do | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | **Accounts** | List connected accounts, connect new accounts, disconnect, complete OAuth exchange, list account pages, fetch creator info, get per-account limits | | **Brands** | List, create, update, delete brands | | **Posts** | List, create, get, update, delete, retry, unpublish posts; fetch metrics; validate; import | | **Instagram Stories** | List currently-active Instagram Stories (Instagram only) | | **Media** | List, delete media; get upload URLs; upload; verify; check storage usage | | **Comments** | List commented posts, list comments on a post, list reply threads, reply, hide/unhide, like/unlike, delete, send private reply, moderate | | **DMs** | List conversations, get conversation, list and send messages, mark as read | | **Reviews** | List reviews, reply to a review, update or delete a review reply | | **Mentions** | Fetch mentions for an account | | **Inbox posts** | List inbox posts | | **Usage** | Check workspace usage, per-account limits, storage usage, account constraints, publishing constraints | | **Keys** | List, create, revoke API keys | | **Webhooks** | List, create, get, update, delete webhooks; list deliveries; retry deliveries; test webhooks; list event catalog | | **Exports** | Create exports, get an export, list exports, fetch export videos | | **Events** | List events, fetch events summary | | **Invites** | List, create, revoke brand invites | | **Users** | Get/update profile, delete account | | **Feedback** | Send feedback to the [SocialAPI.AI](https://social-api.ai) team | ## Troubleshooting **Authorization fails or loops** - Make sure pop-ups are not blocked in your browser when the OAuth window opens. Some browser extensions (ad blockers, privacy tools) can interfere with the redirect. **Tool calls fail with 401** - Your OAuth session may have expired. Remove the [SocialAPI.AI](https://social-api.ai) connection and re-add it to trigger a fresh authorization. If you authenticate with an API key, check that the key is still active in the [dashboard](https://app.social-api.ai/settings/keys). **"MCP server not responding"** - Verify the URL is exactly `https://api.social-api.ai/mcp`. Check [status.social-api.ai](https://status.social-api.ai) for any ongoing incidents. ## Still have questions? If you have any issues or want to learn more about using [SocialAPI.AI](https://social-api.ai) with AI assistants, reach out to our support team at [support@social-api.ai](mailto:support@social-api.ai). # VS Code + GitHub Copilot Source: https://docs.social-api.ai/integrations/vscode Connect SocialAPI.AI to VS Code with GitHub Copilot via config file or Command Palette. **MCP Server URL:** `https://api.social-api.ai/mcp` ## Prerequisites * [VS Code](https://code.visualstudio.com) (1.101+) with [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) extension * A GitHub Copilot subscription (Individual, Business, or Enterprise) * A [SocialAPI.AI](https://social-api.ai) account with at least one connected social account ## Config file Create or edit `.vscode/mcp.json` in your workspace (or add to your user profile for global access): ```json theme={null} { "servers": { "SocialAPI": { "type": "http", "url": "https://api.social-api.ai/mcp" } } } ``` VS Code will show a **CodeLens** action above the server entry. Click **Auth** to start the OAuth flow and authorize in the browser. VS Code supports OAuth 2.1 with Dynamic Client Registration. It auto-discovers the authorization server via `/.well-known/oauth-protected-resource`. ## Command Palette 1. Open the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`) 2. Run **MCP: Add Server** 3. Select **HTTP** as the server type 4. Enter the URL: `https://api.social-api.ai/mcp` 5. Choose **Global** (user profile) or **Workspace** scope 6. Click the **Auth** CodeLens to complete the OAuth flow VS Code MCP server configuration [SocialAPI.AI](https://social-api.ai) tools are then available in **Copilot Chat** (Agent mode). ## Troubleshooting **"Auth" button not showing** - Make sure you're on VS Code 1.101+ and the GitHub Copilot extension is installed and active. The CodeLens appears above the server entry in `.vscode/mcp.json`. # Facebook publishing Source: https://docs.social-api.ai/posts/facebook Full reference for publishing Facebook Page posts via SocialAPI, including text, link preview, photo, video, Reel, and Story posts. ## 1. Overview Facebook publishing uses the Meta Graph API directly against your connected Facebook Page. SocialAPI selects the correct Graph API endpoint based on the content type: text and link posts go to `/{page-id}/feed`, single-photo posts to `/{page-id}/photos`, single-video posts to `/{page-id}/videos`, and multi-photo albums use a two-step upload-then-feed flow. Two richer formats are opted into explicitly with `platform_data.content_type`: Reels (`content_type: "reel"`) publish through the Reels API, and 24-hour Stories (`content_type: "stories"`) publish through the photo or video Stories API. Every format is described below. | Field | Value | | ---------------------- | --------------------------------------------------------------------------- | | Platform slug | `facebook` | | Auth type | OAuth 2.0 (Meta) | | API | Meta Graph API (Facebook Pages) | | Create post | Yes | | Update post | Yes (message text only) | | Delete post | Yes | | Schedule | Yes (deferred publish via `scheduled_at`) | | First comment | Yes (best-effort; not on Stories) | | Carousel (multi-photo) | Yes (2 to 10 photos; images only) | | Reel | Yes (`content_type: "reel"`; one vertical video) | | Story | Yes (`content_type: "stories"`; one photo or video, expires after 24 hours) | *** ## 2. Supported media | Content type | How to trigger | Notes | | ----------------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Text only | No `media_ids`; no `link` in `platform_data` | Publishes a plain text status update | | Link preview | No `media_ids`; include `link` in the target's `platform_data` | Facebook generates an Open Graph preview card | | Single photo | One image entry in `media_ids` | Routes to `/{page-id}/photos`; `platform_data` is ignored | | Single video | One video entry in `media_ids` (`.mp4`, `.mov`, `.avi`, `.mkv`, `.webm`) | Routes to `/{page-id}/videos`; only the `title` platform\_data field is forwarded | | Reel | One vertical video entry in `media_ids` plus `content_type: "reel"` in the target's `platform_data` | Routes to the Reels API (`/{page-id}/video_reels`). Unlike standard videos, `platform_data` IS honored (`content_type`, `share_to_feed`). | | Story | One photo or video entry in `media_ids` plus `content_type: "stories"` in the target's `platform_data` | Publishes a Page Story that expires after 24 hours. Photos publish via `/{page-id}/photo_stories`; videos run the three-phase `/{page-id}/video_stories` flow. Captions, links, and audience targeting are ignored. | | Multi-photo album | 2 to 10 image entries in `media_ids` | Each photo is uploaded with `published=false`, then a single `/{page-id}/feed` call attaches all `media_fbid` values via `attached_media[]`. The target's `platform_data` fields (`link`, CTA, age targeting) are forwarded to the feed call. | Route selection is mostly automatic, driven by how many `media_ids` you pass: * `media_ids` is empty → text or link post on `/{page-id}/feed` * `media_ids` has one entry → photo or video endpoint based on the file extension * `media_ids` has 2 to 10 entries → multi-photo album Reels and Stories override that extension-based routing. Set `content_type` in the target's `platform_data` to `"reel"` (one vertical video) or `"stories"` (one photo or video), and that intent takes precedence over the rules above. **Caption limit:** Follows Facebook's standard post message limit (approximately 63,206 characters, though shorter posts perform better). **Multi-photo album rules:** * 2 to 10 photos per post. Passing more than 10 entries returns a validation error. * Images only. Mixing images and videos in the same album is rejected with a validation error. Facebook's Graph API does not support mixed-media albums; publish separate posts or use a single Reel with image overlays. * Each photo is uploaded as an unpublished photo, which Facebook retains for approximately 24 hours. The follow-up feed call happens immediately, so this expiry only matters if the feed call fails. In that case the uploaded photos are abandoned and you can safely retry the post. * For best rendering, use consistent 1,080 by 1,080 px (1:1) images. *** ## 3. Create post Use `POST /v1/posts` with `targets` targeting a Facebook Page account. The `text` field becomes the post message. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Exciting news from our team. Read the full announcement on our website.", "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform_data": { "link": "https://example.com/announcement" } }] }' ``` ### Platform data fields Pass these inside the target's `platform_data` object (keys are read directly, not nested under a `facebook` key). These fields apply to text, link posts, and multi-photo albums. Single-photo posts honor `alt_text`; single-video posts honor `title`. Other `platform_data` keys are silently ignored for those post types. | Field | Type | Description | | ---------- | ------ | -------------------------------------------------------------------------------------------------------------- | | `link` | string | URL to attach as a link preview card. Facebook generates the Open Graph preview automatically. | | `cta_type` | string | Call-to-action button type (for example, `SHOP_NOW`, `LEARN_MORE`). Must be provided together with `cta_link`. | | `cta_link` | string | Destination URL for the call-to-action button. Must be provided together with `cta_type`. | | `min_age` | number | Minimum age for audience targeting (13 to 65). | | `max_age` | number | Maximum age for audience targeting (13 to 65). Must be greater than or equal to `min_age`. | Only the documented fields above are forwarded to the Graph API; any additional keys in `platform_data` are silently ignored. ### Photo post Provide a single image entry in `media_ids`. The `alt_text` platform\_data field is forwarded as `alt_text_custom` on the photo; all other `platform_data` keys are ignored for single-photo posts. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Our new product is here.", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"] }' ``` ### Multi-photo album Provide 2 to 10 image entries in `media_ids` to publish a multi-photo album. SocialAPI handles the two-step Graph API flow internally: each photo is uploaded with `published=false` to obtain a `media_fbid`, then a single `/{page-id}/feed` call attaches all photo IDs via `attached_media[]` and sets the caption. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Highlights from this week.", "media_ids": [ "f47ac10b-58cc-4372-a567-0e02b2c3d479", "8d5e9b2c-1a3f-4c6d-9e8b-2f7a4c1d6e0b", "3b1f7a90-6c2d-4e85-b9a1-0d4f8c3e2a17" ] }' ``` Unlike single-photo posts, the target's `platform_data` IS honored for albums. The `link`, `cta_type` plus `cta_link`, and age targeting fields are forwarded to the feed call: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Shop the spring collection.", "media_ids": [ "f47ac10b-58cc-4372-a567-0e02b2c3d479", "8d5e9b2c-1a3f-4c6d-9e8b-2f7a4c1d6e0b", "3b1f7a90-6c2d-4e85-b9a1-0d4f8c3e2a17", "5e2c8a14-7b9d-4f03-a6c1-9d8e0b3f4a26" ], "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform_data": { "cta_type": "SHOP_NOW", "cta_link": "https://example.com/spring" } }] }' ``` **Limits:** * Minimum 2, maximum 10 photos per album. * Images only. Including a video entry (any of `.mp4`, `.mov`, `.avi`, `.mkv`, `.webm`) in a 2+ item `media_ids` returns a validation error before any upload to Facebook. ### Video post Provide a single video entry in `media_ids`. SocialAPI detects the video extension and routes to `/{page-id}/videos`. The `title` platform\_data field is forwarded as the video title; all other `platform_data` keys are ignored for single-video posts. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Watch our latest brand film.", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"] }' ``` ### Reel Publish a short-form vertical video as a Page Reel by setting `content_type: "reel"` in the target's `platform_data`. Provide exactly one vertical video entry in `media_ids`. SocialAPI runs Facebook's Reels publishing flow (upload, processing, publish). The request blocks until the Reel is ready or published, polling for up to about 10 minutes (120 status checks at 5-second intervals). If the Reel does not finish processing within that window, the publish fails with `504 Gateway Timeout`. The `platform_data` object lives on the target (inside the `targets` array), and its keys are read directly (do not nest them under a `facebook` key). ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Behind the scenes #reels", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform_data": { "content_type": "reel", "share_to_feed": true } }] }' ``` Reel fields (inside the target's `platform_data`): | Field | Type | Default | Description | | --------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------ | | `content_type` | string | `"feed"` | Set to `"reel"` to publish a Reel. Any other value (or omitting it) publishes a standard post or feed video. | | `share_to_feed` | boolean | `true` | When `true`, the Reel also appears in the Page feed, not just the Reels tab. | Requirements: exactly one video, vertical orientation (9:16), and a duration of 3 to 90 seconds. SocialAPI warns when the duration is outside that range; Facebook enforces the aspect ratio at publish time and rejects videos that do not comply. ### Story Publish a 24-hour Page Story by setting `content_type: "stories"` in the target's `platform_data`. Provide exactly one photo or video entry in `media_ids`, and SocialAPI picks the right flow from the media type: a photo is uploaded as an unpublished photo and then published to `/{page-id}/photo_stories`, while a video runs Facebook's three-phase `/{page-id}/video_stories` flow (upload, processing, publish). For a video, the request blocks until processing finishes, polling for up to about 10 minutes (120 status checks at 5-second intervals); if it does not finish in that window, the publish fails with `504 Gateway Timeout`. Stories carry no caption, link, call-to-action, or audience targeting, so any `text` or `platform_data` field other than `content_type` is ignored. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform_data": { "content_type": "stories" } }] }' ``` Story fields (inside the target's `platform_data`): | Field | Type | Default | Description | | -------------- | ------ | -------- | -------------------------------------- | | `content_type` | string | `"feed"` | Set to `"stories"` to publish a Story. | Requirements and limits: * Exactly one media item. Passing zero or more than one entry returns a validation error before any upload to Facebook. * Photos: JPEG, BMP, PNG, GIF, or TIFF, up to 10 MB (keep PNGs under 1 MB). * Videos: 9:16 vertical, 1080 by 1920 recommended (minimum 540 by 960), 3 to 90 seconds, 24 to 60 fps. * The photo or video must not have been used in a previously published post. Facebook rejects a Story whose media already appeared elsewhere, so upload a fresh asset for each Story. * Stories are a Pages-only format and expire after 24 hours (see [Stories expire](#stories-expire)). ### First comment Set `first_comment` to a string to post an automated comment immediately after publish. This is best-effort: if the comment fails (for example due to a temporary rate limit), the post is still considered published. Page Stories are the one exception: a Story has no comments edge on the Graph API, so `first_comment` is skipped for `content_type: "stories"` targets. Every other post type (text, link, single photo, multi-photo album, single video, Reel) accepts one. ```json theme={null} { "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Exciting news from our team.", "first_comment": "Read the full announcement: https://example.com/blog" } ``` ### Scheduling Set `scheduled_at` to an ISO 8601 timestamp (UTC) to defer publishing: ```json theme={null} { "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Scheduled announcement.", "scheduled_at": "2026-04-15T10:00:00Z" } ``` *** ## 4. Update post Facebook supports editing the message text of a post that was created by the same app. Media cannot be changed after publish. Use `PATCH /v1/posts/:pid` with a `text` field: ```bash theme={null} curl -X PATCH https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Updated message text." }' ``` SocialAPI calls `POST /{page-id}_{post-id}` with the new `message` value. Only the `text` field is forwarded; all other patch fields are ignored for Facebook targets. **Limitation:** Only posts created by your app can be updated. Posts imported from the platform (via sync) may return a `400` from Facebook if the app does not own them. *** ## 5. Delete post ```bash theme={null} curl -X DELETE https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Or, to delete only the Facebook target of a cross-platform post: ```bash theme={null} curl -X DELETE "https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J?platform=facebook" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Deletion calls `DELETE /{page-id}_{post-id}` on the Graph API. If the post no longer exists on Facebook, the call returns success. *** ## 6. Retrieve posts Use `GET /v1/posts` to list posts. Filter by platform or account: ```bash theme={null} curl "https://api.social-api.ai/v1/posts?platform=facebook&account_ids=acc_01HZ9X3Q4R5M6N7P8V2K0W1J&limit=20" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Each post includes a `targets` array with per-platform status and engagement metrics: ```json theme={null} { "id": "p_01HZ9X3Q4R5M6N7P8V2K0W1J", "text": "Exciting news from our team.", "status": "published", "targets": [ { "platform": "facebook", "platform_post_id": "123456789012345_987654321098765", "status": "published", "permalink": "https://www.facebook.com/permalink/123456789012345", "metrics": { "likes": 15, "comments": 3, "shares": 7, "saves": 0, "extra": null, "metrics_synced_at": "2026-04-10T12:00:00Z" } } ] } ``` **Metrics notes:** * `likes` and `comments` are synced periodically from the Graph API. * `shares` is populated for Facebook posts. Facebook is the only platform where SocialAPI reports a non-zero share count. * `saves` is not exposed by the Facebook Pages API and is always `0`. * `metrics_synced_at` reflects when SocialAPI last refreshed the metrics from Facebook. *** ## 7. Quirks, errors, and recovery ### Page token lifetime Facebook Page tokens derived from long-lived user tokens do not expire. Once an account is connected, the token remains valid indefinitely unless the user revokes app access or changes their password. Reconnection is only needed if you see an `auth` error. ### Multi-page OAuth When a user completes the Facebook OAuth flow, SocialAPI connects all Facebook Pages that the user manages in a single OAuth exchange. Each page becomes a separate connected account entry. Users do not need to repeat the OAuth flow for additional pages. ### platform\_data forwarding rules The target's `platform_data` block is forwarded to the Graph API for text/link posts and for multi-photo albums (both go through the `/{page-id}/feed` endpoint). For single-photo and single-video posts, most `platform_data` keys are ignored: `alt_text` is honored for single-photo posts and `title` is honored for single-video posts. For Reels, `content_type` and `share_to_feed` apply; for Stories, only `content_type` applies (Stories take no caption, link, or targeting). All other keys are silently dropped for those post types. ### Multi-photo album semantics Multi-photo albums (2 to 10 image entries in `media_ids`) use a two-step flow: each photo is uploaded with `published=false`, then attached to a single feed post. The unpublished photos Facebook stores during step one expire after about 24 hours, so if the second step fails the photos are simply abandoned and you can retry the entire post. Mixing images and videos in the same album returns a validation error before any upload happens, and passing more than 10 entries also fails validation. ### First comment is non-blocking If `first_comment` is set and the comment fails after publish, the post remains `published`. The comment failure is logged but does not roll back or change the post status. Because the post status stays `published` either way, the server log is the only record that a first comment did not land. ### Stories expire Facebook Page Stories are visible for 24 hours and then disappear from the Page. The post record in SocialAPI is not removed when the story expires on Facebook, so a `GET /v1/posts` result may still reference a story that is no longer live. Stories are also single-use per asset: the photo or video must not have appeared in a previously published post, or Facebook rejects the publish. Upload a fresh media item for each Story. ### Reviews deprecated The Facebook Graph API v22.0 removed the reviews edge. `GET /v1/inbox/reviews` for a Facebook account returns `501 Not Implemented`. This is a platform-level deprecation and cannot be worked around. ### DM 24-hour window Facebook Messenger only allows pages to send outbound DMs within a 24-hour window after the user last messaged the page. Attempting to send outside this window returns a platform error. SocialAPI surfaces this as an `api_error`. ### Error shapes When a publish fails, the post target's `error` field contains a structured error: ```json theme={null} { "code": "platform.facebook.api_error", "message": "Invalid OAuth access token.", "category": "platform", "caused_by": "platform" } ``` | Error code | Category | Caused by | Meaning | | ------------------------------ | ------------ | ---------- | ---------------------------------------------------------------- | | `platform.facebook.api_error` | `platform` | `platform` | The Graph API rejected the request. Check `message` for details. | | `platform.facebook.rate_limit` | `rate_limit` | `platform` | Facebook rate limit hit. Retry after a delay. | | `platform.facebook.auth` | `auth` | `platform` | Access token invalid or revoked. Reconnect the account. | ### Recovery * **Rate limit:** Retry the post via `POST /v1/posts/:pid/retry` after waiting. SocialAPI applies exponential backoff for scheduled retries. * **Auth error:** Disconnect and reconnect the Facebook account through the OAuth flow to obtain a fresh token. * **API error:** Check the `message` field for the Graph API error description. Common causes include missing page permissions, a post that no longer exists, or an invalid media URL. *** ## 8. Full worked example The following example publishes a link preview post, then checks the metrics after publish. **Step 1: Publish the post** ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "We just launched our spring collection. Shop now and get 20% off your first order.", "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform_data": { "link": "https://example.com/spring-collection" } }] }' ``` Response: ```json theme={null} { "id": "p_01HZ9X3Q4R5M6N7P8V2K0W1J", "text": "We just launched our spring collection. Shop now and get 20% off your first order.", "status": "publishing", "targets": [ { "platform": "facebook", "status": "publishing" } ], "created_at": "2026-04-10T09:00:00Z" } ``` **Step 2: Check status** ```bash theme={null} curl "https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Response once published: ```json theme={null} { "id": "p_01HZ9X3Q4R5M6N7P8V2K0W1J", "text": "We just launched our spring collection. Shop now and get 20% off your first order.", "status": "published", "targets": [ { "platform": "facebook", "platform_post_id": "123456789012345_987654321098765", "status": "published", "permalink": "https://www.facebook.com/permalink/123456789012345", "metrics": { "likes": 15, "comments": 3, "shares": 7, "saves": 0, "extra": null, "metrics_synced_at": "2026-04-10T12:00:00Z" } } ], "created_at": "2026-04-10T09:00:00Z", "published_at": "2026-04-10T09:00:12Z" } ``` **Step 3: Refresh metrics on demand** ```bash theme={null} curl "https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J/metrics" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` *** ## 9. Required OAuth scopes SocialAPI's managed Meta App requests the following scopes on your behalf during the OAuth flow. This is informational, you do not need to configure anything. | Scope | Purpose | | ------------------------- | ------------------------------------------------- | | `pages_show_list` | List the pages the user manages | | `pages_read_engagement` | Read likes, comments, and shares on page posts | | `pages_read_user_content` | Read posts and comments made by users on the page | | `pages_manage_engagement` | Reply to and delete comments | | `pages_manage_metadata` | Access page metadata and settings | | `pages_messaging` | Send and receive Messenger DMs | | `pages_manage_posts` | Create, update, and delete page posts | | `public_profile` | Access the user's basic public profile | To verify which scopes your connected account has granted, call `GET /v1/accounts/:id/limits`. # Google Business Profile publishing Source: https://docs.social-api.ai/posts/google Full reference for publishing Google Business Profile local posts via SocialAPI, including text, image, event, offer, and alert posts. Available Google Business Profile publishing is live. You can create, update, and delete local posts (updates, offers, and events) on a connected Business Profile location. See [Google Business Profile](/connectors/google) for the connector overview. ## 1. Overview Google Business Profile (GBP) publishing uses the Google My Business API (v4) against your connected GBP location. SocialAPI creates local posts via the `/{location}/localPosts` endpoint. Each post publishes immediately and appears in your business knowledge panel on Google Search and Google Maps. | Field | Value | | ------------- | ---------------------------------------------------------------------- | | Platform slug | `google` | | Auth type | OAuth 2.0 (Google) | | API | Google My Business API v4 | | Create post | Yes (immediate publish via `createLocalPost`) | | Update post | Yes (PATCH with `updateMask`) | | Delete post | Yes | | Schedule | Yes (server-side; SocialAPI holds and publishes at the scheduled time) | | First comment | No | | Carousel | No | *** ## 2. Supported media | Content type | How to trigger | Notes | | ------------ | ------------------------------ | --------------------------------------------------------------- | | Text only | No `media_ids` | Publishes a plain text local post | | Single image | One image entry in `media_ids` | GBP supports one image per post; additional entries are ignored | **Video:** Not supported. The Google My Business API does not accept video uploads for local posts. **Caption limit:** GBP does not publish a documented character limit, but posts display best under 1,500 characters. Very long posts may be truncated in the knowledge panel display. **Carousels and multi-image posts:** Not supported. Only the first entry in `media_ids` is used. *** ## 3. Create post Use `POST /v1/posts` with `targets` targeting a connected GBP location. The `text` field maps to the `summary` field in the GBP API. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Join us this Saturday for our grand reopening. Free coffee for the first 100 visitors.", "platform_data": { "google": { "topicType": "STANDARD" } } }' ``` ### Platform data fields Pass these inside `platform_data.google`. | Field | Type | Default | Description | | ------------ | ------ | ---------- | -------------------------------------------------------------------------------------------------- | | `topicType` | string | `STANDARD` | Post type. One of `STANDARD`, `EVENT`, `OFFER`, or `ALERT`. Must match the post content structure. | | `updateMask` | string | `summary` | Used only on updates (PATCH). Comma-separated list of field paths to update. Ignored on create. | **topicType details:** * `STANDARD`: A general informational post. No extra fields required. * `EVENT`: Requires an `event` object with `title`, `schedule.startDate`, and `schedule.endDate`. Pass via `platform_data.google`. * `OFFER`: Requires an `offer` object with `couponCode`, `redeemOnlineUrl`, or `termsConditions`. Pass via `platform_data.google`. * `ALERT`: Reserved for urgent or time-sensitive announcements. Requires an `alertType` field passed via `platform_data.google`. Treated similarly to `STANDARD` but may render differently in the knowledge panel. ### Event post example ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Come celebrate our 10th anniversary with us.", "platform_data": { "google": { "topicType": "EVENT", "event": { "title": "10th Anniversary Celebration", "schedule": { "startDate": { "year": 2026, "month": 5, "day": 1 }, "endDate": { "year": 2026, "month": 5, "day": 1 } } } } } }' ``` ### Image post example ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Our spring menu is now available.", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "platform_data": { "google": { "topicType": "STANDARD" } } }' ``` SocialAPI forwards any additional keys in `platform_data.google` directly to the underlying platform API. These fields are not validated by SocialAPI and may break if the platform changes its API. See [Google Business Profile API reference](https://developers.google.com/my-business/reference/rest/v4/accounts.locations.localPosts) for the full list of supported parameters. *** ## 4. Update post GBP supports patching the `summary` and other fields of an existing local post. Use `PATCH /v1/posts/:pid` with a `text` field to update the post body. ```bash theme={null} curl -X PATCH https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Updated: Join us this Saturday and Sunday for our grand reopening.", "platform_data": { "google": { "updateMask": "summary" } } }' ``` ### updateMask The `updateMask` field controls which fields are sent to the GBP PATCH endpoint. It defaults to `"summary"` when omitted. To update additional fields (for example, an event schedule), pass them in `platform_data.google` alongside an explicit `updateMask` listing all changed paths as a comma-separated string. ```json theme={null} { "platform_data": { "google": { "updateMask": "summary,event.schedule.endDate", "event": { "schedule": { "endDate": { "year": 2026, "month": 5, "day": 2 } } } } } } ``` **Limitation:** `topicType` cannot be changed after a post is created. Attempting to include it in an update will result in a GBP API error. *** ## 5. Delete post ```bash theme={null} curl -X DELETE https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Or, to delete only the Google target of a cross-platform post: ```bash theme={null} curl -X DELETE "https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J?platform=google" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` SocialAPI calls `DELETE` on the full post resource name (for example, `accounts/123/locations/456/localPosts/789`). If the post no longer exists on GBP, the call returns `404` with code `resource.not_found`. **Retry not supported:** The GBP connector does not support `POST /v1/posts/:pid/retry`. If a post fails, delete it and create a new one. *** ## 6. Retrieve posts Use `GET /v1/posts` to list posts stored in SocialAPI. Filter by platform or account: ```bash theme={null} curl "https://api.social-api.ai/v1/posts?platform=google&account_ids=acc_01HZ9X3Q4R5M6N7P8V2K0W1J&limit=20" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Each post includes a `targets` array with per-platform status. Engagement metrics are not available for Google Business Profile posts: ```json theme={null} { "id": "p_01HZ9X3Q4R5M6N7P8V2K0W1J", "text": "Join us this Saturday for our grand reopening.", "status": "published", "targets": [ { "platform": "google", "platform_post_id": "accounts/123/locations/456/localPosts/789", "status": "published", "permalink": "https://www.google.com/maps/place/?cid=...", "metrics": { "likes": 0, "comments": 0, "shares": 0, "saves": 0, "extra": null, "metrics_synced_at": null } } ] } ``` **Metrics notes:** The Google My Business API does not expose per-post engagement metrics (likes, comments, shares, or saves) via the local posts endpoint. All metric fields are always `0` and `metrics_synced_at` is `null`. Use the Google Business Profile dashboard or Insights API for performance data. **Permalink note:** The `permalink` value comes from the GBP `searchUrl` field returned by the API. It is a Google Search URL linking directly to the business location. *** ## 7. Quirks, errors, and recovery ### No scheduling The GBP local posts API publishes immediately on create. There is no scheduled or draft publish capability. If you set `scheduled_at` on a post targeting a Google account, SocialAPI will create the post at the scheduled time but Google will publish it immediately at that point, not at a future time of your choosing within GBP's own UI. ### topicType must be set for non-standard posts Omitting `topicType` defaults to `STANDARD`, which is correct for general updates. For event, offer, or alert posts, you must explicitly set `topicType` and provide the required nested fields. Sending an `event` or `offer` object without the matching `topicType` results in a GBP API validation error. ### Post IDs are full resource names The `platform_post_id` returned for a Google post is the full GBP resource name, for example `accounts/123456789/locations/987654321/localPosts/abcdef`. This is the ID required by the update and delete endpoints. SocialAPI stores and forwards this value automatically. ### Location ID format Each connected GBP location has an account ID in the format `accounts/{accountId}/locations/{locationId}`. When a user connects their Google account, SocialAPI discovers all locations they manage and creates one connected account entry per location. Users do not need to reconnect for additional locations owned by the same Google account as long as they were present at the time of the initial OAuth exchange. ### Token auto-refresh Google OAuth 2.0 access tokens expire after one hour. SocialAPI automatically refreshes the token using the stored refresh token before each API call when the token is within five minutes of expiry. The refreshed token is written back to the database. No action is required from your application. ### Rate limits The GBP API enforces a 300 QPM (queries per minute) quota on SocialAPI's managed Google Cloud project, shared across all locations. SocialAPI applies an internal throttle at 240 QPM (80% of the limit) to leave headroom for bursts. When the throttle is active, requests return `429 Too Many Requests`. Retry after a short delay. ### Error shapes When a publish fails, the post target's `error` field contains a structured error: ```json theme={null} { "code": "platform.google.api_error", "message": "Request contains an invalid argument.", "category": "platform", "caused_by": "platform" } ``` | Error code | Category | Caused by | Meaning | | --------------------------- | ---------- | ---------- | -------------------------------------------------------------------------------------------------------------------------- | | `platform.google.api_error` | `platform` | `platform` | The GBP API rejected the request. Check `message` for details, typically a missing required field or invalid value. | | `platform.google.auth` | `auth` | `platform` | Access token invalid, expired, or revoked. SocialAPI attempts auto-refresh; if this error persists, reconnect the account. | ### Recovery * **Auth error:** If auto-refresh fails, disconnect and reconnect the Google account through the OAuth flow to obtain a new refresh token. * **API error:** Check the `message` field. Common causes include a missing `topicType` for event or offer posts, an invalid event date format, or attempting to update a field not included in `updateMask`. * **Rate limit:** Wait before retrying. The Google connector does not support `POST /v1/posts/:pid/retry`; delete and recreate the post instead. *** ## 8. Full worked example The following example creates an offer post, checks its status, then updates the summary text. **Step 1: Create the offer post** ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Get 20% off all services this week only. Use code SPRING20 at checkout.", "platform_data": { "google": { "topicType": "OFFER", "offer": { "couponCode": "SPRING20", "redeemOnlineUrl": "https://example.com/book" } } } }' ``` Response: ```json theme={null} { "id": "p_01HZ9X3Q4R5M6N7P8V2K0W1J", "text": "Get 20% off all services this week only. Use code SPRING20 at checkout.", "status": "publishing", "targets": [ { "platform": "google", "status": "publishing" } ], "created_at": "2026-04-10T09:00:00Z" } ``` **Step 2: Check status** ```bash theme={null} curl "https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Response once published: ```json theme={null} { "id": "p_01HZ9X3Q4R5M6N7P8V2K0W1J", "text": "Get 20% off all services this week only. Use code SPRING20 at checkout.", "status": "published", "targets": [ { "platform": "google", "platform_post_id": "accounts/123456789/locations/987654321/localPosts/abcdef", "status": "published", "permalink": "https://www.google.com/maps/place/?cid=123456789", "metrics": { "likes": 0, "comments": 0, "shares": 0, "saves": 0, "extra": null, "metrics_synced_at": null } } ], "created_at": "2026-04-10T09:00:00Z", "published_at": "2026-04-10T09:00:08Z" } ``` **Step 3: Update the post summary** ```bash theme={null} curl -X PATCH https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Get 20% off all services this week only. Use code SPRING20 at checkout. Offer ends Sunday.", "platform_data": { "google": { "updateMask": "summary" } } }' ``` *** ## 9. Required OAuth scopes SocialAPI's managed Google Cloud OAuth app requests the following scope on your behalf during the authorization flow. This is informational — you do not need to configure anything. | Scope | Purpose | | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `https://www.googleapis.com/auth/business.manage` | Read and write access to all Google Business Profile locations, posts, reviews, and Q\&A for the authenticated user | This single scope covers the GBP API operations we use: listing locations, creating and managing local posts, and reading and replying to reviews. # Instagram publishing Source: https://docs.social-api.ai/posts/instagram Full reference for publishing Instagram posts via SocialAPI, including images, videos, reels, carousels, and platform-specific parameters. ## 1. Overview Instagram publishing uses the Meta Graph API two-step flow: SocialAPI creates a media container, polls until the container is ready, then publishes it. All post types (image, video, reel, carousel, story) go through this same pipeline. | Field | Value | | ------------- | ----------------------------------------- | | Platform slug | `instagram` | | Auth type | OAuth 2.0 (Meta) | | API | Meta Graph API | | Create post | Yes | | Update post | No (not supported by platform) | | Delete post | Yes | | Schedule | Yes (deferred publish via `scheduled_at`) | | First comment | Yes (best-effort, non-blocking) | *** ## 2. Supported media | Content type | How to trigger | Notes | | ------------------- | -------------------------------------------------------------- | ----------------------------------------------- | | Single image | `content_type: "feed"` | Standard image post | | Single video (Reel) | URL ending in `.mp4`, `.mov`, etc., or `content_type: "reel"` | Published as `media_type: REELS` | | Carousel | Two or more `media_ids` entries, or `content_type: "carousel"` | 2 to 10 items; mix of images and videos allowed | | Story | `content_type: "stories"` | Single image or video; captions not supported | `content_type` is required when publishing or scheduling an Instagram post. Without it, validation returns an error. When saving a draft (without publishing), `content_type` can be omitted and SocialAPI auto-detects at publish time: multiple `media_ids` produce a carousel, a single entry produces a feed image or reel depending on file extension. For Instagram feed images, use JPEG. PNG uploads can be rejected by Meta at the container-creation step. This is a Meta-side constraint, so SocialAPI does not block a PNG in advance; if a feed image fails to publish, re-encode it as JPEG. Story publishing is supported via the API using `content_type: "stories"`. Note that stories expire after 24 hours and the Instagram platform does not support captions on stories. **Caption limit:** 2200 characters. **Carousel limits:** 2 to 10 items. Carousel child containers do not accept captions; only the parent container carries the caption. Attach media by **uploading it first and passing the returned `media_id`** in the `media_ids` array. There is no `image_url`, `video_url`, or `media_urls` field: passing a raw public URL is silently ignored, and the post publishes with no media (Instagram then returns `The parameter image_url is required`). Upload in one step with `POST /v1/media/upload` (multipart), or use the presigned-URL flow (`GET /v1/media/upload-url` → `PUT` the file → `POST /v1/media/:id/verify`). The `verify` step is required: without it the media stays `pending` and publishing fails with `Media not found`. *** ## 3. Create post Use `POST /v1/posts` with `targets` targeting an Instagram account. The `text` field becomes the caption. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Check out our new collection. #fashion #style", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "platform_data": { "instagram": { "alt_text": "A flat-lay photo of our spring collection", "location_id": "110506962309835" } } }' ``` ### Platform data fields Pass these inside `platform_data.instagram`: | Field | Type | Description | | -------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `content_type` | string | One of `"feed"`, `"reel"`, `"carousel"`, `"stories"`. Overrides auto-detection. | | `alt_text` | string | Accessibility alt text applied to the image container. | | `location_id` | string | Facebook location page ID to geo-tag the post. | | `share_to_feed` | bool | For Reels, also share to the main profile feed. | | `cover_url` | string | Custom cover image URL for video or Reel posts. | | `collaborators` | string or array | Instagram usernames to tag as collaborators. Leading `@` is stripped automatically. Accepts a single string, a comma-separated string, or a JSON array. | | `video_cover_timestamp_ms` | number | Millisecond offset into the video to use as the cover frame. Ignored if `cover_url` is set. | SocialAPI forwards any additional keys in `platform_data.instagram` directly to the underlying platform API. These fields are not validated by SocialAPI and may break if the platform changes its API. See [Instagram Publishing API reference](https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/content-publishing) for the full list of supported parameters. ### Scheduling Set `scheduled_at` to an ISO 8601 timestamp (UTC) to defer publishing: ```json theme={null} { "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Scheduled caption", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "scheduled_at": "2026-04-15T10:00:00Z" } ``` ### First comment Set `first_comment` to a string to post an automated comment immediately after publish. This is best-effort: if the comment fails (for example due to a temporary rate limit), the post is still considered published. ```json theme={null} { "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "New drop. Link in bio.", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "first_comment": "Shop now: https://example.com/shop" } ``` *** ## 4. Update post Instagram does not support editing a published post's caption or media via the API. `PATCH /v1/posts/:pid` for an Instagram target returns `501 Not Implemented`. If you need to correct a caption, delete the post and recreate it. *** ## 5. Delete post ```bash theme={null} curl -X DELETE https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Or, to delete only the Instagram target of a cross-platform post: ```bash theme={null} curl -X DELETE "https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J?platform=instagram" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Deletion calls `DELETE /{ig-media-id}` on the Meta Graph API. If the media ID no longer exists on Instagram, the API returns success. *** ## 6. Retrieve posts Use `GET /v1/posts` to list posts. Filter by platform or account: ```bash theme={null} curl "https://api.social-api.ai/v1/posts?platform=instagram&account_ids=acc_01HZ9X3Q4R5M6N7P8V2K0W1J&limit=20" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Each post includes a `targets` array with per-platform status and engagement metrics: ```json theme={null} { "id": "p_01HZ9X3Q4R5M6N7P8V2K0W1J", "text": "Check out our new collection.", "status": "published", "targets": [ { "platform": "instagram", "platform_post_id": "17895695668004550", "status": "published", "permalink": "https://www.instagram.com/p/ABC123/", "metrics": { "likes": 42, "comments": 5, "shares": 0, "saves": 0, "extra": null, "metrics_synced_at": "2026-04-10T12:00:00Z" } } ] } ``` **Metrics notes:** * `likes`, `comments`, and `shares` are synced periodically from the Meta Graph API. * `saves` is not available from the Instagram API and is always `0`. * `shares` is `0` because SocialAPI does not yet sync the `shares_count` field from the Graph API. * `metrics_synced_at` reflects when SocialAPI last refreshed the metrics from Instagram. *** ## 7. Quirks, errors, and recovery ### Container polling After creating a media container, SocialAPI polls the container status before publishing. Instagram processes images almost instantly, but videos and reels can take up to 5 minutes (60 attempts at 5-second intervals). If the container does not reach `FINISHED` within that window, the publish fails with `504 Gateway Timeout` and the post target is marked `failed`. Because presigned S3 URLs do not reliably expose file extensions in the path, SocialAPI polls all containers unconditionally, not only video ones. ### Carousel captions Only the parent carousel container accepts a caption. Child item containers are created without captions. Passing a caption on a carousel child is silently ignored by the Meta Graph API; SocialAPI does not forward captions to child containers. ### First comment is non-blocking If `first_comment` is set and the comment fails after publish, the post remains `published`. The comment failure is logged but does not roll back or change the post status. ### Stories expire Instagram Stories expire 24 hours after posting. The post record in SocialAPI is not automatically removed when the story expires on Instagram. ### Error shapes When a publish fails, the post target's `error` field contains a structured error: ```json theme={null} { "code": "platform.instagram.api_error", "message": "The media could not be processed", "category": "platform", "caused_by": "platform" } ``` | Error code | Category | Caused by | Meaning | | -------------------------------------- | ------------ | ---------- | ------------------------------------------------------------------------------------------------------------------- | | `platform.instagram.api_error` | `platform` | `platform` | Meta Graph API rejected the request. Check `message` for details. | | `platform.instagram.rate_limit` | `rate_limit` | `platform` | Instagram rate limit hit. Retry after a delay. | | `platform.instagram.auth` | `auth` | `platform` | Access token expired or revoked. Reconnect the account. | | `platform.instagram.container_failed` | `platform` | `platform` | Instagram accepted the media container, then failed to process it. See "Container failures report no reason" below. | | `platform.instagram.container_expired` | `platform` | `platform` | The media container was not published within its 24-hour validity window. Create the post again. | | `platform.instagram.container_timeout` | `platform` | `platform` | The container was still `IN_PROGRESS` after roughly 5 minutes of polling. | Codes of the form `platform.instagram.` (for example `platform.instagram.36003`) carry a Meta error code, and the `message` is Meta's own text ("The aspect ratio is not supported."). These are the most actionable failures. ### Container failures report no reason Instagram publishing is a two-step flow: SocialAPI creates a media container, then polls it until Instagram finishes processing. When Instagram rejects the media at that second step, its container-status endpoint returns only a status enum (`ERROR`), with no error code and no message. Meta exposes no field carrying the reason, so SocialAPI cannot forward one. `platform.instagram.container_failed` therefore reports the fact of the failure plus the container ID, not a cause. In practice it almost always means the file does not meet Instagram's specs: for Reels, MOV or MP4 with H.264 or HEVC video, up to 300 MB and 15 minutes, 9:16 recommended. Re-encoding the video and retrying usually resolves it. ### Recovery * **Rate limit:** Retry the post via `POST /v1/posts/:pid/retry` after waiting. SocialAPI applies exponential backoff for scheduled retries. * **Auth error:** Disconnect and reconnect the Instagram account through the OAuth flow to obtain a fresh token. * **Container timeout:** Usually caused by a large video file or a slow upstream URL. Re-upload the media and retry. * **Container failed:** Instagram gives no reason, so a transient processing fault and a rejected file look identical. Retry once via `POST /v1/posts/:pid/retry`; if it fails again, re-encode the media to the specs above rather than retrying further. *** ## 8. Full worked example The following example publishes a Reel with a cover image, a collaborator tag, and a first comment. **Step 1: Upload media** ```bash theme={null} # Get a presigned upload URL curl "https://api.social-api.ai/v1/media/upload-url?filename=reel.mp4&media_type=video/mp4" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Response: ```json theme={null} { "upload_url": "https://r2.example.com/media/reel.mp4?X-Amz-Signature=...", "media_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "public_url": "https://cdn.example.com/media/reel.mp4" } ``` Upload the file, then verify: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/media/f47ac10b-58cc-4372-a567-0e02b2c3d479/verify \ -H "Authorization: Bearer $SOCAPI_KEY" ``` **Step 2: Publish the post** ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Behind the scenes from our studio shoot. #bts #fashion", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "first_comment": "Full video on YouTube: https://youtube.com/watch?v=example", "platform_data": { "instagram": { "content_type": "reel", "share_to_feed": true, "cover_url": "https://cdn.example.com/media/reel-cover.jpg", "collaborators": "@partner_brand", "alt_text": "Studio shoot behind the scenes" } } }' ``` **Step 3: Check status** ```bash theme={null} curl "https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Response once published: ```json theme={null} { "id": "p_01HZ9X3Q4R5M6N7P8V2K0W1J", "text": "Behind the scenes from our studio shoot. #bts #fashion", "status": "published", "targets": [ { "platform": "instagram", "platform_post_id": "17895695668004550", "status": "published", "permalink": "https://www.instagram.com/reel/ABC123/", "metrics": { "likes": 42, "comments": 5, "shares": 0, "saves": 0, "extra": null, "metrics_synced_at": "2026-04-10T12:00:00Z" } } ], "created_at": "2026-04-10T09:00:00Z", "published_at": "2026-04-10T09:00:47Z" } ``` *** ## 9. Required OAuth scopes SocialAPI's managed Meta App requests the following scopes on your behalf during the OAuth flow. This is informational — you do not need to configure anything. | Scope | Purpose | | ------------------------------------ | ----------------------------------------------------- | | `instagram_business_basic` | Read basic account info and media | | `instagram_business_manage_comments` | Post and manage comments (required for first comment) | | `instagram_business_manage_messages` | Send and receive DMs | | `instagram_business_content_publish` | Create and publish media containers | To verify which scopes your connected account has granted, call `GET /v1/accounts/:id/limits`. # LinkedIn Posts Source: https://docs.social-api.ai/posts/linkedin Publish and manage posts on LinkedIn as the authenticated member. ## Overview Publish posts to LinkedIn as the authenticated individual member (your personal feed). The connector supports text posts, single image, single video, and multi-image (carousel) posts. Publishing as the individual member is live. Organization (company) Page posting is available in beta via the `linkedin_page` platform identifier. Contact us to enable it for your account. ## Supported content | Content type | Supported | Notes | | ---------------------- | --------- | ------------------------------------------------------------------------------------ | | Text only | Yes | `text` is the post commentary | | Single image | Yes | one entry in `media_ids` | | Single video | Yes | one video entry in `media_ids` | | Multi-image (carousel) | Yes | multiple image entries in `media_ids` | | Mixed image + video | No | LinkedIn does not support mixed carousels | | Article / link | Yes | set `article_url` in `platform_data`; the first attached image becomes the thumbnail | ## Post fields LinkedIn uses the standard `POST /v1/posts` body. The target account is specified inside `targets`, not at the top level. | Field | Type | Required | Description | | ------------ | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------- | | `text` | string | Yes | Post commentary | | `targets` | array | Yes | One entry per account; each requires `account_id`. The platform is inferred from the account, no `platform` field is sent | | `media_ids` | array | No | IDs of media uploaded via the [media upload flow](/posts/instagram#1-upload-media); attach images or a video | | `visibility` | string | No | `public` (default), `connections_only`, or `logged_in` (logged-in LinkedIn members). Set on the target | ## Visibility LinkedIn honors the `visibility` field on the target: | Value | LinkedIn audience | | ------------------ | ---------------------------------------------------- | | `public` (default) | Anyone on or off LinkedIn | | `connections_only` | The member's connections only (member accounts only) | | `logged_in` | Any logged-in LinkedIn member | `connections_only` is a member-only value. Organization page posts (`linkedin_page` platform) do not support `CONNECTIONS` visibility; SocialAPI rejects it with a validation error. Use `public` or `logged_in` for org page posts. ## Platform options Pass these keys inside `platform_data` on a target for LinkedIn-specific options: | Key | Type | Description | | --------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `article_url` | string | Publishes a link post. The post links to this URL; the first attached image (if any) is uploaded as the thumbnail | | `article_title` | string | Title shown on the link card | | `article_description` | string | Description shown on the link card | | `cta_label` | string | Call-to-action button on a link post. One of `LEARN_MORE`, `SIGN_UP`, `DOWNLOAD`, `REGISTER`, `SUBSCRIBE`, `APPLY`, `REQUEST_DEMO`, `JOIN`, `ATTEND`. Requires `article_url` | | `cta_url` | string | Destination for the call-to-action button. Requires `article_url` | | `alt_text` | string | Accessibility alt text for a single-image post | | `alt_texts` | array | Per-image alt text for a carousel, matched by position to `media_ids` | | `media_title` | string | Caption or title for an attached image or video | | `disable_reshare` | boolean | When true, others cannot reshare the post | ## Creating a post ```bash cURL theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCIALAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Big news from our team!", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"] }' ``` The post publishes to the member's feed. The author is the authenticated person. To attach media, upload it first (see the [Instagram media upload steps](/posts/instagram#1-upload-media)) and pass the returned `media_id` values in `media_ids`. ### Link post with a call to action Set `article_url` in `platform_data` to publish a link post. Add `cta_label` and `cta_url` for a call-to-action button. If you attach an image, it becomes the link thumbnail. ```bash cURL theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCIALAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Read our latest product update.", "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "visibility": "public", "platform_data": { "article_url": "https://example.com/update", "article_title": "Product update", "article_description": "What shipped this month", "cta_label": "LEARN_MORE", "cta_url": "https://example.com/update" } }] }' ``` ## Updating a post Only the post text (commentary) can be changed after publishing. Attached media cannot be modified. ```bash cURL theme={null} curl -X PATCH https://api.social-api.ai/v1/posts/{post_id} \ -H "Authorization: Bearer $SOCIALAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"text": "Updated announcement text"}' ``` ## Deleting a post ```bash cURL theme={null} curl -X DELETE https://api.social-api.ai/v1/posts/{post_id} \ -H "Authorization: Bearer $SOCIALAPI_KEY" ``` ## Reading posts Reading a member's own posts directly from LinkedIn requires elevated permissions and is not available. Listing posts returns the posts SocialAPI has stored for the account (for example, posts you published through SocialAPI), not a live read from LinkedIn. ## Quirks and limitations * Posts are published as the individual member, not an organization page. Organization page posting is available in beta via the `linkedin_page` platform (contact us to enable). * Only the commentary can be updated after publishing. * Mixed image and video carousels are not supported. * LinkedIn returns the post URN to SocialAPI in an upstream response header. SocialAPI captures it for you: it is returned as `platform_post_id` in the post body (and in `post.published` webhooks) along with a `permalink`. You never need to read a header on SocialAPI responses. * Reading a member's own posts live from LinkedIn is not available; stored posts are returned instead. ## Next steps Connection and authentication details. How publishing works across all platforms. # Posts overview Source: https://docs.social-api.ai/posts/overview Compare post publishing capabilities across all supported platforms. The Posts API is a content publishing API: one endpoint to publish and schedule photos, videos, carousels, stories, and reels across Instagram, Facebook, TikTok, LinkedIn, YouTube, and Threads, with per-platform delivery status. Publishing uses a single endpoint, `POST /v1/posts`, with per-platform behavior controlled by the `platform_data` field. This page compares capabilities across all platforms at a glance. For full details on a specific platform, see the deep-dive pages linked below. Use `POST /v1/posts/validate` to check constraints before publishing, and `GET /v1/posts/validate` to fetch platform-specific constraints (e.g. TikTok's per-creator privacy levels and duration limits). ## Attaching media Photos and videos are upload-first. Upload a file to receive a `media_id`, then pass that ID in the `media_ids` array. A raw public URL in `media_ids` is ignored. See the [Media uploads guide](/guides/media) for the two upload paths, the verify step, and storage limits. ## Capability matrix | Capability | Instagram | Facebook | LinkedIn | Threads | TikTok | YouTube | X / Twitter | Google | | ------------------ | ----------------- | ----------------- | --------------- | ---------- | ---------------------- | ----------------- | ------------- | ----------- | | Create post | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Coming soon | | Update post | No | Yes (text only) | Yes (text only) | No | No | Yes | No | Coming soon | | Delete post | Yes | Yes | Yes | Yes | No | Yes | Yes | Coming soon | | Schedule | Yes | Yes | Yes | Yes | No | Native | No | Coming soon | | First comment | Yes (best-effort) | Yes (best-effort) | No | No | No | Yes (best-effort) | No | No | | Image | Yes | Yes | Yes | Yes | Yes (1-35) | Thumbnail | No | Coming soon | | Video | Yes (as Reel) | Yes | Yes | Yes | Yes | Yes | No | No | | Carousel | Yes (2-10) | Yes (2-10) | Yes (2-20) | Yes (2-20) | Photo carousel only | No | No | No | | Max media per post | 10 | 10 | 20 | 20 | 35 (photo) / 1 (video) | 1 (video + thumb) | 0 (text only) | 1 | ## Deleting posts `DELETE /v1/posts/{pid}` is best-effort and never lets the dashboard drift from the platform. For a draft, scheduled, or failed post (nothing was ever published), the post is removed locally. For a published post, SocialAPI calls each platform's delete API in turn: * If the platform deletes the copy, that target is cleared. * If the platform does not support deletion (for example TikTok) or the delete call fails, that copy stays live and the post is kept (shrunk to the targets that are still live), not removed. A post is fully removed only when no live copy remains. The response is a per-platform result, not a bare acknowledgement: ```json theme={null} { "success": true, "deleted": false, "results": [ { "platform": "instagram", "account_id": "acc_...", "outcome": "deleted" }, { "platform": "tiktok", "account_id": "acc_...", "outcome": "not_supported", "message": "Operation not supported for this platform" } ] } ``` `deleted` is `true` only when the post was fully removed. Each entry in `results` has an `outcome` of `deleted`, `not_supported`, or `failed`. A `failed` target can be retried; a `not_supported` target must be removed on the platform itself. See `DELETE /v1/posts/{pid}` in the API reference for the full schema. ## Core field handling How each platform interprets the shared `PostDraft` fields: | Field | Instagram | Facebook | LinkedIn | Threads | TikTok | YouTube | X / Twitter | Google | | --------------- | ------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------ | ------------------------- | --------------------------------------- | ------------------------------------------------------------- | ------------------------- | ---------------- | | `text` | Caption (max 2200 chars) | Post message | Commentary | Post text (max 500 chars) | Video description | Video description | Tweet text (max 280) | Summary | | `media_ids` | Presigned URLs; determines image/video/carousel | First URL only; photo or video | Presigned URLs; image or video | Presigned URLs | Presigned URLs; video or photo carousel | Single video URL | Not supported (text only) | Single image URL | | `first_comment` | Posted as a comment after publish (best-effort, non-blocking) | Posted as a comment after publish (best-effort, non-blocking) | Not supported | Not supported | Not supported | Posted as a comment after publish (best-effort, non-blocking) | Not supported | Not supported | | `scheduled_at` | Deferred publish | Deferred publish | Deferred publish | Deferred publish | Not supported | Not supported | Not supported | Not supported | ## Platform-specific `platform_data` summary Each platform accepts optional fields inside `platform_data.`. For the full reference, see the per-platform page. Pass these inside each target's `platform_data` object (per-target overrides supported). | Platform | Required fields | Notable optional fields | | ---------------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | [Instagram](/posts/instagram) | None | `alt_text`, `location_id`, `share_to_feed`, `cover_url` | | [Facebook](/posts/facebook) | None | `link`, `cta_type`, `cta_link`, `min_age`, `max_age` (text, link, album posts); `alt_text` (single-photo); `title` (single-video) | | [LinkedIn](/posts/linkedin) | None | `disable_reshare`, `cta_label`, `cta_url`, `article_url`, `article_title`, `article_description`, `media_title`, `alt_text`, `alt_texts` | | [Threads](/posts/threads) | None | `reply_control`, `link_attachment` | | [TikTok](/posts/tiktok) | `privacy_level` | `media_type`, `brand_content_toggle`, `disable_duet`, `disable_stitch`, `disable_comment`, `is_aigc`, `video_cover_timestamp_ms` | | [YouTube](/posts/youtube) | None | `category_id`, `tags`, `made_for_kids`, `embeddable`, `notify_subscribers`, `publish_at` | | [X / Twitter](/connectors/twitter) | None | `reply_settings` (`following`, `mentionedUsers`, `subscribers`, `verified`) | | [Google](/posts/google) | None (defaults to STANDARD) | `topicType` (STANDARD, EVENT, OFFER, ALERT), `updateMask` | ## Required OAuth scopes | Platform | Scopes | | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Instagram | `instagram_business_basic`, `instagram_business_manage_comments`, `instagram_business_manage_messages`, `instagram_business_content_publish` | | Facebook | `pages_show_list`, `pages_read_engagement`, `pages_read_user_content`, `pages_manage_engagement`, `pages_manage_metadata`, `pages_messaging`, `pages_manage_posts`, `public_profile`, `business_management` | | LinkedIn | `r_liteprofile`, `r_organization_social`, `w_organization_social`, `r_organization_social_feed`, `w_organization_social_feed`, `rw_organization_admin` | | Threads | `threads_basic`, `threads_content_publish`, `threads_read_replies`, `threads_delete` | | TikTok | `user.info.basic`, `user.info.profile`, `video.publish`, `video.upload` | | YouTube | `https://www.googleapis.com/auth/youtube.force-ssl` | | X / Twitter | `tweet.read`, `tweet.write`, `tweet.moderate.write`, `users.read`, `dm.read`, `dm.write`, `like.write`, `offline.access` (BYOK; you configure your own app) | | Google | `https://www.googleapis.com/auth/business.manage` (Coming soon) | ## Per-platform deep dives Images, videos, reels, carousels. Meta Graph API. Text, photo, and video posts. Facebook Pages API. Organization page posts. LinkedIn Marketing API. Text, image, video posts. Threads API. Video and photo carousel posts. TikTok Content Posting API. Video uploads. YouTube Data API v3. Tweets with text or images. X API v2. Business Profile local posts. Coming soon. # Scheduling posts Source: https://docs.social-api.ai/posts/scheduling Schedule posts across platforms via the unified social media API. One call, multiple targets, one scheduled time. Schedule a post by passing the `scheduled_at` field to `POST /v1/posts`. SocialAPI holds the post and publishes it at the scheduled time. Multi-platform fan-out works the same way as immediate publishing: one post object, multiple targets, one scheduled time applied to all of them. ## How scheduling works Set `scheduled_at` to an ISO 8601 timestamp in the future. SocialAPI validates the request immediately and queues it for delivery at the specified time. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Launching next week.", "targets": [ { "account_id": "acc_instagram_01" }, { "account_id": "acc_facebook_01" } ], "scheduled_at": "2026-06-01T09:00:00Z" }' ``` Response: ```json theme={null} { "id": "p_01HZ9X3Q4R5M6N7P8V2K0W1J", "status": "scheduled", "scheduled_at": "2026-06-01T09:00:00Z", "targets": [ { "account_id": "acc_instagram_01", "platform": "instagram", "status": "scheduled" }, { "account_id": "acc_facebook_01", "platform": "facebook", "status": "scheduled" } ] } ``` To publish immediately instead of scheduling, pass `"publish_now": true`. Passing neither `scheduled_at` nor `publish_now` saves the post as a **draft** (even when targets are provided), it is not published. ## Managing scheduled posts ### List scheduled posts Filter by status to see all upcoming posts: ```bash theme={null} curl "https://api.social-api.ai/v1/posts?status=scheduled" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ### Edit or reschedule a post Use `PATCH /v1/posts/:pid` to update the text, media, or schedule time of a post in `draft`, `scheduled`, or `failed` status. Editing a failed post lets you correct content issues before calling `POST /v1/posts/:pid/retry`. Update the scheduled time with: ```bash theme={null} curl -X PATCH https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "scheduled_at": "2026-06-02T14:00:00Z" }' ``` ### Publish a draft or scheduled post now To publish a post that is currently in `draft` or `scheduled` status, call the publish endpoint. This sends it to all targets immediately and synchronously (the response reflects the final delivery result): ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J/publish \ -H "Authorization: Bearer $SOCAPI_KEY" ``` This is the only way to publish an existing draft. `PATCH` only edits a post's content, it does not trigger publishing. Consumes 1 post credit. ### Cancel a scheduled post Delete it before it publishes: ```bash theme={null} curl -X DELETE https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Only posts in `scheduled`, `draft`, or `failed` status can be deleted. Once a post enters `publishing`, it cannot be stopped. ## Status lifecycle Every post moves through this state machine: ``` draft ------> scheduled --> publishing --> published \ / \-> partial \-----------------/ \-> failed (publish_now / POST /posts/{pid}/publish) ``` A `draft` reaches `publishing` either by being created with `publish_now: true`, or later via `POST /v1/posts/{pid}/publish`. | Status | Meaning | | ------------ | ------------------------------------------------------------- | | `draft` | Created but not yet scheduled or published | | `scheduled` | Queued for future delivery at `scheduled_at` | | `publishing` | SocialAPI is actively sending the post to platforms | | `published` | All targets delivered successfully | | `partial` | Some targets succeeded, some failed (check per-target status) | | `failed` | All targets failed | Deleting a post permanently removes it (the post returns `404` on subsequent fetch). For scheduled posts, any pending platform deliveries are marked `cancelled` before the post row is removed. ## Platform scheduling support When you set `scheduled_at`, SocialAPI holds the post in its own queue and publishes it when the scheduled time arrives. This works the same way for every platform: the post is sent to the platform only at the scheduled moment, and the platform's own scheduler is never used. Every platform below accepts `scheduled_at`. ### YouTube: `scheduled_at` vs `publish_at` YouTube has its own native scheduling, which is a **separate feature** from `scheduled_at`. The two behave differently: * **`scheduled_at`** (this page): SocialAPI holds the post and uploads it to YouTube at the scheduled time. YouTube is not told about the schedule. * **`platform_data.youtube.publish_at`**: SocialAPI uploads the video to YouTube right away, and YouTube itself makes it public at the given time. Use `scheduled_at` to have SocialAPI do the waiting; use `publish_at` to hand scheduling to YouTube. See [YouTube posts](/posts/youtube) for details. To discover scheduling support programmatically instead of hardcoding this table, call [`GET /v1/posts/validate`](/api-reference/posts/get-platform-constraints). Each platform includes a `scheduling` object with a `supported` boolean. It is `true` for every publishing platform today, but querying it means your integration never relies on a hardcoded list that can silently go stale. ```json theme={null} { "data": { "tiktok": { "scheduling": { "supported": true }, "text": { "max_length": 2200, "counting_mode": "utf16" } } } } ``` ## Related * [Posts overview](/posts/overview) for the full publishing capability matrix * [Platform support](/guides/platforms) for per-platform feature coverage * [API Reference](/api-reference/introduction) for the complete `POST /v1/posts` schema # Threads publishing Source: https://docs.social-api.ai/posts/threads Full reference for publishing Threads posts via SocialAPI, including text, images, videos, and carousels, plus platform-specific parameters. ## 1. Overview Threads publishing uses three distinct flows depending on content type. Text-only posts use a single atomic API call (no container polling). Image and video posts use a two-step flow: container creation then publish, with polling added for video containers until they reach `FINISHED`. Carousel posts use a three-step flow: one container per item, then a parent carousel container, then publish. | Field | Value | | ------------- | ----------------------------------------- | | Platform slug | `threads` | | Auth type | OAuth 2.0 (Meta Threads) | | API | Threads Graph API (`graph.threads.net`) | | Create post | Yes | | Update post | No (not supported by platform) | | Delete post | Yes | | Schedule | Yes (deferred publish via `scheduled_at`) | | First comment | No | *** ## 2. Supported media | Content type | How to trigger | Notes | | ------------ | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | Text only | Omit `media_ids` | Supported natively; unlike Instagram, text-only posts work without any media | | Single image | One `media_ids` entry pointing to an image URL | Sent as `media_type: IMAGE` | | Single video | One `media_ids` entry pointing to a video URL (`.mp4`, `.mov`, `.avi`, `.mkv`, `.webm`) | Sent as `media_type: VIDEO`; container is polled until `FINISHED` | | Carousel | Two or more `media_ids` entries | Up to 20 items; mix of images and videos allowed | SocialAPI auto-detects the media type from the URL file extension. Video detection inspects the URL path, ignoring query parameters (safe for presigned S3 URLs). **Text limit:** 500 characters. **Carousel limit:** Maximum 20 items per post. *** ## 3. Create post Use `POST /v1/posts` with `targets` targeting a Threads account. The `text` field becomes the post body. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Shipping updates for April. Full details on our site.", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "platform_data": { "threads": { "reply_control": "accounts_you_follow" } } }' ``` ### Platform data fields Pass these inside `platform_data.threads`: | Field | Type | Description | | ----------------- | ------ | --------------------------------------------------------------------------------------------- | | `reply_control` | string | Who can reply. One of `"everyone"` (default), `"accounts_you_follow"`, or `"mentioned_only"`. | | `link_attachment` | string | A URL to attach as a link preview. Must be a valid URL string. | SocialAPI forwards any additional keys in `platform_data.threads` directly to the underlying platform API. These fields are not validated by SocialAPI and may break if the platform changes its API. See [Threads API reference](https://developers.facebook.com/docs/threads/posts) for the full list of supported parameters. ### Scheduling Set `scheduled_at` to an ISO 8601 timestamp (UTC) to defer publishing: ```json theme={null} { "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Big announcement coming tomorrow.", "scheduled_at": "2026-04-15T10:00:00Z" } ``` *** ## 4. Update post Threads does not support editing a published post via the API. `PATCH /v1/posts/:pid` for a Threads target returns `501 Not Implemented`. If you need to correct a post, delete it and recreate it. *** ## 5. Delete post ```bash theme={null} curl -X DELETE https://api.social-api.ai/v1/posts/post_01HZ9X3Q4R5M6N7P8V2K0W1J \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Or, to delete only the Threads target of a cross-platform post: ```bash theme={null} curl -X DELETE "https://api.social-api.ai/v1/posts/post_01HZ9X3Q4R5M6N7P8V2K0W1J?platform=threads" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Deletion calls `DELETE /{media-id}` on the Threads Graph API. If the media ID no longer exists on Threads, the API returns success. *** ## 6. Retrieve posts Use `GET /v1/posts` to list posts. Filter by platform or account: ```bash theme={null} curl "https://api.social-api.ai/v1/posts?platform=threads&account_ids=acc_01HZ9X3Q4R5M6N7P8V2K0W1J&limit=20" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Each post includes a `targets` array with per-platform status and engagement metrics: ```json theme={null} { "id": "post_01HZ9X3Q4R5M6N7P8V2K0W1J", "text": "Shipping updates for April.", "status": "published", "targets": [ { "platform": "threads", "platform_post_id": "1234567890123456", "status": "published", "permalink": "https://www.threads.net/@yourhandle/post/ABC123", "metrics": { "likes": 0, "comments": 0, "shares": 0, "saves": 0, "extra": null, "metrics_synced_at": null } } ] } ``` **Metrics notes:** SocialAPI fetches likes and replies via the Threads `/insights` endpoint (requires the `threads_manage_insights` scope). However, that scope is not requested by default, so like and reply counts remain `0` for most connected accounts. Shares and saves are not available from the Threads API at all. To enable metric collection, contact us about the `threads_manage_insights` beta scope. *** ## 7. Quirks, errors, and recovery ### Container polling for video After creating a video container, SocialAPI polls the container status (once per minute, up to 5 attempts) before publishing. If the container reaches `FINISHED` or `PUBLISHED`, the post is immediately sent to `threads_publish`. If the container returns `ERROR` or `EXPIRED`, the publish fails. If the container does not finish within 5 minutes, SocialAPI returns `504 Gateway Timeout` and marks the post target as `failed`. Image posts proceed to publish directly after container creation, without polling. Text posts use a single atomic call and skip both container creation and polling entirely. ### No post editing Threads has no API for editing a post's text or media after publication. Attempting `PATCH /v1/posts/:pid` targeting Threads always returns `501 Not Implemented`. Delete and recreate the post if a correction is needed. ### Text-only posts Unlike Instagram, Threads supports text-only posts natively. Omit `media_ids` entirely to create a post with just a text body. The container is created with `media_type: TEXT`. ### reply\_control defaults to "everyone" If `reply_control` is not set in `platform_data.threads`, the Threads API defaults to `"everyone"`. You must explicitly pass `"accounts_you_follow"` or `"mentioned_only"` to restrict who can reply. ### Token expiry Threads long-lived user access tokens expire after approximately 60 days. SocialAPI stores the token and its expiry. When a token expires, publish calls will fail with an `auth` error. Reconnect the account via the OAuth flow to obtain a fresh token. ### Error shapes When a publish fails, the post target's `error` field contains a structured error: ```json theme={null} { "code": "platform.threads.api_error", "message": "The media could not be processed", "category": "platform", "caused_by": "platform" } ``` | Error code | Category | Caused by | Meaning | | ---------------------------- | ---------- | ---------- | -------------------------------------------------------------------- | | `platform.threads.api_error` | `platform` | `platform` | Threads Graph API rejected the request. Check `message` for details. | | `platform.threads.auth` | `auth` | `platform` | Access token expired or revoked. Reconnect the account. | ### Recovery * **Auth error:** Disconnect and reconnect the Threads account through the OAuth flow to obtain a fresh token. * **Container timeout:** Usually caused by a large video file or a slow upstream URL. Re-upload the media and retry. * **API error:** Retry the post via `POST /v1/posts/:pid/retry` after reviewing the `message` field for platform guidance. *** ## 8. Full worked example The following example publishes a video post with restricted reply control, then checks its status. **Step 1: Upload media** ```bash theme={null} # Get a presigned upload URL curl "https://api.social-api.ai/v1/media/upload-url?filename=clip.mp4&media_type=video/mp4" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Response: ```json theme={null} { "upload_url": "https://r2.example.com/media/clip.mp4?X-Amz-Signature=...", "media_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "public_url": "https://cdn.example.com/media/clip.mp4" } ``` Upload the file directly to the presigned URL, then verify: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/media/f47ac10b-58cc-4372-a567-0e02b2c3d479/verify \ -H "Authorization: Bearer $SOCAPI_KEY" ``` **Step 2: Publish the post** ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Our April product update is live. Check the link in bio for full details.", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "platform_data": { "threads": { "reply_control": "accounts_you_follow", "link_attachment": "https://example.com/updates/april" } } }' ``` Response (container polling starts in background): ```json theme={null} { "id": "post_01HZ9X3Q4R5M6N7P8V2K0W1J", "status": "publishing", "targets": [ { "platform": "threads", "status": "publishing" } ] } ``` **Step 3: Check status** ```bash theme={null} curl "https://api.social-api.ai/v1/posts/post_01HZ9X3Q4R5M6N7P8V2K0W1J" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Response once published: ```json theme={null} { "id": "post_01HZ9X3Q4R5M6N7P8V2K0W1J", "text": "Our April product update is live. Check the link in bio for full details.", "status": "published", "targets": [ { "platform": "threads", "platform_post_id": "1234567890123456", "status": "published", "permalink": "https://www.threads.net/@yourhandle/post/ABC123", "metrics": { "likes": 0, "comments": 0, "shares": 0, "saves": 0, "extra": null, "metrics_synced_at": null } } ], "created_at": "2026-04-10T09:00:00Z", "published_at": "2026-04-10T09:02:05Z" } ``` *** ## 9. Required OAuth scopes SocialAPI's managed Meta Threads App requests the following scopes on your behalf during the OAuth flow. This is informational — you do not need to configure anything. | Scope | Purpose | | ------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `threads_basic` | Read basic account info and profile | | `threads_content_publish` | Create and publish media containers | | `threads_read_replies` | Read replies on posts | | `threads_delete` | Delete published posts | | `threads_manage_insights` | Fetch engagement metrics (likes, replies) via the insights endpoint. Not requested by default; contact us to enable. | To verify which scopes your connected account has granted, call `GET /v1/accounts/:id/limits`. # TikTok publishing Source: https://docs.social-api.ai/posts/tiktok Full reference for publishing TikTok videos and photo carousels via SocialAPI, including platform-specific parameters, creator validation, and polling behavior. ## 1. Overview TikTok publishing uses the TikTok Content Posting API v2 (PULL\_FROM\_URL flow). SocialAPI queries creator info to validate settings, submits a publish job, then polls the status endpoint until the post reaches `PUBLISH_COMPLETE` or `FAILED`. Both video and photo carousel formats are supported. | Field | Value | | ------------- | ---------------------------------------------------------------------- | | Platform slug | `tiktok` | | Auth type | OAuth 2.0 (TikTok) | | API | TikTok Content Posting API v2 | | Create post | Yes | | Update post | No (not supported by platform) | | Delete post | No (not supported by platform) | | Schedule | Yes (server-side; SocialAPI holds and publishes at the scheduled time) | | First comment | No | *** ## 2. Supported media | Content type | How to trigger | Notes | | -------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | | Single video | `platform_data.tiktok.media_type: "video"` or omit (default) | One `media_ids` entry required. Accepted formats: MP4, MOV, WebM. | | Photo carousel | `platform_data.tiktok.media_type: "photo"` (or omit for 2+ images) | 2 to 35 images required. Accepted formats: JPEG and WebP only. `disable_duet` and `disable_stitch` are ignored. | Single images are not supported. Mixed media (video plus images) is not supported. The `media_ids` field takes IDs of media you upload first via the [media upload flow](/posts/instagram#1-upload-media). SocialAPI resolves each ID to a presigned HTTPS URL that TikTok pulls directly, so the underlying files must remain accessible while the post is being published. **Accepted formats.** TikTok is strict about formats, and a mismatch is rejected up front rather than failing silently mid-publish: * **Video posts** accept MP4, MOV, or WebM. * **Photo posts** accept **JPEG and WebP only**. PNG, AVIF, GIF, and other image formats are rejected by TikTok; convert them to JPEG or WebP before uploading. **Media type is resolved for you when omitted.** If you do not set `media_type`, SocialAPI infers it: 2 or more images with no video is treated as a photo carousel, and anything else is treated as a video. Attaching an image to a video post (for example, a single image with no `media_type`) is rejected with a clear `400` error, not accepted and then silently dropped. See [Media type must match the file](#media-type-must-match-the-file). **Video duration:** The maximum video duration is per-creator and returned by the TikTok creator info endpoint. SocialAPI validates duration during `POST /v1/posts/validate` when the media item includes a `duration_seconds` field. **Photo carousel count:** Exactly 2 to 35 images. Fewer than 2 or more than 35 will produce a validation error. *** ## 3. Create post Use `POST /v1/posts` with `targets` targeting a TikTok account. The `text` field becomes the post title or description. `privacy_level` in `platform_data.tiktok` is required: TikTok's Direct Post guidelines prohibit a client-applied default, so SocialAPI enforces an explicit selection. Before publishing, SocialAPI calls the TikTok creator info endpoint to validate the privacy level, check posting availability, and apply creator-level disable flags for comment, duet, and stitch settings. ### Video post ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "My latest video #fyp", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "platform_data": { "tiktok": { "privacy_level": "PUBLIC_TO_EVERYONE", "disable_comment": false, "disable_duet": false, "disable_stitch": false, "video_cover_timestamp_ms": 3000 } } }' ``` ### Photo carousel post ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Swipe through the highlights", "media_ids": [ "f47ac10b-58cc-4372-a567-0e02b2c3d479", "8d5e9b2c-1a3f-4c6d-9e8b-2f7a4c1d6e0b", "3b1f7a90-6c2d-4e85-b9a1-0d4f8c3e2a17" ], "platform_data": { "tiktok": { "media_type": "photo", "privacy_level": "FRIENDS_ONLY", "disable_comment": true } } }' ``` ### platform\_data.tiktok fields | Field | Type | Required | Description | | -------------------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `privacy_level` | string | **Yes** | Audience setting. Must be one of the values returned by the creator info endpoint for this account. Common values: `PUBLIC_TO_EVERYONE`, `MUTUAL_FOLLOW_FRIENDS`, `FOLLOWER_OF_CREATOR`, `SELF_ONLY`. Required; omitting produces a validation error (field `privacy_level`). | | `media_type` | string | No | `"video"` (default) or `"photo"`. | | `brand_content_toggle` | bool | No | Marks the post as branded or sponsored content. Cannot be combined with `SELF_ONLY` privacy. | | `brand_organic_toggle` | bool | No | Organic branded content disclosure flag. | | `_disclosure_on` | bool | No | SocialAPI-only disclosure gate. When `true`, at least one of `brand_content_toggle` or `brand_organic_toggle` must also be `true`; otherwise validation returns a `400` error on the `brand_content` field. | | `disable_comment` | bool | No | Disables comments on the post. Defaults to `true` (disabled) when omitted. Creator-level disable forces this to `true` and cannot be overridden. | | `disable_duet` | bool | No | Disables duet. Only applies to video posts; ignored for photo carousels. Defaults to `true` (disabled) when omitted. Creator-level disable forces this to `true`. | | `disable_stitch` | bool | No | Disables stitch. Only applies to video posts; ignored for photo carousels. Defaults to `true` (disabled) when omitted. Creator-level disable forces this to `true`. | | `is_aigc` | bool | No | Discloses the post as AI-generated content. | | `video_cover_timestamp_ms` | number | No | Timestamp in milliseconds for the video cover frame. Only applies to video posts. | | `photo_cover_index` | number | No | 0-based index of the photo to use as the cover. Only applies to photo posts. Defaults to `0`. | | `auto_add_music` | bool | No | Automatically adds TikTok-recommended music to the photo post. Only applies to photo posts. | ### Creator info and valid privacy levels Privacy level options differ per creator. Always call `POST /v1/posts/validate` before publishing to confirm the target privacy level is allowed for the specific account. SocialAPI fetches creator info from TikTok and caches it for 10 minutes. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts/validate \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "My post caption", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "platform_data": { "tiktok": { "privacy_level": "PUBLIC_TO_EVERYONE" } } }' ``` ### Publish flow 1. SocialAPI calls `POST /v2/post/publish/creator_info/query/` to get creator settings and allowed privacy levels. 2. SocialAPI validates the draft (privacy level required, duet/stitch for video, brand content rules). 3. For video: SocialAPI calls `POST /v2/post/publish/video/init/` (PULL\_FROM\_URL, single media URL). 4. For photo: SocialAPI calls `POST /v2/post/publish/content/init/` (PULL\_FROM\_URL, array of image URLs). 5. SocialAPI polls `POST /v2/post/publish/status/fetch/` every 3 seconds until `PUBLISH_COMPLETE` or `FAILED`, or until a 60-second timeout. 6. On success, the published item ID is stored and returned. *** ## 4. Update post TikTok does not support editing posts after publication. `PATCH /v1/posts/:pid` returns `501 Not Supported` for TikTok post platforms. *** ## 5. Delete post TikTok does not expose a post deletion endpoint in its Content Posting API. `DELETE /v1/posts/:pid` returns `501 Not Supported` for TikTok post platforms. *** ## 6. Retrieve posts `GET /v1/posts` returns TikTok posts stored in the SocialAPI database. SocialAPI fetches videos from TikTok via `POST /v2/video/list/` (up to 20 per page, paginated by cursor) during background sync. This sync requires the `video.list` scope, which is subject to TikTok app review and may not be active for all connected accounts. **What this means in practice:** * Posts created through SocialAPI are stored immediately after a successful publish and are returned by `GET /v1/posts`. * Background sync pulls TikTok-native posts (created outside SocialAPI) once the `video.list` scope is approved for the app. * Engagement metrics (likes, comments, shares, views) are populated from the video list response during sync. **Analytics exports** (`POST /v1/accounts/:id/export`) do retrieve full TikTok video data including `like_count`, `comment_count`, `share_count`, `view_count`, `duration`, `width`, and `height`. *** ## 7. Quirks, errors, and recovery ### privacy\_level is required `privacy_level` is required. Omitting it produces an error issue (field `privacy_level`) from `POST /v1/posts/validate` and blocks publishing. TikTok's Direct Post guidelines prohibit any client-applied default, so SocialAPI enforces an explicit selection. The allowed values are not static: they are returned by the TikTok creator info endpoint and vary per account. Validate before publishing to confirm the chosen value is allowed for the target account. ### Photo carousels require 2 to 35 images Sending fewer than 2 or more than 35 `media_ids` when `media_type` is `"photo"` returns a `400` validation error immediately. Single-image photo posts are not supported by TikTok. ### Media type must match the file TikTok publishes videos and photos through different endpoints, and it will not build a video out of an image. If an image reaches the video path (for example, a single image with no `media_type`, which resolves to a video), TikTok accepts the job but never downloads the file, so it stalls until the 60-second timeout. SocialAPI now blocks this before publishing: both `POST /v1/posts/validate` and `POST /v1/posts` return a `400` error on the `media` field (code `validation.media_type_mismatch`) telling you to publish the images as a photo post instead. To post images, set `media_type` to `"photo"` and attach 2 or more JPEG or WebP images. ### Photo posts accept only JPEG and WebP A photo carousel that includes a PNG, AVIF, GIF, or any non-JPEG/WebP image is rejected with a `400` error on the `media` field (code `validation.unsupported_photo_format`) that names the offending format. A video included in a photo post is rejected the same way. Convert images to JPEG or WebP before uploading. ### disable\_duet and disable\_stitch are silently ignored for photo posts Setting `disable_duet: true` or `disable_stitch: true` on a photo carousel post has no effect. TikTok does not expose these controls for photo posts. `POST /v1/posts/validate` will return a warning (not an error) if these fields are set on a photo post. ### brand\_content\_toggle is incompatible with SELF\_ONLY privacy Setting `brand_content_toggle: true` alongside `privacy_level: "SELF_ONLY"` is rejected by TikTok. SocialAPI validates this combination before submitting and returns a `400` error. ### video\_cover\_timestamp\_ms applies only to videos This field is sent only when `media_type` is `"video"`. It is not included in photo carousel requests. ### Creator daily posting limit When a creator has reached their daily TikTok posting limit, the creator info endpoint returns an empty `privacy_level_options` list. SocialAPI detects this and returns a `400` validation error with field `posting_limit`. ### Token auto-refresh TikTok access tokens expire. SocialAPI automatically refreshes them using the stored refresh token before each request. If the refresh token has also expired, the connector returns `401 invalid_token` and the account must be reconnected. ### Publish timeout SocialAPI polls the publish status for up to 60 seconds. If TikTok has not completed processing within that window, the post status is set to `failed` with error code `publish_timeout`. Retry via `POST /v1/posts/:pid/retry`. ### Error categories | Error code prefix | Category | Caused by | Description | | --------------------------- | ------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `platform.tiktok.api_error` | `platform` | `platform` | Generic TikTok API error (includes publish failures and timeouts) | | `platform.tiktok.auth` | `auth` | `platform` | Invalid or expired token, missing scope | | `validation.*` | `validation` | `user` | Missing or invalid `platform_data.tiktok` fields (privacy level, media count, media type mismatch, unsupported photo format, etc.) | *** ## 8. Full worked example This example publishes a video, validates the request first, then creates the post. ### Step 1: validate ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts/validate \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Behind the scenes of our product shoot #bts #brand", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "platform_data": { "tiktok": { "privacy_level": "PUBLIC_TO_EVERYONE", "brand_content_toggle": true, "brand_organic_toggle": false, "disable_comment": false, "disable_duet": true, "disable_stitch": true, "video_cover_timestamp_ms": 2000 } } }' ``` Expected response (no issues): ```json theme={null} { "valid": true, "issues": [] } ``` ### Step 2: create post ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "text": "Behind the scenes of our product shoot #bts #brand", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "platform_data": { "tiktok": { "privacy_level": "PUBLIC_TO_EVERYONE", "brand_content_toggle": true, "brand_organic_toggle": false, "disable_comment": false, "disable_duet": true, "disable_stitch": true, "video_cover_timestamp_ms": 2000 } } }' ``` Expected response: ```json theme={null} { "id": "post_01HZ9X3Q4R5M6N7P8V2K0W1J", "status": "published", "platforms": [ { "platform": "tiktok", "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "status": "published", "platform_post_id": "7380000000000000001" } ], "created_at": "2026-04-10T12:00:00Z" } ``` ### Handling a failed publish If TikTok rejects the video during processing, the platform status is `failed` with an error detail. Inspect the error and retry: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts/post_01HZ9X3Q4R5M6N7P8V2K0W1J/retry \ -H "Authorization: Bearer $SOCAPI_KEY" ``` *** ## 9. Required OAuth scopes | Scope | Purpose | | ------------------- | --------------------------------------------------------------- | | `user.info.basic` | Read account profile (open ID, username, display name, avatar) | | `user.info.profile` | Read additional profile fields (e.g. bio, follower counts) | | `video.publish` | Publish videos and photo carousels via PULL\_FROM\_URL | | `video.upload` | Upload video files directly (used for file-based publish flows) | These scopes are requested automatically during the OAuth connect flow (`POST /v1/accounts/connect`). No additional configuration is required. # YouTube publishing Source: https://docs.social-api.ai/posts/youtube Full reference for publishing YouTube videos via SocialAPI, including metadata, native scheduling, updates, and platform-specific parameters. ## 1. Overview YouTube publishing uploads a single video through the YouTube Data API v3 resumable upload protocol. SocialAPI streams the media from its store directly to YouTube, sets the video metadata (title, description, privacy, tags, category), and returns the published video. Unlike the Meta and TikTok connectors, YouTube supports editing a video's metadata after publish and scheduling a future publish natively. | Field | Value | | --------------- | --------------------------------------------------------------- | | Platform slug | `youtube` | | Auth type | OAuth 2.0 (Google) | | API | YouTube Data API v3 | | Create post | Yes | | Update post | Yes (title, description, tags, privacy, category, status flags) | | Delete post | Yes | | Schedule | Native (via `publish_at` + `private` visibility) | | First comment | Yes (best-effort, non-blocking) | | Add to playlist | Yes (best-effort, non-blocking) | *** ## 2. Supported media | Content type | How to trigger | Notes | | ------------ | --------------------- | ------------------------------------------- | | Single video | One `media_ids` entry | The only supported format. MP4 recommended. | YouTube publishing takes exactly one video. Image posts and carousels are not supported: images surface only as thumbnails when you retrieve posts. Accepted media is `video/*`, uploaded via the resumable protocol, up to 256 GB. **Title is required** (max 100 characters). If you publish or schedule without a title, validation returns an error. **Description limit:** 5000 bytes (maps from the `text` field). Attach the video by **uploading it first and passing the returned `media_id`** in the `media_ids` array. There is no `video_url` or `media_url` field. Upload in one step with `POST /v1/media/upload` (multipart), or use the presigned-URL flow (`GET /v1/media/upload-url` → `PUT` the file → `POST /v1/media/:id/verify`). The `verify` step is required: without it the media stays `pending` and publishing fails. *** ## 3. Create post Use `POST /v1/posts` with `targets` targeting a YouTube account. * `text` becomes the video description. * The title comes from the top-level `title` field, or from `platform_data.youtube.title` as a fallback. The top-level value wins when both are present. * `visibility` maps to the video's privacy status. It defaults to `private` when omitted. `public` and `unlisted` both work normally. ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "title": "Our spring collection walkthrough", "text": "A full tour of the new lineup. Chapters in the description below.", "visibility": "public", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "platform_data": { "youtube": { "category_id": "22", "tags": ["fashion", "spring", "lookbook"], "made_for_kids": false, "notify_subscribers": true } } }' ``` ### Where to put platform data You can pass the YouTube fields below in either of two places: * **Post level**, keyed by platform: `platform_data.youtube`. Applies to every YouTube target in the post. * **Target level**, flat (no `youtube` key): inside a target's own `platform_data` object. Applies only to that target. When both set the same field, the target-level value wins, so you can set channel-wide defaults at the post level and override per account: ```json theme={null} { "targets": [ { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform_data": { "notify_subscribers": false } } ], "platform_data": { "youtube": { "category_id": "22", "notify_subscribers": true } } } ``` Here the target publishes with `category_id: "22"` (inherited from the post level) and `notify_subscribers: false` (the target override wins). ### Platform data fields Pass these inside `platform_data.youtube` (post level) or directly in a target's `platform_data` (target level): | Field | Type | Description | | -------------------------- | --------------- | --------------------------------------------------------------------------------------------- | | `title` | string | Video title fallback, used when the top-level `title` is empty. | | `category_id` | string | YouTube video category ID (for example `22` for People & Blogs). | | `tags` | string or array | Video tags. Accepts a JSON array, a comma-separated string, or a single string. | | `default_language` | string | BCP-47 language code for the title and description. | | `made_for_kids` | bool | COPPA self-declaration. Defaults to `false`. | | `embeddable` | bool | Whether the video can be embedded on other sites. | | `license` | string | `youtube` (standard) or `creativeCommon`. | | `public_stats_viewable` | bool | Whether the video's view and like counts are publicly visible. | | `publish_at` | string | ISO 8601 timestamp for a native scheduled publish. Requires `private` visibility. | | `recording_date` | string | ISO 8601 date the video was recorded. | | `contains_synthetic_media` | bool | Declares that the video contains AI-generated or altered content. | | `notify_subscribers` | bool | Whether to notify channel subscribers on publish. Defaults to YouTube's own default (`true`). | | `playlist_id` | string | Playlist to add the video to after publish (best-effort). | ### Native scheduling YouTube schedules a publish natively: you upload the video as `private` with a `publish_at` timestamp, and YouTube flips it to public automatically at that time. This is different from the platform-agnostic `scheduled_at` deferred publish (which YouTube does not use). ```json theme={null} { "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "title": "Launch day announcement", "text": "Going live for everyone at 10am.", "visibility": "private", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "platform_data": { "youtube": { "publish_at": "2026-04-15T10:00:00Z" } } } ``` `publish_at` with any visibility other than `private` returns a validation error on the `publish_at` field. Leaving `visibility` unset is allowed because the connector defaults it to `private`. ### First comment Set `first_comment` to a string to post a top-level comment on the video immediately after publish. This is best-effort: if the comment fails, the post is still considered published. ### Add to playlist Set `platform_data.youtube.playlist_id` to add the newly published video to one of your playlists. This runs after publish and is best-effort: a failure here does not change the post status. *** ## 4. Update post YouTube supports editing a video's metadata after publish. `PATCH /v1/posts/:pid` updates the title, description, tags, category, privacy, and status flags. ```bash theme={null} curl -X PATCH https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Our spring collection walkthrough (updated)", "text": "Updated description with corrected chapter timestamps.", "visibility": "unlisted", "platform_data": { "youtube": { "tags": ["fashion", "spring", "lookbook", "2026"] } } }' ``` Partial edits are safe: fields you do not provide are preserved. SocialAPI fetches the current video and backfills any unspecified snippet field, so an omitted field is never wiped. **Notes:** * Updating the snippet always requires a title and a category. SocialAPI backfills both from the current video when you omit them. * `publish_at` can only be set before the first publish. It cannot be changed once the video is public. *** ## 5. Delete post ```bash theme={null} curl -X DELETE https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Or, to delete only the YouTube target of a cross-platform post: ```bash theme={null} curl -X DELETE "https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J?platform=youtube" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Deletion removes the video from YouTube. If the video no longer exists, the API reports success. *** ## 6. Retrieve posts Use `GET /v1/posts` to list posts. Filter by platform or account: ```bash theme={null} curl "https://api.social-api.ai/v1/posts?platform=youtube&account_ids=acc_01HZ9X3Q4R5M6N7P8V2K0W1J&limit=20" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Listing is a two-step process internally (the channel's uploads playlist is read, then each video is enriched with statistics), transparent to the caller. Each post includes a `targets` array with per-platform status and engagement metrics: ```json theme={null} { "id": "p_01HZ9X3Q4R5M6N7P8V2K0W1J", "text": "A full tour of the new lineup.", "status": "published", "targets": [ { "platform": "youtube", "platform_post_id": "dQw4w9WgXcQ", "status": "published", "permalink": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "metrics": { "likes": 128, "comments": 17, "views": 5432, "metrics_synced_at": "2026-04-10T12:00:00Z" } } ] } ``` **Metrics notes:** * `likes`, `comments`, and `views` are synced periodically from the YouTube Data API. * `shares` and `saves` are not exposed by the YouTube API and are not reported. * `metrics_synced_at` reflects when SocialAPI last refreshed the metrics from YouTube. *** ## 7. Quirks, errors, and recovery ### Title is required, with length limits A title is required to publish. Titles are capped at 100 characters and descriptions at 5000 bytes. Exceeding these limits returns an error from YouTube. ### publish\_at requires private visibility Native scheduling only works on private videos. Setting `publish_at` alongside a `public` or `unlisted` visibility returns a validation error on the `publish_at` field before the request reaches YouTube. ### First comment and playlist add are non-blocking If `first_comment` or `playlist_id` is set and the follow-up call fails after a successful upload, the post remains `published`. The failure is logged but does not roll back or change the post status. ### Error shapes When a publish fails, the post target's `error` field contains a structured error: ```json theme={null} { "code": "platform.youtube.api_error", "message": "The request could not be completed", "category": "platform", "caused_by": "platform" } ``` | Error code | Category | HTTP | Meaning | | ------------------------------------------ | ------------ | --------------------- | --------------------------------------------------------------------------- | | `platform.youtube.api_error` | `platform` | 502 (or upstream 4xx) | Generic YouTube Data API error. Check `message` for details. | | `platform.youtube.rate_limit` | `rate_limit` | 429 | YouTube rejected the request for exceeding its limits. Retry after a delay. | | `platform.youtube.auth` | `auth` | 401 | Access token invalid or expired. Reconnect the account. | | `platform.youtube.insufficientPermissions` | `platform` | 403 | The granted OAuth scope does not permit this action. | | `resource.not_found` | `resource` | 404 | The video ID does not exist or is not owned by the account. | ### Recovery * **Rate limit:** Retry the post via `POST /v1/posts/:pid/retry` after waiting. SocialAPI applies exponential backoff for scheduled retries. * **Auth error:** Disconnect and reconnect the YouTube account through the OAuth flow to obtain a fresh token. *** ## 8. Full worked example The following example uploads a video, then publishes it with a title, description, tags, and a scheduled publish time. **Step 1: Upload media** ```bash theme={null} # Get a presigned upload URL curl "https://api.social-api.ai/v1/media/upload-url?filename=walkthrough.mp4&media_type=video/mp4" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Response: ```json theme={null} { "upload_url": "https://r2.example.com/media/walkthrough.mp4?X-Amz-Signature=...", "media_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "public_url": "https://cdn.example.com/media/walkthrough.mp4" } ``` Upload the file, then verify: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/media/f47ac10b-58cc-4372-a567-0e02b2c3d479/verify \ -H "Authorization: Bearer $SOCAPI_KEY" ``` **Step 2: Publish the video (scheduled)** ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "targets": [{ "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" }], "title": "Spring collection walkthrough", "text": "A full tour of the new lineup. Chapters below.", "visibility": "private", "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "first_comment": "Which piece is your favorite? Let us know below.", "platform_data": { "youtube": { "category_id": "22", "tags": ["fashion", "spring", "lookbook"], "publish_at": "2026-04-15T10:00:00Z", "notify_subscribers": true, "playlist_id": "PL0123456789abcdef" } } }' ``` **Step 3: Check status** ```bash theme={null} curl "https://api.social-api.ai/v1/posts/p_01HZ9X3Q4R5M6N7P8V2K0W1J" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Response once uploaded: ```json theme={null} { "id": "p_01HZ9X3Q4R5M6N7P8V2K0W1J", "title": "Spring collection walkthrough", "text": "A full tour of the new lineup. Chapters below.", "status": "published", "targets": [ { "platform": "youtube", "platform_post_id": "dQw4w9WgXcQ", "status": "published", "permalink": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" } ], "created_at": "2026-04-10T09:00:00Z", "published_at": "2026-04-10T09:03:22Z" } ``` The video is uploaded as private and goes public automatically at the `publish_at` time. *** ## 9. Required OAuth scopes SocialAPI's managed Google App requests the following scope on your behalf during the OAuth flow. This is informational: you do not register a Google project or configure anything. | Scope | Purpose | | --------------------------------------------------- | ------------------------------------------------------------------------------- | | `https://www.googleapis.com/auth/youtube.force-ssl` | Upload, update, and delete videos; read channel and video data; manage comments | To verify which scopes your connected account has granted, call `GET /v1/accounts/:id/limits`. # Quickstart Source: https://docs.social-api.ai/quickstart Make your first social media inbox API call in under 5 minutes. This quickstart gets you calling the SocialAPI.ai unified social media API in minutes: one key, one base URL, and the same `Interaction` schema for comments, DMs, mentions, and reviews across every connected platform. This walks you from zero to a published post in seven steps. By the end you will have a working API key, a connected social account, a comment listed and replied to, and a post live on at least one platform. Total time: about five minutes if your social account is ready, ten if you need to create one. ## 1. Create your account Sign up at [app.social-api.ai](https://app.social-api.ai). Verify your email and log in. Not sure which plan you need? See [plans and pricing](https://social-api.ai/pricing). ## 2. Get an API key In the dashboard, go to **Keys**, then **New Key**. Give it a name and click **Create**. Copy the key immediately, it is shown only once. It starts with `sapi_key_`. Store it in your environment: ```bash theme={null} export SOCAPI_KEY="sapi_key_your_key_here" ``` If the dashboard does not show a key after creation, refresh the Keys list. The key value itself is shown only once; if you missed it, revoke and create another. ## 3. Connect a social account No platform developer app required. Click Connect and you're authorizing through [SocialAPI](https://social-api.ai)'s pre-approved app. Twitter is the one exception - it requires a one-time [BYOK setup](/connectors/twitter-byok). See [Platform credentials](/guides/platform-credentials) for the full story. In the dashboard, go to **Accounts**, then **Connect**, and choose your platform. Complete the OAuth flow in your browser. Or connect programmatically: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/accounts/connect \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"platform": "instagram"}' ``` The response includes an `auth_url`. Redirect the user there to complete OAuth. If `auth_url` is missing from the response, check that `platform` matches one of `instagram`, `facebook`, `threads`, `google`, `tiktok`, `linkedin`, `youtube`, `twitter`. See [OAuth flows](/guides/oauth#step-1-initiate-the-flow) for the full handshake. ## 4. List your accounts ```bash theme={null} curl https://api.social-api.ai/v1/accounts \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ```json theme={null} { "data": [ { "id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform": "instagram", "name": "Acme Corp", "username": "acmecorp" } ], "count": 1 } ``` Copy the `id`. You'll use it to scope inbox queries and publish posts. If the response is empty, the OAuth flow did not complete. Re-run step 3 and follow the redirect through to the end. ## 5. Read your inbox List posts that have comments: ```bash theme={null} curl "https://api.social-api.ai/v1/inbox/comments?account_id=acc_01HZ9X3Q4R5M6N7P8V2K0W1J&limit=5" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` Then fetch comments on a specific post: ```bash theme={null} curl "https://api.social-api.ai/v1/inbox/comments/POST_ID?account_id=acc_01HZ9X3Q4R5M6N7P8V2K0W1J&limit=5" \ -H "Authorization: Bearer $SOCAPI_KEY" ``` ```json theme={null} { "data": [ { "platform_id": "17858893269577012", "platform": "instagram", "text": "Love this product!", "author_id": "17841405793187218", "author_name": "Jane Smith", "author_username": "janesmith", "author_picture": "https://example.com/avatar.jpg", "is_owner": false, "like_count": 3, "reply_count": 0, "has_replies": false, "is_hidden": false, "parent_id": null, "created_at": "2026-04-15T14:30:00Z", "capabilities": { "can_reply": true, "can_delete": true, "can_hide": true, "can_like": true, "can_private_reply": true } } ], "pagination": { "has_more": false, "next_cursor": "" } } ``` To reply to a specific comment in step 6, use the `platform_id` value above (or pass a `sapi_cmt_...` interaction ID, both are accepted). If `data` is empty, the account has no comments yet, or the post being queried has none. Try a different `account_id`, or post a comment from another account first. ## 6. Reply to a comment ```bash theme={null} curl -X POST \ "https://api.social-api.ai/v1/inbox/comments/POST_ID" \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{"text": "Thanks so much, Jane!", "comment_id": "17858893269577012", "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J"}' ``` ```json theme={null} { "success": true, "comment_id": "sapi_cmt_cmVwbHk6MTIzNDU2Nzg5" } ``` If you receive `501 not_supported`, the platform does not allow programmatic reply for that interaction type. Check `capabilities.can_reply` on the comment first. ## 7. Publish a post Create a post and publish it to one or more platforms: ```bash theme={null} curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Hello world from SocialAPI!", "publish_now": true, "targets": [ { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" } ] }' ``` ```json theme={null} { "id": "p_01HZ9X3Q4R5M6N7P8V2K0W1J", "text": "Hello world from SocialAPI!", "status": "publishing", "hidden": false, "retry_count": 0, "created_at": "2026-04-15T12:00:00Z", "updated_at": "2026-04-15T12:00:00Z", "targets": [ { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J", "platform": "instagram", "status": "publishing" } ] } ``` The platform for each target is inferred from `account_id`, no `platform` field is required. Set `scheduled_at` instead of `publish_now` to queue the post for later. Omit both to save as a draft. Omitting `scheduled_at` does **not** publish immediately, you must pass `"publish_now": true`. A post created without `publish_now` or `scheduled_at` is saved as a draft (drafts are free, no post credit is charged). To publish an existing draft later, call `POST /v1/posts/{pid}/publish`. ### Publish a post with an image Text-only posts need just the call above. To attach an image, upload it first, verify it, then pass its ID in `media_ids` (a flat array of strings). This is a four-step flow: ```bash theme={null} # 1. Request a signed upload URL (media_type and filename are required) curl "https://api.social-api.ai/v1/media/upload-url?media_type=image/jpeg&filename=launch.jpg" \ -H "Authorization: Bearer $SOCAPI_KEY" # -> { "media_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "upload_url": "https://...", "expires_at": "..." } # 2. Upload the bytes to the signed URL (Content-Type must match media_type) curl -X PUT "https://...signed-upload-url..." \ -H "Content-Type: image/jpeg" \ --data-binary @launch.jpg # 3. Verify the upload (required: flips the media from pending to ready) curl -X POST https://api.social-api.ai/v1/media/f47ac10b-58cc-4372-a567-0e02b2c3d479/verify \ -H "Authorization: Bearer $SOCAPI_KEY" # 4. Create the post, referencing the media by ID curl -X POST https://api.social-api.ai/v1/posts \ -H "Authorization: Bearer $SOCAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Our spring collection is live.", "publish_now": true, "media_ids": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"], "targets": [ { "account_id": "acc_01HZ9X3Q4R5M6N7P8V2K0W1J" } ] }' ``` The field is `media_ids`, a flat array of ID strings. A raw image URL, or a different shape such as `media: [{ "media_id": "..." }]`, is not applied, and the post publishes without the image. Skipping step 3 leaves the media `pending`, and the publish fails rather than posting without it. See [Media uploads](/guides/media) for videos, carousels, and the server-side upload path. Publishing to several targets can partly succeed. A `201` means the post was created (delivery runs in the background); `207` means some targets published and others failed; `422` means all failed. When a target fails, the reason is under `targets[].error`. Do not treat `207` as a total failure. See [Publishing behavior and reliability](/guides/reliability#response-codes). ## What you just built You now have a working integration: an API key authenticated through [SocialAPI](https://social-api.ai), a connected social account whose tokens we manage for you, the ability to read and respond to comments, and the ability to publish posts. Everything else in the docs builds on these primitives. If you want to understand the data model behind what you just used, read [Core concepts](/guides/concepts). It explains the five nouns the API exposes (Brand, which pricing calls a social profile, plus Account, Post, Interaction, and Capability) in about five minutes. ## Where to go next Brand, Account, Post, Interaction, Capability. API keys, JWTs, and OAuth tokens. Per-platform fields, media constraints, scheduling. Page through large result sets with cursors. Get notified of new comments, messages, and events. Capabilities by platform.