# Zernio API - LLM Documentation # https://zernio.com # A powerful REST API for scheduling social media posts across 15 platforms + ad management on 6 ad networks ## Overview Zernio is a social media scheduling API that lets you post to TikTok, Instagram, WhatsApp, Facebook, YouTube, LinkedIn, Twitter/X, Threads, Reddit, Pinterest, Bluesky, Google Business, Telegram, Snapchat, and Discord. It also supports paid advertising across Meta Ads (Facebook + Instagram), Google Ads, TikTok Ads, LinkedIn Ads, Pinterest Ads, and X Ads via a unified /v1/ads endpoint. Base URL: https://zernio.com/api/v1 ## Authentication All endpoints require Bearer token authentication using API keys. ``` Authorization: Bearer YOUR_API_KEY ``` Get your API key by: 1. Sign up at https://zernio.com/signup 2. Create an API key in the dashboard 3. Use the key in the Authorization header ## Rate Limits - Free (first 2 social accounts): 60 requests/minute - Paid (any usage-based account, AppSumo): 600 requests/minute - High-volume / Enterprise: 1,200 requests/minute (contact support to upgrade) Posting volume itself is unlimited per connected social account; the rate limit applies only to API request throughput. Per-platform safety caps still apply on the social-network side. ## Core Concepts ### Profile System Zernio uses profiles to organize social media accounts: - Create multiple profiles per user account - **One account per platform per profile**: Each profile can connect ONE account per social platform (e.g., 1 Instagram + 1 TikTok + 1 YouTube, but NOT 2 Instagram accounts) - To manage multiple accounts from the same platform (e.g., 20 GMB locations or 10 Instagram accounts), you need one profile for each - Posts are scheduled to accounts connected to a specific profile ### Pricing (usage-based) - **Free**: first 2 social accounts, unlimited posts, full API access (no credit card required) - **Beyond 2 accounts**: pay only for what you use - $6/account/month for accounts 1-10 - $3/account/month for accounts 11-100 - $1/account/month for accounts 101-2,000 - 2,001+ accounts: custom enterprise pricing (https://zernio.com/enterprise) - Analytics, inbox (comments + DMs), ads, and unlimited posts are bundled with every connected account. No separate add-ons or tier upgrades. - WhatsApp numbers: $2/number/month (separate from social accounts). - X (Twitter) API: pass-through cost at zero markup. Example: Managing 20 Google Business locations needs 20 connected accounts. With the first 2 free, the remaining 18 are: 8 × $6 + 10 × $3 = $78/month. ### Workflow 1. Create Profile → 2. Connect Social Accounts → 3. Schedule Posts ## API Endpoints ### Profiles #### List Profiles ``` GET /v1/profiles ``` Returns all profiles for authenticated user. Response: ```json { "profiles": [ { "_id": "64f0...", "name": "Personal Brand", "description": "My personal accounts", "color": "#ffeda0", "isDefault": true, "createdAt": "2024-01-01T00:00:00Z" } ] } ``` #### Create Profile ``` POST /v1/profiles ``` Body: ```json { "name": "Personal Brand", "description": "My personal accounts", "color": "#ffeda0" } ``` #### Get Profile ``` GET /v1/profiles/{profileId} ``` #### Update Profile ``` PUT /v1/profiles/{profileId} ``` Body: ```json { "name": "Updated Name", "description": "Updated description", "color": "#newcolor", "isDefault": false } ``` #### Delete Profile ``` DELETE /v1/profiles/{profileId} ``` Note: Profile must have no connected accounts to be deleted. ### Social Accounts #### List Connected Accounts ``` GET /v1/accounts GET /v1/accounts?profileId={profileId} ``` Optional query param to filter by profile. Response: ```json { "accounts": [ { "_id": "64e1...", "platform": "twitter", "profileId": "64f0...", "username": "@acme", "displayName": "Acme", "isActive": true } ] } ``` #### Update Account ``` PUT /v1/accounts/{accountId} ``` Body: ```json { "username": "newusername", "displayName": "New Display Name" } ``` #### Disconnect Account ``` DELETE /v1/accounts/{accountId} ``` ### Connect Social Platforms #### Start OAuth Connection ``` GET /v1/connect/{platform}?profileId={profileId}&redirect_url={optional_redirect} ``` Platforms: twitter, instagram, facebook, youtube, linkedin, threads, tiktok, pinterest, reddit Optional redirect_url for custom OAuth success/error handling. Response: ```json { "authUrl": "https://platform.com/oauth/authorize?...", "state": "random_state_string" } ``` #### Complete OAuth (Manual) ``` POST /v1/connect/{platform} ``` Body: ```json { "code": "oauth_code", "state": "state_from_initial_request", "profileId": "your_profile_id" } ``` #### Connect Bluesky (App Password) ``` POST /v1/connect/bluesky/credentials ``` Body: ```json { "identifier": "username.bsky.social", "appPassword": "your-app-password", "state": "state_string", "redirectUri": "optional_redirect" } ``` #### Facebook: Select Page ``` GET /v1/connect/facebook/select-page?profileId={profileId}&tempToken={tempToken} POST /v1/connect/facebook/select-page ``` Body: ```json { "profileId": "profile_id", "pageId": "facebook_page_id", "tempToken": "temp_token", "redirect_url": "optional" } ``` #### LinkedIn: Select Organization ``` POST /v1/connect/linkedin/select-organization ``` Body: ```json { "profileId": "profile_id", "tempToken": "temp_token", "userProfile": {}, "accountType": "personal", "selectedOrganization": {}, "redirect_url": "optional" } ``` #### Pinterest: Select Board (Headless Mode) ``` GET /v1/connect/pinterest/select-board?profileId={profileId}&tempToken={tempToken} POST /v1/connect/pinterest/select-board ``` GET: Fetch available Pinterest boards for selection UI (use X-Connect-Token header). POST Body: ```json { "profileId": "profile_id", "boardId": "pinterest_board_id", "boardName": "Marketing Ideas", "tempToken": "temp_token", "userProfile": {}, "redirect_url": "optional" } ``` ### Posts #### List Posts ``` GET /v1/posts GET /v1/posts?page=1&limit=10&status=scheduled&platform=twitter&profileId={id} ``` Query params: - page (default: 1) - limit (default: 10, max: 100) - status: draft, scheduled, published, failed - platform: twitter, instagram, facebook, etc. - profileId: filter by profile - createdBy: filter by user ID - dateFrom: YYYY-MM-DD - dateTo: YYYY-MM-DD - includeHidden: true/false (default: false) Response: ```json { "posts": [ { "_id": "65f1c0a9e2b5af0012ab34cd", "title": "Launch post", "content": "We just launched!", "status": "scheduled", "scheduledFor": "2024-11-01T10:00:00Z", "timezone": "UTC", "mediaItems": [], "platforms": [ { "platform": "twitter", "accountId": "64e1f0...", "status": "pending" } ], "tags": ["launch"], "hashtags": [], "visibility": "public", "createdAt": "2024-10-01T12:00:00Z", "updatedAt": "2024-10-01T12:00:00Z" } ], "pagination": { "page": 1, "limit": 10, "total": 1, "pages": 1 } } ``` #### Create Post ``` POST /v1/posts ``` Body: ```json { "content": "Hello, world! 🌍", "scheduledFor": "2024-01-01T12:00:00Z", "publishNow": false, "isDraft": false, "timezone": "America/New_York", "platforms": [ { "platform": "twitter", "accountId": "TWITTER_ACCOUNT_ID" }, { "platform": "linkedin", "accountId": "LINKEDIN_ACCOUNT_ID", "customContent": "Professional version of post" } ], "profileId": "PROFILE_ID", "mediaItems": [ { "type": "image", "url": "https://your-storage.com/image.jpg" } ], "title": "My Post Title", "tags": ["marketing", "social"], "hashtags": ["startup", "tech"], "visibility": "public" } ``` Options: - publishNow: true (publish immediately) - isDraft: true (save as draft) - scheduledFor: ISO 8601 datetime (schedule for later) #### Get Single Post ``` GET /v1/posts/{postId} ``` #### Update Post ``` PUT /v1/posts/{postId} ``` Body: Same as create, any fields to update. #### Delete Post ``` DELETE /v1/posts/{postId} ``` Note: Cannot delete already published posts. #### Retry Failed Post ``` POST /v1/posts/{postId}/retry ``` Retry publishing a failed or partial post. #### Bulk Upload Posts (CSV) ``` POST /v1/posts/bulk-upload?dryRun=false ``` Upload CSV file with multiple posts. Form data: ``` file: posts.csv (multipart/form-data) ``` ### Media #### Upload Media (Presigned URL) ``` POST /v1/media/presign ``` Get a presigned URL for uploading files directly (up to 5GB). Request: ```json { "filename": "video.mp4", "contentType": "video/mp4" } ``` Response: ```json { "uploadUrl": "", "publicUrl": "https://media.zernio.com/temp/...", "key": "temp/1234_abc_video.mp4", "type": "video" } ``` Then upload directly to the `uploadUrl` with a PUT request: ```bash curl -X PUT "$uploadUrl" -H "Content-Type: video/mp4" --data-binary @video.mp4 ``` Use the `publicUrl` in your posts. ### API Keys #### List API Keys ``` GET /v1/api-keys ``` #### Create API Key ``` POST /v1/api-keys ``` Body: ```json { "name": "Production API Key", "permissions": ["read", "write"], "expiresIn": 365 } ``` Response includes the key (only shown once): ```json { "message": "API key created", "apiKey": { "id": "key_id", "name": "Production API Key", "key": "late_abc123...", "keyPreview": "late_abc...xyz", "permissions": ["read", "write"], "expiresAt": "2025-01-01T00:00:00Z", "createdAt": "2024-01-01T00:00:00Z" } } ``` #### Delete API Key ``` DELETE /v1/api-keys/{keyId} ``` ### Account Groups #### List Account Groups ``` GET /v1/account-groups ``` #### Create Account Group ``` POST /v1/account-groups ``` Body: ```json { "name": "My Social Media Team", "accountIds": ["account1", "account2"] } ``` #### Update Account Group ``` PUT /v1/account-groups/{groupId} ``` #### Delete Account Group ``` DELETE /v1/account-groups/{groupId} ``` ### Usage Stats #### Get Usage Stats ``` GET /v1/usage-stats ``` Response: ```json { "planName": "Pro", "billingPeriod": "monthly", "signupDate": "2024-01-01T00:00:00Z", "limits": { "uploads": 1000, "profiles": 10 }, "usage": { "uploads": 45, "profiles": 3, "lastReset": "2024-10-01T00:00:00Z" } } ``` ### Platform-Specific Endpoints #### Facebook: Update Page ``` PUT /v1/accounts/{accountId}/facebook-page ``` Body: ```json { "selectedPageId": "facebook_page_id" } ``` #### LinkedIn: List Organizations ``` GET /v1/accounts/{accountId}/linkedin-organizations ``` #### LinkedIn: Switch Organization ``` PUT /v1/accounts/{accountId}/linkedin-organization ``` Body: ```json { "accountType": "organization", "selectedOrganization": {} } ``` #### Pinterest: List Boards ``` GET /v1/accounts/{accountId}/pinterest-boards ``` #### Pinterest: Set Default Board ``` PUT /v1/accounts/{accountId}/pinterest-boards ``` Body: ```json { "defaultBoardId": "board_id", "defaultBoardName": "Board Name" } ``` #### Reddit: List Subreddits ``` GET /v1/accounts/{accountId}/reddit-subreddits ``` #### Reddit: Set Default Subreddit ``` PUT /v1/accounts/{accountId}/reddit-subreddits ``` Body: ```json { "defaultSubreddit": "r/programming" } ``` #### Reddit: Search Posts ``` GET /v1/reddit/search?accountId={id}&q=search+term&subreddit=programming&sort=new&limit=25 ``` #### Reddit: Get Feed ``` GET /v1/reddit/feed?accountId={id}&subreddit=programming&sort=hot&limit=25 ``` ## Platform-Specific Data ### Twitter Threads Use `platformSpecificData.threadItems`: ```json { "platforms": [ { "platform": "twitter", "accountId": "TWITTER_ID", "platformSpecificData": { "threadItems": [ { "content": "First tweet", "mediaItems": [] }, { "content": "Second tweet", "mediaItems": [] } ] } } ] } ``` ### Threads Threads Use `platformSpecificData.threadItems`: ```json { "platforms": [ { "platform": "threads", "accountId": "THREADS_ID", "platformSpecificData": { "threadItems": [ { "content": "First post", "mediaItems": [] }, { "content": "Reply", "mediaItems": [] } ] } } ] } ``` ### Facebook First Comment ```json { "platforms": [ { "platform": "facebook", "accountId": "FB_ID", "platformSpecificData": { "firstComment": "Check out the link in comments!", "pageId": "optional_page_id" } } ] } ``` ### Instagram Stories & Collaborators ```json { "platforms": [ { "platform": "instagram", "accountId": "IG_ID", "platformSpecificData": { "contentType": "story", "collaborators": ["username1", "username2"] } } ] } ``` Note: Instagram requires Business accounts. Stories require media. ### LinkedIn First Comment ```json { "platforms": [ { "platform": "linkedin", "accountId": "LI_ID", "platformSpecificData": { "firstComment": "Thoughts?" } } ] } ``` ### Pinterest Pins ```json { "platforms": [ { "platform": "pinterest", "accountId": "PIN_ID", "platformSpecificData": { "title": "My Pin Title (max 100 chars)", "boardId": "board_id", "link": "https://example.com", "coverImageUrl": "https://cover.jpg" } } ] } ``` ### TikTok Settings TikTok requires extensive settings. Use root-level `tiktokSettings` as a shorthand that applies to all TikTok platforms: ```json { "content": "My TikTok video", "mediaItems": [{"type": "video", "url": "https://example.com/video.mp4"}], "platforms": [{"platform": "tiktok", "accountId": "TIKTOK_ACCOUNT_ID"}], "tiktokSettings": { "privacy_level": "PUBLIC_TO_EVERYONE", "allow_comment": true, "allow_duet": true, "allow_stitch": true, "commercial_content_type": "none", "content_preview_confirmed": true, "express_consent_given": true, "media_type": "video", "auto_add_music": false, "video_made_with_ai": false } } ``` For photo carousels, set `media_type` to `"photo"` and optionally enable `auto_add_music` for TikTok to add recommended music. Get available privacy levels from TikTok creator info API first. ### YouTube Tags & Title ```json { "title": "My Video Title (max 100 chars)", "tags": ["tag1", "tag2"], "visibility": "public" } ``` YouTube constraints: - Title ≤ 100 characters - Each tag ≤ 100 characters - Total tag characters ≤ 500 - No tag count limit (duplicates removed) - Requires video in mediaItems ## Platform Constraints ### Instagram - Requires Business accounts - Carousels: up to 10 items, cannot mix images/videos - Stories: require media, no captions - Collaborators: up to 3 usernames (feed/Reels only) ### TikTok - Photo carousels: up to 35 images, cannot mix with videos - Video title: up to 2200 chars - Photo title: automatically truncated to 90 chars (hashtags/URLs stripped). Use description field for full text (up to 4000 chars). - Must set privacy_level from creator_info options - Required: allow_comment, allow_duet, allow_stitch - Must confirm content preview and give express consent ### YouTube - Always requires at least one video - Title ≤ 100 characters - Tags: no count cap, but ≤ 500 total characters ### Facebook - Cannot mix videos and images - Multiple images: up to 10 via attached_media - Multiple videos not supported ### LinkedIn - Multi-image: up to 20 images - Multi-video not supported - Single PDF documents supported ### Pinterest - Single video per Pin - Single image per Pin (image_url flow) - boardId required - Title ≤ 100 characters (optional, defaults to first line of content) ### Reddit - Must select subreddit ## Media Types Supported media types: - image: JPEG, PNG, GIF (static) - video: MP4, MOV, etc. - gif: Animated GIF - document: PDF (LinkedIn only) Media properties: ```json { "type": "image", "url": "https://publicly-accessible-url.jpg", "filename": "optional.jpg", "size": 524288, "mimeType": "image/jpeg", "thumbnail": "https://video-thumbnail.jpg", "instagramThumbnail": "https://reel-cover.jpg" } ``` Important: URLs must be publicly accessible over HTTPS until upload completes. ## Error Responses All errors return JSON: ```json { "error": "Error message", "details": {} } ``` HTTP Status Codes: - 400: Bad Request (validation error) - 401: Unauthorized (invalid/missing API key) - 403: Forbidden (quota exceeded, BYOK required) - 404: Not Found - 409: Conflict (duplicate, already exists) - 413: Payload Too Large - 429: Rate Limit Exceeded - 500: Internal Server Error ## Users & Teams #### List Users ``` GET /v1/users ``` List all users (root + invited team members). #### Get User ``` GET /v1/users/{userId} ``` ## Custom Content Per Platform Each platform in the platforms array can have: - customContent: Override the main content for this platform - customMedia: Override the main mediaItems for this platform Example: ```json { "content": "General post content", "mediaItems": [{"type": "image", "url": "https://default.jpg"}], "platforms": [ { "platform": "twitter", "accountId": "TWITTER_ID" }, { "platform": "linkedin", "accountId": "LINKEDIN_ID", "customContent": "Professional version with custom content", "customMedia": [{"type": "image", "url": "https://professional.jpg"}] } ] } ``` ## Timezones Posts can be scheduled in any timezone: ```json { "scheduledFor": "2024-01-01T12:00:00Z", "timezone": "America/New_York" } ``` Supported timezone format: IANA timezone strings (e.g., "America/New_York", "Europe/London", "UTC") ## Examples ### Example 1: Schedule Simple Text Post ```bash curl -X POST https://zernio.com/api/v1/posts \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "content": "Hello, world! 🌍", "scheduledFor": "2024-01-01T12:00:00Z", "platforms": [ {"platform": "twitter", "accountId": "TWITTER_ID"}, {"platform": "linkedin", "accountId": "LINKEDIN_ID"} ], "profileId": "PROFILE_ID" }' ``` ### Example 2: Schedule Post with Media ```bash # First upload media curl -X POST https://zernio.com/api/v1/media \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "files=@image.jpg" # Then schedule post curl -X POST https://zernio.com/api/v1/posts \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "content": "Check out this image! 📸", "scheduledFor": "2024-01-01T12:00:00Z", "platforms": [ {"platform": "instagram", "accountId": "IG_ID"} ], "profileId": "PROFILE_ID", "mediaItems": [ {"type": "image", "url": "https://media.zernio.com/uploaded-image.jpg"} ] }' ``` ### Example 3: Twitter Thread ```bash curl -X POST https://zernio.com/api/v1/posts \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "platforms": [ { "platform": "twitter", "accountId": "TWITTER_ID", "platformSpecificData": { "threadItems": [ {"content": "1/ This is a thread about Zernio API"}, {"content": "2/ You can schedule threads easily"}, {"content": "3/ Just use the threadItems array!"} ] } } ], "profileId": "PROFILE_ID", "scheduledFor": "2024-01-01T12:00:00Z" }' ``` ### Example 4: Multi-Platform with Custom Content ```bash curl -X POST https://zernio.com/api/v1/posts \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "content": "Check out our new feature! 🚀", "platforms": [ { "platform": "twitter", "accountId": "TWITTER_ID" }, { "platform": "linkedin", "accountId": "LINKEDIN_ID", "customContent": "Excited to announce our new feature that helps businesses streamline their social media workflow. Learn more about how Zernio can transform your content strategy." }, { "platform": "instagram", "accountId": "IG_ID", "customContent": "New feature alert! 🎉 #socialmedia #marketing" } ], "profileId": "PROFILE_ID", "publishNow": true }' ``` ### Example 5: Connect Platform via OAuth ```bash # Step 1: Get authorization URL curl "https://zernio.com/api/v1/connect/twitter?profileId=PROFILE_ID&redirect_url=https://myapp.com/success" \ -H "Authorization: Bearer YOUR_API_KEY" # Returns: {"authUrl": "https://twitter.com/oauth/...", "state": "xyz"} # Step 2: User authorizes on Twitter and is redirected back with code # Step 3: Complete connection (optional manual step) curl -X POST https://zernio.com/api/v1/connect/twitter \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": "oauth_code_from_callback", "state": "xyz", "profileId": "PROFILE_ID" }' ``` ## Common Workflows ### Workflow 1: First Time Setup 1. Create API key in dashboard 2. Create a profile: `POST /v1/profiles` 3. Connect social accounts: `GET /v1/connect/{platform}?profileId=ID` 4. List connected accounts: `GET /v1/accounts?profileId=ID` 5. Schedule first post: `POST /v1/posts` ### Workflow 2: Schedule Content Calendar 1. Prepare media: `POST /v1/media` (for each image/video) 2. Create multiple posts: `POST /v1/posts` (with different scheduledFor times) 3. Monitor posts: `GET /v1/posts?status=scheduled` ### Workflow 3: Multi-Brand Management 1. Create profile for each brand: `POST /v1/profiles` 2. Connect accounts to each profile 3. Schedule to specific profiles ## Best Practices 1. **Store API keys securely**: Never commit API keys to version control 2. **Handle rate limits**: Implement exponential backoff for 429 responses 3. **Validate before scheduling**: Check platform constraints before creating posts 4. **Use webhooks**: Monitor post status changes via webhooks (if available) 5. **Test with drafts**: Use isDraft: true to test without publishing 6. **Upload media first**: Upload media via /v1/media before scheduling posts 7. **Use dryRun**: Test bulk uploads with dryRun=true parameter 8. **Handle OAuth carefully**: Store state parameter and verify it on callback 9. **Check usage stats**: Monitor your usage to avoid hitting limits 10. **Set proper timezones**: Always specify timezone for accurate scheduling ## Support - Website: https://zernio.com - Documentation: https://docs.zernio.com - Email: miki@zernio.com - API Status: https://zernio.com/status ## Additional Resources - OpenAPI Spec: https://zernio.com/openapi.yaml - Interactive Docs: https://docs.zernio.com - Code Examples: https://docs.zernio.com#examples - Changelog: https://zernio.com/changelog ## Version API Version: 1.0.0 Last Updated: October 2024