openapi: 3.1.0
info:
title: Late API
version: "1.0.1"
description: |
API reference for Late. Authenticate with a Bearer API key.
Base URL: https://getlate.dev/api
termsOfService: https://getlate.dev/tos
contact:
name: Late Support
url: https://getlate.dev
email: support@getlate.dev
x-logo:
url: https://getlate.dev/icon.png?v=3
x-long-description: |
Late is the social media API that replaces 13 integrations. Schedule posts, retrieve analytics,
manage DMs, comments, and reviews across Twitter/X, Instagram, TikTok, LinkedIn, Facebook,
YouTube, Threads, Reddit, Pinterest, Bluesky, Telegram, Google Business, and Snapchat, all
from a single REST API.
Key features: Unified posting to 13 platforms, aggregated analytics, unified inbox (DMs, comments, reviews), webhooks, OAuth connect, queue scheduling, and white-label support for agencies managing 5,000+ accounts.
Supported platforms: Twitter/X, Instagram, Facebook, LinkedIn, TikTok, YouTube, Pinterest, Reddit, Bluesky, Threads, Google Business, Telegram, Snapchat.
x-category: Social
x-website: https://getlate.dev
x-thumbnail: https://rapidapi-prod-apis.s3.amazonaws.com/b24d3df5-563c-4a50-9e1e-1ad3eb1fce69.png
x-version-lifecycle: ACTIVE
x-badges:
- name: "social media"
value: "social media"
- name: "scheduling"
value: "scheduling"
- name: "instagram"
value: "instagram"
- name: "tiktok"
value: "tiktok"
- name: "twitter"
value: "twitter"
- name: "linkedin"
value: "linkedin"
- name: "facebook"
value: "facebook"
- name: "youtube"
value: "youtube"
- name: "social media api"
value: "social media api"
- name: "posting"
value: "posting"
x-documentation:
readme: |
# Late API
The social media API that replaces 13 integrations. Build social media features into your app in minutes, not months.
## Quick Start
**Base URL:** `https://getlate.dev/api/v1`
**Authentication:** All requests require a Bearer API key in the `Authorization` header.
```bash
curl https://getlate.dev/api/v1/user \
-H "Authorization: Bearer YOUR_API_KEY"
```
Get your API key at [getlate.dev/dashboard/api-keys](https://getlate.dev/dashboard/api-keys).
## Core Concepts
| Concept | Description |
|---------|-------------|
| **Profiles** | Containers that organize social accounts into brands or projects |
| **Accounts** | Connected social media accounts belonging to a profile |
| **Posts** | Content scheduled or published to one or more accounts |
| **Queue** | Recurring time slots for automatic post scheduling |
## Create a Post
```bash
curl -X POST https://getlate.dev/api/v1/post \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profileId": "your-profile-id",
"text": "Hello from Late API!",
"socialAccountIds": ["account-1", "account-2"],
"scheduledAt": "2025-01-15T10:00:00Z"
}'
```
This single call publishes or schedules the post to all selected accounts across any platform.
## Supported Platforms
| Platform | Post | Stories/Reels | Analytics | Inbox |
|----------|------|---------------|-----------|-------|
| Twitter/X | Yes | - | Yes | Yes |
| Instagram | Yes | Yes | Yes | Yes |
| Facebook | Yes | Stories | Yes | Yes |
| LinkedIn | Yes | - | Yes | - |
| TikTok | Yes | - | Yes | - |
| YouTube | Yes | Shorts | Yes | Yes |
| Pinterest | Yes | - | Yes | - |
| Reddit | Yes | - | - | Yes |
| Bluesky | Yes | - | - | Yes |
| Threads | Yes | - | Yes | Yes |
| Google Business | Yes | - | - | Yes |
| Telegram | Yes | - | - | - |
| Snapchat | Yes | - | - | - |
## Rate Limits
| Plan | Requests/min | Posts/month |
|------|-------------|-------------|
| Free | 60 | 20 |
| Build | 120 | 120 |
| Accelerate | 600 | Unlimited |
| Unlimited | 1,200 | Unlimited |
All responses include `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers.
## Webhooks
Receive real-time notifications for post status changes, account events, and incoming messages:
- `post.scheduled` - Post successfully scheduled
- `post.published` - Post successfully published
- `post.failed` - Post failed on all platforms
- `post.partial` - Post published to some platforms, failed on others
- `account.connected` - Social account connected
- `account.disconnected` - Social account disconnected (token expired)
- `message.received` - New DM received
- `comment.received` - New comment received on a post
Webhook payloads are signed with HMAC-SHA256 via the `X-Late-Signature` header.
## Full Documentation
For complete guides, platform-specific details, and SDK references, visit [docs.getlate.dev](https://docs.getlate.dev).
## SDKs
Official SDKs available for: [Node.js](https://www.npmjs.com/package/late), [Python](https://pypi.org/project/late), Go, Ruby, Java, PHP, .NET, and Rust.
servers:
- url: https://getlate.dev/api
description: Production
- url: http://localhost:3000/api
description: Local
tags:
- name: Posts
- name: Users
- name: Usage
- name: Profiles
- name: Accounts
- name: Account Groups
- name: API Keys
- name: Invites
- name: Connect
- name: Media
- name: Reddit Search
- name: Facebook
- name: GMB Reviews
- name: GMB Food Menus
- name: GMB Location Details
- name: GMB Media
- name: GMB Attributes
- name: GMB Place Actions
- name: LinkedIn Mentions
- name: Pinterest
- name: TikTok
- name: Queue
- name: Analytics
- name: Inbox Access
description: |
Check and manage inbox feature access.
- name: Messages
description: |
Unified inbox API for managing conversations and direct messages across all connected accounts.
All endpoints aggregate data from multiple social accounts in a single API call.
Requires Inbox addon.
- name: Comments
description: |
Unified inbox API for managing comments on posts across all connected accounts.
Supports commenting on third-party posts for platforms that allow it (YouTube, Twitter, Reddit, Bluesky, Threads).
All endpoints aggregate data from multiple social accounts in a single API call.
Requires Inbox addon.
- name: Reviews
description: |
Unified inbox API for managing reviews on Facebook Pages and Google Business accounts.
All endpoints aggregate data from multiple social accounts in a single API call.
Requires Inbox addon.
- name: Tools
description: |
Media download and utility tools. Available to paid plans only.
Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited).
All responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers.
- name: Validate
description: |
Pre-flight validation endpoints. Check post content, character limits, media URLs, and subreddit existence before publishing.
- name: Account Settings
description: |
Platform-specific account settings: Facebook persistent menu, Instagram ice breakers, and Telegram bot commands.
- name: Webhooks
description: |
Configure webhooks for real-time notifications. Events: post.scheduled, post.published, post.failed, post.partial, account.connected, account.disconnected, message.received, comment.received.
Security: optional HMAC-SHA256 signature in X-Late-Signature header. Configure a secret key to enable verification. Custom headers supported.
- name: Logs
description: |
Publishing logs for transparency and debugging. Each log includes the platform API endpoint, HTTP status code, request/response bodies, duration, and retry attempts. Logs are automatically deleted after 7 days.
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: API key authentication - use your Late API key as a Bearer token
connectToken:
type: apiKey
in: header
name: X-Connect-Token
description: |
Short-lived connect token for API users during OAuth flows.
Automatically generated when initiating OAuth without a browser session.
Valid for 15 minutes. Used to authenticate Facebook page selection API calls.
parameters:
PageParam:
name: page
in: query
description: Page number (1-based)
schema: { type: integer, minimum: 1, default: 1 }
LimitParam:
name: limit
in: query
description: Page size
schema: { type: integer, minimum: 1, maximum: 100, default: 10 }
responses:
Unauthorized:
description: Unauthorized
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: Unauthorized
NotFound:
description: Resource not found
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: Not found
schemas:
ErrorResponse:
type: object
properties:
error:
type: string
details:
type: object
additionalProperties: true
FoodMenuLabel:
type: object
required: [displayName]
properties:
displayName: { type: string, description: Display name of the item/section/menu }
description: { type: string, description: Optional description }
languageCode: { type: string, description: "BCP-47 language code (e.g. en, es)" }
Money:
type: object
required: [currencyCode, units]
properties:
currencyCode: { type: string, description: "ISO 4217 currency code (e.g. USD, EUR)" }
units: { type: string, description: Whole units of the amount }
nanos: { type: integer, description: Nano units (10^-9) of the amount }
FoodMenuItemAttributes:
type: object
properties:
price: { $ref: '#/components/schemas/Money' }
spiciness: { type: string, description: "Spiciness level (e.g. MILD, MEDIUM, HOT)" }
allergen:
type: array
items: { type: string }
description: "Allergens (e.g. DAIRY, GLUTEN, SHELLFISH)"
dietaryRestriction:
type: array
items: { type: string }
description: "Dietary labels (e.g. VEGETARIAN, VEGAN, GLUTEN_FREE)"
servesNumPeople: { type: integer, description: Number of people the item serves }
preparationMethods:
type: array
items: { type: string }
description: "Preparation methods (e.g. GRILLED, FRIED)"
mediaKeys:
type: array
items: { type: string }
description: Media references for item photos
FoodMenuItem:
type: object
required: [labels]
properties:
labels:
type: array
items: { $ref: '#/components/schemas/FoodMenuLabel' }
attributes: { $ref: '#/components/schemas/FoodMenuItemAttributes' }
options:
type: array
items:
type: object
properties:
labels:
type: array
items: { $ref: '#/components/schemas/FoodMenuLabel' }
attributes: { $ref: '#/components/schemas/FoodMenuItemAttributes' }
description: Item variants/options (e.g. sizes, preparations)
FoodMenuSection:
type: object
required: [labels]
properties:
labels:
type: array
items: { $ref: '#/components/schemas/FoodMenuLabel' }
items:
type: array
items: { $ref: '#/components/schemas/FoodMenuItem' }
FoodMenu:
type: object
required: [labels]
properties:
labels:
type: array
items: { $ref: '#/components/schemas/FoodMenuLabel' }
sections:
type: array
items: { $ref: '#/components/schemas/FoodMenuSection' }
cuisines:
type: array
items: { type: string }
description: "Cuisine types (e.g. AMERICAN, ITALIAN, JAPANESE)"
sourceUrl:
type: string
description: URL of the original menu source
YouTubeDailyViewsResponse:
type: object
properties:
success:
type: boolean
example: true
videoId:
type: string
description: The YouTube video ID
dateRange:
type: object
properties:
startDate:
type: string
format: date
endDate:
type: string
format: date
totalViews:
type: integer
description: Sum of views across all days in the range
dailyViews:
type: array
items:
type: object
properties:
date:
type: string
format: date
views:
type: integer
estimatedMinutesWatched:
type: number
averageViewDuration:
type: number
description: Average view duration in seconds
subscribersGained:
type: integer
subscribersLost:
type: integer
likes:
type: integer
comments:
type: integer
shares:
type: integer
lastSyncedAt:
type: string
format: date-time
nullable: true
description: When the data was last synced from YouTube
scopeStatus:
type: object
properties:
hasAnalyticsScope:
type: boolean
YouTubeScopeMissingResponse:
type: object
properties:
success:
type: boolean
example: false
error:
type: string
example: "To access daily video analytics, please reconnect your YouTube account to grant the required permissions."
code:
type: string
example: youtube_analytics_scope_missing
scopeStatus:
type: object
properties:
hasAnalyticsScope:
type: boolean
example: false
requiresReauthorization:
type: boolean
example: true
reauthorizeUrl:
type: string
format: uri
description: URL to redirect user for reauthorization
Webhook:
type: object
description: Individual webhook configuration for receiving real-time notifications
properties:
_id:
type: string
description: Unique webhook identifier
name:
type: string
description: Webhook name (for identification)
maxLength: 50
url:
type: string
format: uri
description: Webhook endpoint URL
secret:
type: string
description: Secret key for HMAC-SHA256 signature (not returned in responses for security)
events:
type: array
items:
type: string
enum: [post.scheduled, post.published, post.failed, post.partial, account.connected, account.disconnected, message.received, comment.received]
description: Events subscribed to
isActive:
type: boolean
description: Whether webhook delivery is enabled
lastFiredAt:
type: string
format: date-time
description: Timestamp of last successful webhook delivery
failureCount:
type: integer
description: Consecutive delivery failures (resets on success, webhook disabled at 10)
customHeaders:
type: object
additionalProperties:
type: string
description: Custom headers included in webhook requests
WebhookLog:
type: object
description: Webhook delivery log entry
properties:
_id:
type: string
webhookId:
type: string
description: ID of the webhook that was triggered
webhookName:
type: string
description: Name of the webhook that was triggered
event:
type: string
enum: [post.scheduled, post.published, post.failed, post.partial, account.connected, account.disconnected, message.received, comment.received, webhook.test]
url:
type: string
format: uri
status:
type: string
enum: [success, failed]
statusCode:
type: integer
description: HTTP status code from webhook endpoint
requestPayload:
type: object
description: Payload sent to webhook endpoint
responseBody:
type: string
description: Response body from webhook endpoint (truncated to 10KB)
errorMessage:
type: string
description: Error message if delivery failed
attemptNumber:
type: integer
description: Delivery attempt number (max 3 retries)
responseTime:
type: integer
description: Response time in milliseconds
createdAt:
type: string
format: date-time
WebhookPayloadPost:
type: object
description: Webhook payload for post events
properties:
event:
type: string
enum: [post.scheduled, post.published, post.failed, post.partial]
post:
type: object
properties:
id:
type: string
content:
type: string
status:
type: string
scheduledFor:
type: string
format: date-time
publishedAt:
type: string
format: date-time
platforms:
type: array
items:
type: object
properties:
platform:
type: string
status:
type: string
publishedUrl:
type: string
error:
type: string
timestamp:
type: string
format: date-time
WebhookPayloadAccountConnected:
type: object
description: Webhook payload for account connected events
properties:
event:
type: string
enum: [account.connected]
account:
type: object
properties:
accountId:
type: string
description: The account's unique identifier (same as used in /v1/accounts/{accountId})
profileId:
type: string
description: The profile's unique identifier this account belongs to
platform:
type: string
username:
type: string
displayName:
type: string
timestamp:
type: string
format: date-time
WebhookPayloadAccountDisconnected:
type: object
description: Webhook payload for account disconnected events
properties:
event:
type: string
enum: [account.disconnected]
account:
type: object
properties:
accountId:
type: string
description: The account's unique identifier (same as used in /v1/accounts/{accountId})
profileId:
type: string
description: The profile's unique identifier this account belongs to
platform:
type: string
username:
type: string
displayName:
type: string
disconnectionType:
type: string
enum: [intentional, unintentional]
description: Whether the disconnection was intentional (user action) or unintentional (token expired/revoked)
reason:
type: string
description: Human-readable reason for the disconnection
timestamp:
type: string
format: date-time
WebhookPayloadComment:
type: object
description: Webhook payload for comment received events (Instagram, Facebook, Twitter/X, YouTube, LinkedIn, Bluesky, Reddit)
properties:
event:
type: string
enum: [comment.received]
comment:
type: object
properties:
id:
type: string
description: Platform comment ID
postId:
type: string
description: Internal post ID
platformPostId:
type: string
description: Platform's post ID
platform:
type: string
enum: [instagram, facebook, twitter, youtube, linkedin, bluesky, reddit]
text:
type: string
description: Comment text content
author:
type: object
properties:
id:
type: string
description: Author's platform ID
username:
type: string
name:
type: string
picture:
type: string
nullable: true
createdAt:
type: string
format: date-time
isReply:
type: boolean
description: Whether this is a reply to another comment
parentCommentId:
type: string
nullable: true
description: Parent comment ID if this is a reply
post:
type: object
properties:
id:
type: string
description: Internal post ID
platformPostId:
type: string
description: Platform's post ID
account:
type: object
properties:
id:
type: string
description: Social account ID
platform:
type: string
username:
type: string
timestamp:
type: string
format: date-time
WebhookPayloadMessage:
type: object
description: Webhook payload for message received events (DMs from Instagram, Facebook, Telegram, Bluesky, Reddit)
properties:
event:
type: string
enum: [message.received]
message:
type: object
properties:
id:
type: string
description: Internal message ID
conversationId:
type: string
description: Internal conversation ID
platform:
type: string
enum: [instagram, facebook, telegram, bluesky, reddit]
platformMessageId:
type: string
description: Platform's message ID
direction:
type: string
enum: [incoming]
text:
type: string
nullable: true
description: Message text content
attachments:
type: array
items:
type: object
properties:
type:
type: string
description: Attachment type (image, video, file, sticker, audio)
url:
type: string
description: Attachment URL (may expire for Meta platforms)
payload:
type: object
description: Additional attachment metadata
sender:
type: object
properties:
id:
type: string
name:
type: string
username:
type: string
picture:
type: string
instagramProfile:
type: object
nullable: true
description: Instagram profile data for the sender. Only present for Instagram conversations.
properties:
isFollower:
type: boolean
nullable: true
description: Whether the sender follows your Instagram business account
isFollowing:
type: boolean
nullable: true
description: Whether your Instagram business account follows the sender
followerCount:
type: integer
nullable: true
description: The sender's follower count on Instagram
isVerified:
type: boolean
nullable: true
description: Whether the sender is a verified Instagram user
sentAt:
type: string
format: date-time
isRead:
type: boolean
conversation:
type: object
properties:
id:
type: string
platformConversationId:
type: string
participantId:
type: string
participantName:
type: string
participantUsername:
type: string
participantPicture:
type: string
status:
type: string
enum: [active, archived]
account:
type: object
properties:
id:
type: string
description: Social account ID
platform:
type: string
username:
type: string
displayName:
type: string
metadata:
type: object
nullable: true
description: Interactive message metadata (present when message is a quick reply tap, postback button tap, or inline keyboard callback)
properties:
quickReplyPayload:
type: string
description: Payload from a quick reply tap (Meta platforms)
postbackPayload:
type: string
description: Payload from a postback button tap (Meta platforms)
postbackTitle:
type: string
description: Title of the tapped postback button (Meta platforms)
callbackData:
type: string
description: Callback data from an inline keyboard button tap (Telegram)
timestamp:
type: string
format: date-time
PostLog:
type: object
description: Publishing log entry showing details of a post publishing attempt
properties:
_id:
type: string
postId:
oneOf:
- type: string
- type: object
description: Populated post reference
properties:
_id:
type: string
content:
type: string
status:
type: string
userId:
type: string
profileId:
type: string
platform:
type: string
enum: [tiktok, instagram, facebook, youtube, linkedin, twitter, threads, pinterest, reddit, bluesky, googlebusiness, telegram, snapchat]
accountId:
type: string
accountUsername:
type: string
action:
type: string
enum: [publish, retry, media_upload, rate_limit_pause, token_refresh, cancelled]
description: "Type of action logged: publish (initial attempt), retry (after failure), media_upload, rate_limit_pause, token_refresh, cancelled"
status:
type: string
enum: [success, failed, pending, skipped]
statusCode:
type: integer
description: HTTP status code from platform API
endpoint:
type: string
description: Platform API endpoint called
request:
type: object
properties:
contentPreview:
type: string
description: First 200 chars of caption
mediaCount:
type: integer
mediaTypes:
type: array
items:
type: string
mediaUrls:
type: array
items:
type: string
description: URLs of media items sent to platform
scheduledFor:
type: string
format: date-time
rawBody:
type: string
description: Full request body JSON (max 5000 chars)
response:
type: object
properties:
platformPostId:
type: string
description: ID returned by platform on success
platformPostUrl:
type: string
description: URL of published post
errorMessage:
type: string
description: Error message on failure
errorCode:
type: string
description: Platform-specific error code
rawBody:
type: string
description: Full response body JSON (max 5000 chars)
durationMs:
type: integer
description: How long the operation took in milliseconds
attemptNumber:
type: integer
description: Attempt number (1 for first try, 2+ for retries)
createdAt:
type: string
format: date-time
ConnectionLog:
type: object
description: Connection event log showing account connection/disconnection history
properties:
_id:
type: string
userId:
type: string
description: User who owns the connection (may be null for early OAuth failures)
profileId:
type: string
accountId:
type: string
description: The social account ID (present on successful connections and disconnects)
platform:
type: string
enum: [tiktok, instagram, facebook, youtube, linkedin, twitter, threads, pinterest, reddit, bluesky, googlebusiness, telegram, snapchat]
eventType:
type: string
enum: [connect_success, connect_failed, disconnect, reconnect_success, reconnect_failed]
description: "Type of connection event: connect_success, connect_failed, disconnect, reconnect_success, reconnect_failed"
connectionMethod:
type: string
enum: [oauth, credentials, invitation]
description: How the connection was initiated
error:
type: object
description: Error details (present on failed events)
properties:
code:
type: string
description: Error code (e.g., oauth_denied, token_exchange_failed)
message:
type: string
description: Human-readable error message
rawResponse:
type: string
description: Raw error response (truncated to 2000 chars)
success:
type: object
description: Success details (present on successful events)
properties:
displayName:
type: string
username:
type: string
profilePicture:
type: string
permissions:
type: array
items:
type: string
description: OAuth scopes/permissions granted
tokenExpiresAt:
type: string
format: date-time
accountType:
type: string
description: Account type (personal, business, organization)
context:
type: object
description: Additional context about the connection attempt
properties:
isHeadlessMode:
type: boolean
hasCustomRedirectUrl:
type: boolean
isReconnection:
type: boolean
isBYOK:
type: boolean
description: Using bring-your-own-keys
invitationToken:
type: string
connectToken:
type: string
durationMs:
type: integer
description: How long the operation took in milliseconds
metadata:
type: object
description: Additional metadata
createdAt:
type: string
format: date-time
MediaItem:
type: object
description: Media referenced in posts. URLs must be publicly reachable over HTTPS. Use POST /v1/media/presign for uploads up to 5GB. Late auto-compresses images and videos that exceed platform limits (videos over 200 MB may not be compressed).
properties:
type:
type: string
enum: [image, video, gif, document]
url:
type: string
format: uri
title:
type: string
description: Optional title for the media item. Used as the document title for LinkedIn PDF/carousel posts. If omitted, falls back to the post title, then the filename.
filename:
type: string
size:
type: integer
description: Optional file size in bytes
mimeType:
type: string
description: Optional MIME type (e.g. image/jpeg, video/mp4)
thumbnail:
type: string
format: uri
description: Optional thumbnail image URL for videos
instagramThumbnail:
type: string
format: uri
description: Optional custom cover image URL for Instagram Reels
tiktokProcessed:
type: boolean
description: Internal flag indicating the image was resized for TikTok
PlatformTarget:
type: object
properties:
platform:
type: string
example: twitter
description: "Supported values: twitter, threads, instagram, youtube, facebook, linkedin, pinterest, reddit, tiktok, bluesky, googlebusiness, telegram"
accountId:
oneOf:
- type: string
- $ref: '#/components/schemas/SocialAccount'
customContent:
type: string
description: Platform-specific text override. When set, this content is used instead of the top-level post content for this platform. Useful for tailoring captions per platform (e.g. keeping tweets under 280 characters).
customMedia:
type: array
items:
$ref: '#/components/schemas/MediaItem'
scheduledFor:
type: string
format: date-time
description: Optional per-platform scheduled time override (uses post.scheduledFor when omitted)
platformSpecificData:
description: Platform-specific overrides and options.
oneOf:
- $ref: '#/components/schemas/TwitterPlatformData'
- $ref: '#/components/schemas/ThreadsPlatformData'
- $ref: '#/components/schemas/FacebookPlatformData'
- $ref: '#/components/schemas/InstagramPlatformData'
- $ref: '#/components/schemas/LinkedInPlatformData'
- $ref: '#/components/schemas/PinterestPlatformData'
- $ref: '#/components/schemas/YouTubePlatformData'
- $ref: '#/components/schemas/GoogleBusinessPlatformData'
- $ref: '#/components/schemas/TikTokPlatformData'
- $ref: '#/components/schemas/TelegramPlatformData'
- $ref: '#/components/schemas/SnapchatPlatformData'
- $ref: '#/components/schemas/RedditPlatformData'
- $ref: '#/components/schemas/BlueskyPlatformData'
additionalProperties: true
status:
type: string
example: pending
description: "Platform-specific status: pending, publishing, published, failed"
platformPostId:
type: string
description: The native post ID on the platform (populated after successful publish)
example: "1234567890123456789"
platformPostUrl:
type: string
format: uri
description: Public URL of the published post. Included in the response for immediate posts; for scheduled posts, fetch via GET /v1/posts/{postId} after publish time.
example: "https://twitter.com/acmecorp/status/1234567890123456789"
publishedAt:
type: string
format: date-time
description: Timestamp when the post was published to this platform
errorMessage:
type: string
description: Human-readable error message when status is failed. Contains platform-specific error details explaining why the publish failed.
errorCategory:
type: string
enum: [auth_expired, user_content, user_abuse, account_issue, platform_rejected, platform_error, system_error, unknown]
description: "Error category for programmatic handling: auth_expired (token expired/revoked), user_content (wrong format/too long), user_abuse (rate limits/spam), account_issue (config problems), platform_rejected (policy violation), platform_error (5xx/maintenance), system_error (Late infra), unknown"
errorSource:
type: string
enum: [user, platform, system]
description: "Who caused the error: user (fix content/reconnect), platform (outage/API change), system (Late issue, rare)"
Post:
type: object
properties:
_id: { type: string }
userId:
oneOf:
- type: string
- $ref: '#/components/schemas/User'
title:
type: string
description: |
YouTube: title must be ≤ 100 characters.
content: { type: string }
mediaItems:
type: array
items: { $ref: '#/components/schemas/MediaItem' }
platforms:
type: array
items: { $ref: '#/components/schemas/PlatformTarget' }
scheduledFor: { type: string, format: date-time }
timezone: { type: string }
status: { type: string, enum: [draft, scheduled, publishing, published, failed, partial] }
tags:
type: array
description: "YouTube constraints: each tag max 100 chars, combined max 500 chars, duplicates removed."
items: { type: string }
hashtags:
type: array
items: { type: string }
mentions:
type: array
items: { type: string }
visibility: { type: string, enum: [public, private, unlisted] }
metadata:
type: object
additionalProperties: true
queuedFromProfile:
type: string
description: Profile ID if the post was scheduled via the queue
queueId:
type: string
description: Queue ID if the post was scheduled via a specific queue
createdAt: { type: string, format: date-time }
updatedAt: { type: string, format: date-time }
TwitterPlatformData:
type: object
properties:
replySettings:
type: string
enum: [following, mentionedUsers, subscribers]
description: Controls who can reply to the tweet. "following" allows only people you follow, "mentionedUsers" allows only mentioned users, "subscribers" allows only subscribers. Omit for default (everyone can reply). For threads, applies to the first tweet only.
threadItems:
type: array
description: Sequence of tweets in a thread. First item is the root tweet.
items:
type: object
properties:
content: { type: string }
mediaItems:
type: array
items: { $ref: '#/components/schemas/MediaItem' }
ThreadsPlatformData:
type: object
properties:
threadItems:
type: array
description: Sequence of posts in a Threads thread (root then replies in order).
items:
type: object
properties:
content: { type: string }
mediaItems:
type: array
items: { $ref: '#/components/schemas/MediaItem' }
description: Up to 10 images per carousel (no videos). Videos must be H.264/AAC MP4, max 5 min. Images JPEG/PNG, max 8 MB. Use threadItems for reply chains.
FacebookPlatformData:
type: object
properties:
contentType:
type: string
enum: [story, reel]
description: Set to 'story' for Page Stories (24h ephemeral) or 'reel' for Reels (short vertical video). Defaults to feed post if omitted.
title:
type: string
description: Reel title (only for contentType=reel). Separate from the caption/content field.
firstComment:
type: string
description: Optional first comment to post immediately after publishing (feed posts only, not stories or reels)
pageId:
type: string
description: Target Facebook Page ID for multi-page posting. If omitted, uses the default page. Use GET /v1/accounts/{id}/facebook-page to list pages.
description: Feed posts support up to 10 images (no mixed video+image). Stories require single media (24h, no captions). Reels require single vertical video (9:16, 3-60s).
InstagramPlatformData:
type: object
properties:
contentType:
type: string
enum: [story]
description: Set to 'story' to publish as a Story. Default posts become Reels or feed depending on media.
shareToFeed:
type: boolean
default: true
description: For Reels only. When true (default), the Reel appears on both the Reels tab and your main profile feed. Set to false to post to the Reels tab only.
collaborators:
type: array
items: { type: string }
description: Up to 3 Instagram usernames to invite as collaborators (feed/Reels only)
firstComment:
type: string
description: Optional first comment to add after the post is created (not applied to Stories)
trialParams:
type: object
description: Trial Reels configuration. Trial reels are shared to non-followers first and can later be graduated to regular reels manually or automatically based on performance. Only applies to Reels.
properties:
graduationStrategy:
type: string
enum: [MANUAL, SS_PERFORMANCE]
description: "MANUAL (graduate from Instagram app) or SS_PERFORMANCE (auto-graduate if performs well with non-followers)"
userTags:
type: array
description: Tag Instagram users in photos by username and position. Not supported for stories or videos. For carousels, use mediaIndex to target specific slides (defaults to 0). Tags on video items are silently skipped.
items:
type: object
required: [username, x, y]
properties:
username:
type: string
description: Instagram username (@ symbol is optional and will be removed automatically)
example: friend_username
x:
type: number
minimum: 0
maximum: 1
description: X coordinate position from left edge (0.0 = left, 0.5 = center, 1.0 = right)
example: 0.5
y:
type: number
minimum: 0
maximum: 1
description: Y coordinate position from top edge (0.0 = top, 0.5 = center, 1.0 = bottom)
example: 0.5
mediaIndex:
type: integer
minimum: 0
description: Zero-based index of the carousel item to tag. Defaults to 0. Tags on video items or out-of-range indices are ignored.
example: 0
audioName:
type: string
description: Custom name for original audio in Reels. Replaces the default "Original Audio" label. Can only be set once.
example: "My Podcast Intro"
thumbOffset:
type: integer
minimum: 0
description: Millisecond offset from video start for the Reel thumbnail. Ignored if a custom thumbnail URL is provided. Defaults to 0.
example: 5000
description: Feed aspect ratio 0.8-1.91, carousels up to 10 items, stories require media (no captions). User tag coordinates 0.0-1.0 from top-left. Images over 8 MB and videos over platform limits are auto-compressed.
LinkedInPlatformData:
type: object
properties:
documentTitle:
type: string
description: Title displayed on LinkedIn document (PDF/carousel) posts. Required by LinkedIn for document posts. If omitted, falls back to the media item title, then the filename.
organizationUrn:
type: string
description: Target LinkedIn Organization URN (e.g. "urn:li:organization:123456789"). If omitted, uses the default org. Use GET /v1/accounts/{id}/linkedin-organizations to list orgs.
firstComment:
type: string
description: Optional first comment to add after the post is created
disableLinkPreview:
type: boolean
description: Set to true to disable automatic link previews for URLs in the post content (default is false)
description: Up to 20 images, no multi-video. Single PDF supported (max 100MB). Link previews auto-generated when no media attached. Use organizationUrn for multi-org posting.
PinterestPlatformData:
type: object
properties:
title:
type: string
maxLength: 100
description: Pin title. Defaults to first line of content or "Pin". Must be ≤ 100 characters.
boardId:
type: string
description: Target Pinterest board ID. If omitted, the first available board is used.
link:
type: string
format: uri
description: Destination link (pin URL)
coverImageUrl:
type: string
format: uri
description: Optional cover image for video pins
coverImageKeyFrameTime:
type: integer
description: Optional key frame time in seconds for derived video cover
YouTubePlatformData:
type: object
properties:
title:
type: string
maxLength: 100
description: Video title. Defaults to first line of content or "Untitled Video". Must be ≤ 100 characters.
visibility:
type: string
enum: [public, private, unlisted]
default: public
description: "Video visibility: public (default, anyone can watch), unlisted (link only), private (invite only)"
madeForKids:
type: boolean
default: false
description: COPPA compliance flag. Set true for child-directed content (restricts comments, notifications, ad targeting). Defaults to false. YouTube may block views if not explicitly set.
firstComment:
type: string
maxLength: 10000
description: Optional first comment to post immediately after video upload. Up to 10,000 characters (YouTube's comment limit).
containsSyntheticMedia:
type: boolean
default: false
description: AI-generated content disclosure. Set true if the video contains synthetic content that could be mistaken for real. YouTube may add a label.
categoryId:
type: string
default: '22'
description: "YouTube video category ID. Defaults to 22 (People & Blogs). Common: 1 (Film), 2 (Autos), 10 (Music), 15 (Pets), 17 (Sports), 20 (Gaming), 23 (Comedy), 24 (Entertainment), 25 (News), 26 (Howto), 27 (Education), 28 (Science & Tech)."
description: Videos under 3 min auto-detected as Shorts. Custom thumbnails for regular videos only. Scheduled videos are uploaded immediately with the specified visibility.
GoogleBusinessPlatformData:
type: object
properties:
locationId:
type: string
description: Target GBP location ID (e.g. "locations/123456789"). If omitted, uses the default location. Use GET /v1/accounts/{id}/gmb-locations to list locations.
languageCode:
type: string
description: BCP 47 language code (e.g. "en", "de", "es"). Auto-detected if omitted. Set explicitly for short or mixed-language posts.
example: "de"
callToAction:
type: object
description: Optional call-to-action button displayed on the post
properties:
type:
type: string
enum: [LEARN_MORE, BOOK, ORDER, SHOP, SIGN_UP, CALL]
description: "Button action type: LEARN_MORE, BOOK, ORDER, SHOP, SIGN_UP, CALL"
url:
type: string
format: uri
description: Destination URL for the CTA button (required when callToAction is provided)
required: [type, url]
description: Text and single image only (no videos). Optional call-to-action button. Posts appear on GBP, Google Search, and Maps. Use locationId for multi-location posting.
TikTokPlatformData:
type: object
description: Photo carousels up to 35 images. Video titles up to 2200 chars, photo titles truncated to 90 chars. privacyLevel must match creator_info options. Both camelCase and snake_case accepted.
properties:
draft:
type: boolean
description: When true, sends the post to the TikTok Creator Inbox as a draft instead of publishing immediately.
privacyLevel:
type: string
description: One of the values returned by the TikTok creator info API for the account
allowComment:
type: boolean
description: Allow comments on the post
allowDuet:
type: boolean
description: Allow duets (required for video posts)
allowStitch:
type: boolean
description: Allow stitches (required for video posts)
commercialContentType:
type: string
enum: [none, brand_organic, brand_content]
description: Type of commercial content disclosure
brandPartnerPromote:
type: boolean
description: Whether the post promotes a brand partner
isBrandOrganicPost:
type: boolean
description: Whether the post is a brand organic post
contentPreviewConfirmed:
type: boolean
description: User has confirmed they previewed the content
expressConsentGiven:
type: boolean
description: User has given express consent for posting
mediaType:
type: string
enum: [video, photo]
description: Optional override. Defaults based on provided media items.
videoCoverTimestampMs:
type: integer
description: Optional for video posts. Timestamp in milliseconds to select which frame to use as thumbnail (defaults to 1000ms/1 second).
minimum: 0
photoCoverIndex:
type: integer
description: Optional for photo carousels. Index of image to use as cover, 0-based (defaults to 0/first image).
minimum: 0
autoAddMusic:
type: boolean
description: When true, TikTok may add recommended music (photos only)
videoMadeWithAi:
type: boolean
description: Set true to disclose AI-generated content
description:
type: string
maxLength: 4000
description: Optional long-form description for photo posts (max 4000 chars). Recommended when content exceeds 90 chars, as photo titles are auto-truncated.
TelegramPlatformData:
type: object
properties:
parseMode:
type: string
enum: [HTML, Markdown, MarkdownV2]
description: Text formatting mode for the message (default is HTML)
disableWebPagePreview:
type: boolean
description: Disable link preview generation for URLs in the message
disableNotification:
type: boolean
description: Send the message silently (users will receive notification without sound)
protectContent:
type: boolean
description: Protect message content from forwarding and saving
description: Text, images (up to 10), videos (up to 10), and mixed media albums. Captions up to 1024 chars for media, 4096 for text-only.
SnapchatPlatformData:
type: object
properties:
contentType:
type: string
enum: [story, saved_story, spotlight]
default: story
description: "Content type: story (ephemeral 24h, default), saved_story (permanent on Public Profile), spotlight (video feed)"
description: "Requires a Public Profile. Single media item only. Content types: story (ephemeral 24h), saved_story (permanent, title max 45 chars), spotlight (video, max 160 chars)."
RedditPlatformData:
type: object
properties:
subreddit:
type: string
description: Target subreddit name (without "r/" prefix). Overrides the default. Use GET /v1/accounts/{id}/reddit-subreddits to list options.
example: socialmedia
title:
type: string
maxLength: 300
description: Post title. Defaults to the first line of content, truncated to 300 characters.
url:
type: string
format: uri
description: URL for link posts. If provided (and forceSelf is not true), creates a link post instead of a text post.
forceSelf:
type: boolean
description: When true, creates a text/self post even when a URL or media is provided.
flairId:
type: string
description: Flair ID for the post. Required by some subreddits. Use GET /v1/accounts/{id}/reddit-flairs?subreddit=name to list flairs.
example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
description: Posts are either link (with URL/media) or self (text-only). Use forceSelf to override. Subreddit defaults to the account's configured one. Some subreddits require a flair.
BlueskyPlatformData:
type: object
properties:
threadItems:
type: array
description: Sequence of posts in a Bluesky thread (root then replies in order).
items:
type: object
properties:
content:
type: string
mediaItems:
type: array
items:
$ref: '#/components/schemas/MediaItem'
description: |
Bluesky post settings. Supports text posts with up to 4 images or a single video. threadItems creates a reply chain (Bluesky thread). Images exceeding 1MB are automatically compressed. Alt text supported via mediaItem properties.
QueueSlot:
type: object
properties:
dayOfWeek:
type: integer
description: Day of week (0=Sunday, 6=Saturday)
minimum: 0
maximum: 6
time:
type: string
description: Time in HH:mm format (24-hour)
pattern: '^([0-1][0-9]|2[0-3]):[0-5][0-9]$'
QueueSchedule:
type: object
properties:
_id:
type: string
description: Unique queue identifier
profileId:
type: string
description: Profile ID this queue belongs to
name:
type: string
description: Queue name (e.g., "Morning Posts", "Evening Content")
timezone:
type: string
description: IANA timezone (e.g., America/New_York)
slots:
type: array
items:
$ref: '#/components/schemas/QueueSlot'
active:
type: boolean
description: Whether the queue is active
isDefault:
type: boolean
description: Whether this is the default queue for the profile (used when no queueId specified)
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
Pagination:
type: object
properties:
page: { type: integer }
limit: { type: integer }
total: { type: integer }
pages: { type: integer }
Profile:
type: object
properties:
_id: { type: string }
userId: { type: string }
name: { type: string }
description: { type: string }
color: { type: string }
isDefault: { type: boolean }
isOverLimit:
type: boolean
description: Only present when includeOverLimit=true. Indicates if this profile exceeds the plan limit.
createdAt: { type: string, format: date-time }
SocialAccount:
type: object
properties:
_id: { type: string }
platform: { type: string }
profileId:
oneOf:
- type: string
- $ref: '#/components/schemas/Profile'
username: { type: string }
displayName: { type: string }
profileUrl:
type: string
description: Full profile URL for the connected account on its platform.
isActive: { type: boolean }
followersCount:
type: number
description: Follower count (only included if user has analytics add-on)
followersLastUpdated:
type: string
format: date-time
description: Last time follower count was updated (only included if user has analytics add-on)
AccountWithFollowerStats:
allOf:
- $ref: '#/components/schemas/SocialAccount'
- type: object
properties:
profilePicture: { type: string }
currentFollowers: { type: number, description: Current follower count }
lastUpdated: { type: string, format: date-time }
growth: { type: number, description: Follower change over period }
growthPercentage: { type: number, description: Percentage growth }
dataPoints: { type: number, description: Number of historical snapshots }
ApiKey:
type: object
properties:
id: { type: string }
name: { type: string }
keyPreview: { type: string }
expiresAt: { type: string, format: date-time }
createdAt: { type: string, format: date-time }
key:
type: string
description: Returned only once, on creation
scope:
type: string
enum: [full, profiles]
description: "'full' grants access to all profiles, 'profiles' restricts to specific profiles"
default: full
profileIds:
type: array
items:
type: object
properties:
_id: { type: string }
name: { type: string }
color: { type: string }
description: Profiles this key can access (populated with name and color). Only present when scope is 'profiles'.
permission:
type: string
enum: [read-write, read]
description: "'read-write' allows all operations, 'read' restricts to GET requests only"
default: read-write
UsageStats:
type: object
properties:
planName: { type: string }
billingPeriod: { type: string, enum: [monthly, yearly] }
signupDate: { type: string, format: date-time }
limits:
type: object
properties:
uploads: { type: integer }
profiles: { type: integer }
usage:
type: object
properties:
uploads: { type: integer }
profiles: { type: integer }
lastReset: { type: string, format: date-time }
PostAnalytics:
type: object
properties:
impressions: { type: integer, example: 0 }
reach: { type: integer, example: 0 }
likes: { type: integer, example: 0 }
comments: { type: integer, example: 0 }
shares: { type: integer, example: 0 }
saves: { type: integer, example: 0, description: 'Number of saves/bookmarks (Instagram, Pinterest)' }
clicks: { type: integer, example: 0 }
views: { type: integer, example: 0 }
engagementRate: { type: number, example: 0 }
lastUpdated: { type: string, format: date-time }
PlatformAnalytics:
type: object
properties:
platform: { type: string }
status: { type: string }
accountId: { type: string }
accountUsername: { type: string }
analytics:
$ref: '#/components/schemas/PostAnalytics'
accountMetrics:
type: object
nullable: true
properties:
followers: { type: integer, nullable: true, description: 'Followers/fans count (e.g., Instagram, Facebook Pages, Twitter)' }
subscribers: { type: integer, nullable: true, description: 'Subscribers count (e.g., YouTube)' }
lastUpdated: { type: string, format: date-time, nullable: true }
AnalyticsOverview:
type: object
properties:
totalPosts: { type: integer }
publishedPosts: { type: integer }
scheduledPosts: { type: integer }
lastSync: { type: string, format: date-time }
AnalyticsSinglePostResponse:
type: object
properties:
postId: { type: string }
status: { type: string }
content: { type: string }
scheduledFor: { type: string, format: date-time }
publishedAt: { type: string, format: date-time }
analytics:
$ref: '#/components/schemas/PostAnalytics'
platformAnalytics:
type: array
items:
$ref: '#/components/schemas/PlatformAnalytics'
platform: { type: string }
platformPostUrl: { type: string, format: uri }
isExternal: { type: boolean }
AnalyticsListResponse:
type: object
properties:
overview:
$ref: '#/components/schemas/AnalyticsOverview'
posts:
type: array
items:
type: object
properties:
_id: { type: string }
content: { type: string }
scheduledFor: { type: string, format: date-time }
publishedAt: { type: string, format: date-time }
status: { type: string }
analytics:
$ref: '#/components/schemas/PostAnalytics'
platforms:
type: array
items:
$ref: '#/components/schemas/PlatformAnalytics'
platform: { type: string }
platformPostUrl: { type: string, format: uri }
isExternal: { type: boolean }
thumbnailUrl: { type: string, format: uri }
mediaType: { type: string, enum: [image, video, gif, document] }
mediaItems:
type: array
items:
$ref: '#/components/schemas/MediaItem'
pagination:
$ref: '#/components/schemas/Pagination'
accounts:
type: array
description: Connected social accounts (followerCount and followersLastUpdated only included if user has analytics add-on)
items:
$ref: '#/components/schemas/SocialAccount'
hasAnalyticsAccess:
type: boolean
description: Whether user has analytics add-on access
LinkedInAggregateAnalyticsTotalResponse:
type: object
description: Response for TOTAL aggregation (lifetime totals)
properties:
accountId: { type: string }
platform: { type: string, example: linkedin }
accountType: { type: string, example: personal }
username: { type: string }
aggregation: { type: string, enum: [TOTAL] }
dateRange:
type: object
nullable: true
properties:
startDate: { type: string, format: date }
endDate: { type: string, format: date }
analytics:
type: object
properties:
impressions: { type: integer, description: Total impressions across all posts }
reach: { type: integer, description: Unique members reached across all posts }
reactions: { type: integer, description: Total reactions across all posts }
comments: { type: integer, description: Total comments across all posts }
shares: { type: integer, description: Total reshares across all posts }
engagementRate: { type: number, description: Overall engagement rate as percentage }
note: { type: string }
lastUpdated: { type: string, format: date-time }
LinkedInAggregateAnalyticsDailyResponse:
type: object
description: Response for DAILY aggregation (time series breakdown)
properties:
accountId: { type: string }
platform: { type: string, example: linkedin }
accountType: { type: string, example: personal }
username: { type: string }
aggregation: { type: string, enum: [DAILY] }
dateRange:
type: object
nullable: true
properties:
startDate: { type: string, format: date }
endDate: { type: string, format: date }
analytics:
type: object
description: Daily breakdown of each metric as date/count pairs. Reach not available with DAILY aggregation.
properties:
impressions:
type: array
items:
type: object
properties:
date: { type: string, format: date }
count: { type: integer }
reactions:
type: array
items:
type: object
properties:
date: { type: string, format: date }
count: { type: integer }
comments:
type: array
items:
type: object
properties:
date: { type: string, format: date }
count: { type: integer }
shares:
type: array
items:
type: object
properties:
date: { type: string, format: date }
count: { type: integer }
skippedMetrics:
type: array
description: Metrics that were skipped due to API limitations
items: { type: string }
note: { type: string }
lastUpdated: { type: string, format: date-time }
PostsListResponse:
type: object
properties:
posts:
type: array
items:
$ref: '#/components/schemas/Post'
pagination:
$ref: '#/components/schemas/Pagination'
PostGetResponse:
type: object
properties:
post:
$ref: '#/components/schemas/Post'
PostCreateResponse:
type: object
properties:
message:
type: string
post:
$ref: '#/components/schemas/Post'
PostUpdateResponse:
type: object
properties:
message:
type: string
post:
$ref: '#/components/schemas/Post'
PostDeleteResponse:
type: object
properties:
message:
type: string
PostRetryResponse:
type: object
properties:
message:
type: string
post:
$ref: '#/components/schemas/Post'
ProfilesListResponse:
type: object
properties:
profiles:
type: array
items:
$ref: '#/components/schemas/Profile'
ProfileGetResponse:
type: object
properties:
profile:
$ref: '#/components/schemas/Profile'
ProfileCreateResponse:
type: object
properties:
message:
type: string
profile:
$ref: '#/components/schemas/Profile'
ProfileUpdateResponse:
type: object
properties:
message:
type: string
profile:
$ref: '#/components/schemas/Profile'
ProfileDeleteResponse:
type: object
properties:
message:
type: string
AccountsListResponse:
type: object
properties:
accounts:
type: array
items:
$ref: '#/components/schemas/SocialAccount'
hasAnalyticsAccess:
type: boolean
description: Whether user has analytics add-on access
AccountGetResponse:
type: object
properties:
account:
$ref: '#/components/schemas/SocialAccount'
FollowerStatsResponse:
type: object
properties:
accounts:
type: array
items:
$ref: '#/components/schemas/AccountWithFollowerStats'
dateRange:
type: object
properties:
from:
type: string
format: date-time
to:
type: string
format: date-time
aggregation:
type: string
enum: [daily, weekly, monthly]
UploadedFile:
type: object
properties:
type:
type: string
enum: [image, video, document]
url:
type: string
format: uri
filename:
type: string
size:
type: integer
mimeType:
type: string
MediaUploadResponse:
type: object
properties:
files:
type: array
items:
$ref: '#/components/schemas/UploadedFile'
UploadTokenResponse:
type: object
properties:
token:
type: string
uploadUrl:
type: string
format: uri
expiresAt:
type: string
format: date-time
status:
type: string
enum: [pending, completed, expired]
UploadTokenStatusResponse:
type: object
properties:
token:
type: string
status:
type: string
enum: [pending, completed, expired]
files:
type: array
items:
$ref: '#/components/schemas/UploadedFile'
createdAt:
type: string
format: date-time
expiresAt:
type: string
format: date-time
completedAt:
type: string
format: date-time
QueueSlotsResponse:
type: object
properties:
exists:
type: boolean
schedule:
$ref: '#/components/schemas/QueueSchedule'
nextSlots:
type: array
items:
type: string
format: date-time
QueueUpdateResponse:
type: object
properties:
success:
type: boolean
schedule:
$ref: '#/components/schemas/QueueSchedule'
nextSlots:
type: array
items:
type: string
format: date-time
reshuffledCount:
type: integer
QueueDeleteResponse:
type: object
properties:
success:
type: boolean
deleted:
type: boolean
QueuePreviewResponse:
type: object
properties:
profileId:
type: string
count:
type: integer
slots:
type: array
items:
type: string
format: date-time
QueueNextSlotResponse:
type: object
properties:
profileId:
type: string
nextSlot:
type: string
format: date-time
timezone:
type: string
DownloadFormat:
type: object
properties:
formatId:
type: string
ext:
type: string
resolution:
type: string
filesize:
type: integer
quality:
type: string
DownloadResponse:
type: object
properties:
url:
type: string
format: uri
title:
type: string
thumbnail:
type: string
format: uri
duration:
type: integer
formats:
type: array
items:
$ref: '#/components/schemas/DownloadFormat'
TranscriptSegment:
type: object
properties:
text:
type: string
start:
type: number
duration:
type: number
TranscriptResponse:
type: object
properties:
transcript:
type: string
segments:
type: array
items:
$ref: '#/components/schemas/TranscriptSegment'
language:
type: string
HashtagInfo:
type: object
properties:
hashtag:
type: string
status:
type: string
enum: [safe, banned, restricted, unknown]
postCount:
type: integer
HashtagCheckResponse:
type: object
properties:
hashtags:
type: array
items:
$ref: '#/components/schemas/HashtagInfo'
CaptionResponse:
type: object
properties:
caption:
type: string
User:
type: object
properties:
_id:
type: string
email:
type: string
name:
type: string
role:
type: string
createdAt:
type: string
format: date-time
UsersListResponse:
type: object
properties:
users:
type: array
items:
$ref: '#/components/schemas/User'
UserGetResponse:
type: object
properties:
user:
$ref: '#/components/schemas/User'
security:
- bearerAuth: []
paths:
/v1/tools/youtube/download:
get:
operationId: downloadYouTubeVideo
tags: [Tools]
summary: Download YouTube video
description: |
Download YouTube videos or audio. Returns available formats or direct download URL.
Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited).
security:
- bearerAuth: []
parameters:
- name: url
in: query
required: true
description: YouTube video URL or video ID
schema:
type: string
example: "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
- name: action
in: query
description: "Action to perform: 'download' returns download URL, 'formats' lists available formats"
schema:
type: string
enum: [download, formats]
default: download
- name: format
in: query
description: Desired format (when action=download)
schema:
type: string
enum: [video, audio]
default: video
- name: quality
in: query
description: Desired quality (when action=download)
schema:
type: string
enum: [hd, sd]
default: hd
- name: formatId
in: query
description: Specific format ID from formats list
schema:
type: string
responses:
"200":
description: Success
headers:
X-RateLimit-Limit:
schema: { type: string }
description: Daily rate limit
X-RateLimit-Remaining:
schema: { type: string }
description: Remaining calls today
X-RateLimit-Reset:
schema: { type: string }
description: Unix timestamp when limit resets
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
title: { type: string }
downloadUrl: { type: string, format: uri }
formats:
type: array
items:
type: object
properties:
id: { type: string }
label: { type: string }
ext: { type: string }
type: { type: string }
height: { type: integer }
width: { type: integer }
"401":
$ref: '#/components/responses/Unauthorized'
"403":
description: Tools API not available on free plan
"429":
description: Daily rate limit exceeded
/v1/tools/youtube/transcript:
get:
operationId: getYouTubeTranscript
tags: [Tools]
summary: Get YouTube transcript
description: |
Extract transcript/captions from a YouTube video.
Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited).
security:
- bearerAuth: []
parameters:
- name: url
in: query
required: true
description: YouTube video URL or video ID
schema:
type: string
- name: lang
in: query
description: Language code for transcript
schema:
type: string
default: en
responses:
"200":
description: Success
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
videoId: { type: string }
language: { type: string }
fullText: { type: string }
segments:
type: array
items:
type: object
properties:
text: { type: string }
start: { type: number }
duration: { type: number }
"404":
description: No transcript available
/v1/tools/instagram/download:
get:
operationId: downloadInstagramMedia
tags: [Tools]
summary: Download Instagram media
description: |
Download Instagram reels, posts, or photos.
Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited).
security:
- bearerAuth: []
parameters:
- name: url
in: query
required: true
description: Instagram reel or post URL
schema:
type: string
example: "https://www.instagram.com/reel/ABC123/"
responses:
"200":
description: Success
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
title: { type: string }
downloadUrl: { type: string, format: uri }
/v1/tools/instagram/hashtag-checker:
post:
operationId: checkInstagramHashtags
tags: [Tools]
summary: Check IG hashtag bans
description: |
Check if Instagram hashtags are banned, restricted, or safe to use.
Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited).
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [hashtags]
properties:
hashtags:
type: array
maxItems: 20
items:
type: string
example: ["travel", "followforfollow", "fitness"]
responses:
"200":
description: Success
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
results:
type: array
items:
type: object
properties:
hashtag: { type: string }
status:
type: string
enum: [banned, restricted, safe, unknown]
reason: { type: string }
confidence: { type: number }
summary:
type: object
properties:
banned: { type: integer }
restricted: { type: integer }
safe: { type: integer }
/v1/tools/tiktok/download:
get:
operationId: downloadTikTokVideo
tags: [Tools]
summary: Download TikTok video
description: |
Download TikTok videos with or without watermark.
Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited).
security:
- bearerAuth: []
parameters:
- name: url
in: query
required: true
description: TikTok video URL or ID
schema:
type: string
- name: action
in: query
description: "'formats' to list available formats"
schema:
type: string
enum: [download, formats]
default: download
- name: formatId
in: query
description: Specific format ID (0 = no watermark, etc.)
schema:
type: string
responses:
"200":
description: Success
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
title: { type: string }
downloadUrl: { type: string, format: uri }
formats:
type: array
items:
type: object
properties:
id: { type: string }
label: { type: string }
ext: { type: string }
/v1/tools/twitter/download:
get:
operationId: downloadTwitterMedia
tags: [Tools]
summary: Download Twitter/X media
description: |
Download videos from Twitter/X posts.
Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited).
security:
- bearerAuth: []
parameters:
- name: url
in: query
required: true
description: Twitter/X post URL
schema:
type: string
example: "https://x.com/user/status/123456789"
- name: action
in: query
schema:
type: string
enum: [download, formats]
default: download
- name: formatId
in: query
schema:
type: string
responses:
"200":
description: Success
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
title: { type: string }
downloadUrl: { type: string, format: uri }
/v1/tools/facebook/download:
get:
operationId: downloadFacebookVideo
tags: [Tools]
summary: Download Facebook video
description: |
Download videos and reels from Facebook.
Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited).
security:
- bearerAuth: []
parameters:
- name: url
in: query
required: true
description: Facebook video or reel URL
schema:
type: string
responses:
"200":
description: Success
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
title: { type: string }
downloadUrl: { type: string, format: uri }
thumbnail: { type: string, format: uri }
/v1/tools/linkedin/download:
get:
operationId: downloadLinkedInVideo
tags: [Tools]
summary: Download LinkedIn video
description: |
Download videos from LinkedIn posts.
Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited).
security:
- bearerAuth: []
parameters:
- name: url
in: query
required: true
description: LinkedIn post URL
schema:
type: string
responses:
"200":
description: Success
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
title: { type: string }
downloadUrl: { type: string, format: uri }
/v1/tools/bluesky/download:
get:
operationId: downloadBlueskyMedia
tags: [Tools]
summary: Download Bluesky media
description: |
Download videos from Bluesky posts.
Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited).
security:
- bearerAuth: []
parameters:
- name: url
in: query
required: true
description: Bluesky post URL
schema:
type: string
example: "https://bsky.app/profile/user.bsky.social/post/abc123"
responses:
"200":
description: Success
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
title: { type: string }
text: { type: string }
downloadUrl: { type: string, format: uri }
thumbnail: { type: string, format: uri }
/v1/tools/validate/post-length:
post:
operationId: validatePostLength
tags: [Validate]
summary: Validate post character count
description: |
Check weighted character count per platform and whether the text is within each platform's limit.
Twitter/X uses weighted counting (URLs = 23 chars via t.co, emojis = 2 chars). All other platforms use plain character length.
Returns counts and limits for all 15 supported platform variants.
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [text]
properties:
text:
type: string
description: The post text to check
example: "Check out https://getlate.dev for scheduling posts!"
responses:
"200":
description: Character counts per platform
content:
application/json:
schema:
type: object
properties:
text: { type: string }
platforms:
type: object
additionalProperties:
type: object
properties:
count: { type: integer, description: "Character count for this platform" }
limit: { type: integer, description: "Maximum allowed characters" }
valid: { type: boolean, description: "Whether the text is within the limit" }
example:
twitter: { count: 51, limit: 280, valid: true }
twitterPremium: { count: 51, limit: 25000, valid: true }
instagram: { count: 51, limit: 2200, valid: true }
bluesky: { count: 51, limit: 300, valid: true }
snapchat: { count: 51, limit: 160, valid: true }
/v1/tools/validate/post:
post:
operationId: validatePost
tags: [Validate]
summary: Validate post content
description: |
Dry-run the full post validation pipeline without publishing. Catches issues like missing media for Instagram/TikTok/YouTube, hashtag limits, invalid thread formats, Facebook Reel requirements, and character limit violations.
Accepts the same body as `POST /v1/posts`. Does NOT validate accounts, process media, or track usage. This is content-only validation.
Returns errors for failures and warnings for near-limit content (>90% of character limit).
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [platforms]
properties:
content:
type: string
description: Post text content
example: "Check out this video!"
platforms:
type: array
description: Target platforms (same format as POST /v1/posts)
items:
type: object
required: [platform]
properties:
platform:
type: string
enum: [twitter, instagram, tiktok, youtube, facebook, linkedin, bluesky, threads, reddit, pinterest, telegram, snapchat, googlebusiness]
customContent: { type: string }
platformSpecificData: { type: object }
customMedia:
type: array
items:
type: object
properties:
url: { type: string }
type: { type: string, enum: [image, video] }
example:
- platform: youtube
- platform: twitter
mediaItems:
type: array
description: Root media items shared across platforms
items:
type: object
properties:
url: { type: string, format: uri }
type: { type: string, enum: [image, video] }
responses:
"200":
description: Validation result
content:
application/json:
schema:
oneOf:
- type: object
description: Valid post
properties:
valid: { type: boolean }
message: { type: string, example: "No validation issues found." }
warnings:
type: array
items:
type: object
properties:
platform: { type: string }
warning: { type: string }
- type: object
description: Invalid post
properties:
valid: { type: boolean }
errors:
type: array
items:
type: object
properties:
platform: { type: string }
error: { type: string }
warnings:
type: array
items:
type: object
properties:
platform: { type: string }
warning: { type: string }
/v1/tools/validate/media:
post:
operationId: validateMedia
tags: [Validate]
summary: Validate media URL
description: |
Check if a media URL is accessible and return metadata (content type, file size) plus per-platform size limit comparisons.
Performs a HEAD request (with GET fallback) to detect content type and size. Rejects private/localhost URLs for SSRF protection.
Platform limits are sourced from each platform's actual upload constraints.
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [url]
properties:
url:
type: string
format: uri
description: Public media URL to validate
example: "https://example.com/image.jpg"
responses:
"200":
description: Media validation result
content:
application/json:
schema:
type: object
properties:
valid: { type: boolean }
url: { type: string, format: uri }
error: { type: string, description: "Error message if valid is false" }
contentType: { type: string, example: "image/jpeg" }
size: { type: integer, nullable: true, description: "File size in bytes" }
sizeFormatted: { type: string, example: "245 KB" }
type: { type: string, enum: [image, video, unknown] }
platformLimits:
type: object
description: Per-platform size limit comparison (only present when size and type are known)
additionalProperties:
type: object
properties:
limit: { type: integer, description: "Platform size limit in bytes" }
limitFormatted: { type: string }
withinLimit: { type: boolean }
example:
instagram: { limit: 8388608, limitFormatted: "8.0 MB", withinLimit: true }
twitter: { limit: 5242880, limitFormatted: "5.0 MB", withinLimit: true }
bluesky: { limit: 1000000, limitFormatted: "977 KB", withinLimit: true }
/v1/tools/validate/subreddit:
get:
operationId: validateSubreddit
tags: [Validate]
summary: Check subreddit existence
description: |
Check if a subreddit exists and return basic info (title, subscriber count, NSFW status, post types allowed).
Uses Reddit's public JSON API (no Reddit auth needed). Returns `exists: false` for private, banned, or nonexistent subreddits.
security:
- bearerAuth: []
parameters:
- name: name
in: query
required: true
description: Subreddit name (with or without "r/" prefix)
schema:
type: string
example: "programming"
responses:
"200":
description: Subreddit lookup result
content:
application/json:
schema:
oneOf:
- type: object
description: Subreddit exists
properties:
exists: { type: boolean }
subreddit:
type: object
properties:
name: { type: string, example: "programming" }
title: { type: string, example: "programming" }
description: { type: string, example: "Computer Programming" }
subscribers: { type: integer, example: 6844284 }
isNSFW: { type: boolean }
type: { type: string, enum: [public, private, restricted], example: "public" }
allowImages: { type: boolean }
allowVideos: { type: boolean }
- type: object
description: Subreddit not found
properties:
exists: { type: boolean }
error: { type: string }
/v1/analytics:
get:
operationId: getAnalytics
tags: [Analytics]
summary: Get post analytics
description: |
Returns analytics for posts. With postId, returns a single post. Without it, returns a paginated list with overview stats.
Accepts both Late Post IDs and External Post IDs (auto-resolved). Data is cached and refreshed at most once per hour. For follower stats, use /v1/accounts/follower-stats.
parameters:
- name: postId
in: query
schema: { type: string }
description: Returns analytics for a single post. Accepts both Late Post IDs and External Post IDs. Late IDs are auto-resolved to External Post analytics.
- name: platform
in: query
schema: { type: string }
description: Filter by platform (default "all")
- name: profileId
in: query
schema: { type: string }
description: Filter by profile ID (default "all")
- name: source
in: query
schema: { type: string, enum: [all, late, external], default: all }
description: "Filter by post source: late (posted via Late API), external (synced from platform), all (default)"
- name: fromDate
in: query
schema: { type: string, format: date }
description: Inclusive lower bound
- name: toDate
in: query
schema: { type: string, format: date }
description: Inclusive upper bound
- name: limit
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
description: Page size (default 50)
- name: page
in: query
schema: { type: integer, minimum: 1, default: 1 }
description: Page number (default 1)
- name: sortBy
in: query
schema: { type: string, enum: [date, engagement], default: date }
description: Sort by date or engagement
- name: order
in: query
schema: { type: string, enum: [asc, desc], default: desc }
description: Sort order
responses:
'200':
description: Analytics result
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/AnalyticsSinglePostResponse'
- $ref: '#/components/schemas/AnalyticsListResponse'
examples:
singlePost:
summary: Single post analytics
value:
postId: "65f1c0a9e2b5af0012ab34cd"
latePostId: "65f1c0a9e2b5af0012ab34ab"
status: "published"
content: "Check out our new product launch!"
scheduledFor: "2024-11-01T10:00:00Z"
publishedAt: "2024-11-01T10:00:05Z"
analytics:
impressions: 15420
reach: 12350
likes: 342
comments: 28
shares: 45
clicks: 189
views: 0
engagementRate: 2.78
lastUpdated: "2024-11-02T08:30:00Z"
platformAnalytics:
- platform: "twitter"
status: "published"
accountId: "64e1f0a9e2b5af0012ab34cd"
accountUsername: "@acmecorp"
analytics:
impressions: 15420
reach: 12350
likes: 342
comments: 28
shares: 45
clicks: 189
views: 0
engagementRate: 2.78
lastUpdated: "2024-11-02T08:30:00Z"
platform: "twitter"
platformPostUrl: "https://twitter.com/acmecorp/status/123456789"
isExternal: false
postList:
summary: Paginated analytics list
description: |
Note: The list endpoint returns External Post IDs. Posts originally
scheduled via Late will have isExternal: true in this response.
Use platformPostUrl to correlate with your original Late Post IDs.
value:
overview:
totalPosts: 156
publishedPosts: 142
scheduledPosts: 0
lastSync: "2024-11-02T08:30:00Z"
posts:
- _id: "65f1c0a9e2b5af0012ab34cd"
latePostId: "65f1c0a9e2b5af0012ab34ab"
content: "Check out our new product launch!"
scheduledFor: "2024-11-01T10:00:00Z"
publishedAt: "2024-11-01T10:00:05Z"
status: "published"
analytics:
impressions: 15420
likes: 342
comments: 28
engagementRate: 2.78
lastUpdated: "2024-11-02T08:30:00Z"
platforms:
- platform: "instagram"
status: "published"
analytics:
impressions: 15420
likes: 342
comments: 28
platform: "instagram"
platformPostUrl: "https://www.instagram.com/reel/ABC123xyz/"
isExternal: true
thumbnailUrl: "https://storage.example.com/thumb.jpg"
mediaType: "video"
pagination:
page: 1
limit: 50
total: 156
pages: 4
accounts:
- _id: "64e1f0..."
platform: "twitter"
username: "@acmecorp"
displayName: "Acme Corp"
isActive: true
hasAnalyticsAccess: true
'401': { $ref: '#/components/responses/Unauthorized' }
'402':
description: Analytics add-on required
content:
application/json:
schema:
type: object
properties:
error: { type: string, example: Analytics add-on required }
code: { type: string, example: analytics_addon_required }
'404': { $ref: '#/components/responses/NotFound' }
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/v1/analytics/youtube/daily-views:
get:
operationId: getYouTubeDailyViews
tags: [Analytics]
summary: Get YouTube daily views
description: |
Returns daily view counts for a YouTube video including views, watch time, and subscriber changes.
Requires yt-analytics.readonly scope (re-authorization may be needed). Data has a 2-3 day delay. Max 90 days, defaults to last 30 days.
parameters:
- name: videoId
in: query
required: true
schema: { type: string }
description: The YouTube video ID (e.g., "dQw4w9WgXcQ")
- name: accountId
in: query
required: true
schema: { type: string }
description: The Late account ID for the YouTube account
- name: startDate
in: query
schema: { type: string, format: date }
description: Start date (YYYY-MM-DD). Defaults to 30 days ago.
- name: endDate
in: query
schema: { type: string, format: date }
description: End date (YYYY-MM-DD). Defaults to 3 days ago (YouTube data latency).
responses:
'200':
description: Daily views breakdown
content:
application/json:
schema:
$ref: '#/components/schemas/YouTubeDailyViewsResponse'
examples:
success:
summary: Successful response with daily views
value:
success: true
videoId: "dQw4w9WgXcQ"
dateRange:
startDate: "2025-01-01"
endDate: "2025-01-12"
totalViews: 12345
dailyViews:
- date: "2025-01-12"
views: 1234
estimatedMinutesWatched: 567.5
averageViewDuration: 45.2
subscribersGained: 10
subscribersLost: 2
likes: 89
comments: 12
shares: 5
- date: "2025-01-11"
views: 987
estimatedMinutesWatched: 432.1
averageViewDuration: 43.8
subscribersGained: 8
subscribersLost: 1
likes: 67
comments: 8
shares: 3
lastSyncedAt: "2025-01-15T12:00:00Z"
scopeStatus:
hasAnalyticsScope: true
'400':
description: Bad request (missing or invalid parameters)
content:
application/json:
schema:
type: object
properties:
error: { type: string }
examples:
missingVideoId:
value:
error: "videoId is required"
invalidDate:
value:
error: "Invalid startDate format. Use YYYY-MM-DD."
'401': { $ref: '#/components/responses/Unauthorized' }
'402':
description: Analytics add-on required
content:
application/json:
schema:
type: object
properties:
error: { type: string, example: "Analytics add-on required" }
code: { type: string, example: "analytics_addon_required" }
'403':
description: Access denied to this account
content:
application/json:
schema:
type: object
properties:
error: { type: string, example: "Access denied to this account" }
'412':
description: Missing YouTube Analytics scope
content:
application/json:
schema:
$ref: '#/components/schemas/YouTubeScopeMissingResponse'
examples:
scopeMissing:
summary: YouTube Analytics scope not granted
value:
success: false
error: "To access daily video analytics, please reconnect your YouTube account to grant the required permissions."
code: "youtube_analytics_scope_missing"
scopeStatus:
hasAnalyticsScope: false
requiresReauthorization: true
reauthorizeUrl: "https://accounts.google.com/o/oauth2/auth?client_id=..."
'500':
description: Internal server error
content:
application/json:
schema:
type: object
properties:
success: { type: boolean, example: false }
error: { type: string }
/v1/analytics/daily-metrics:
get:
operationId: getDailyMetrics
tags: [Analytics]
summary: Get daily aggregated metrics
description: |
Returns daily aggregated analytics metrics and a per-platform breakdown.
Each day includes post count, platform distribution, and summed metrics (impressions, reach, likes, comments, shares, saves, clicks, views).
Defaults to the last 180 days. Requires the Analytics add-on.
parameters:
- name: platform
in: query
schema: { type: string }
description: Filter by platform (e.g. "instagram", "tiktok"). Omit for all platforms.
- name: profileId
in: query
schema: { type: string }
description: Filter by profile ID. Omit for all profiles.
- name: fromDate
in: query
schema: { type: string, format: date-time }
description: Inclusive start date (ISO 8601). Defaults to 180 days ago.
- name: toDate
in: query
schema: { type: string, format: date-time }
description: Inclusive end date (ISO 8601). Defaults to now.
responses:
'200':
description: Daily metrics and platform breakdown
content:
application/json:
schema:
type: object
properties:
dailyData:
type: array
items:
type: object
properties:
date: { type: string, example: "2025-12-01" }
postCount: { type: integer, example: 3 }
platforms:
type: object
additionalProperties: { type: integer }
example: { instagram: 2, twitter: 1 }
metrics:
type: object
properties:
impressions: { type: integer }
reach: { type: integer }
likes: { type: integer }
comments: { type: integer }
shares: { type: integer }
saves: { type: integer }
clicks: { type: integer }
views: { type: integer }
platformBreakdown:
type: array
items:
type: object
properties:
platform: { type: string, example: "instagram" }
postCount: { type: integer, example: 142 }
impressions: { type: integer }
reach: { type: integer }
likes: { type: integer }
comments: { type: integer }
shares: { type: integer }
saves: { type: integer }
clicks: { type: integer }
views: { type: integer }
examples:
success:
value:
dailyData:
- date: "2025-12-01"
postCount: 3
platforms: { instagram: 2, twitter: 1 }
metrics:
impressions: 4520
reach: 3200
likes: 312
comments: 45
shares: 28
saves: 67
clicks: 89
views: 1560
platformBreakdown:
- platform: "instagram"
postCount: 142
impressions: 89400
reach: 62100
likes: 8930
comments: 1204
shares: 567
saves: 2103
clicks: 3402
views: 45200
'401': { $ref: '#/components/responses/Unauthorized' }
'402':
description: Analytics add-on required
content:
application/json:
schema:
type: object
properties:
error: { type: string, example: "Analytics add-on required" }
code: { type: string, example: "analytics_addon_required" }
/v1/analytics/best-time:
get:
operationId: getBestTimeToPost
tags: [Analytics]
summary: Get best times to post
description: |
Returns the best times to post based on historical engagement data.
Groups all published posts by day of week and hour (UTC), calculating average engagement per slot.
Use this to auto-schedule posts at optimal times. Requires the Analytics add-on.
parameters:
- name: platform
in: query
schema: { type: string }
description: Filter by platform (e.g. "instagram", "tiktok"). Omit for all platforms.
- name: profileId
in: query
schema: { type: string }
description: Filter by profile ID. Omit for all profiles.
responses:
'200':
description: Best time slots
content:
application/json:
schema:
type: object
properties:
slots:
type: array
items:
type: object
properties:
day_of_week: { type: integer, description: "0=Monday, 6=Sunday", minimum: 0, maximum: 6 }
hour: { type: integer, description: "Hour in UTC (0-23)", minimum: 0, maximum: 23 }
avg_engagement: { type: number, description: "Average engagement (likes + comments + shares + saves)" }
post_count: { type: integer, description: "Number of posts in this slot" }
examples:
success:
value:
slots:
- day_of_week: 2
hour: 18
avg_engagement: 510.3
post_count: 15
- day_of_week: 0
hour: 9
avg_engagement: 342.5
post_count: 12
- day_of_week: 4
hour: 12
avg_engagement: 289.1
post_count: 8
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Analytics add-on required
content:
application/json:
schema:
type: object
properties:
error: { type: string, example: "Analytics add-on required" }
requiresAddon: { type: boolean, example: true }
/v1/analytics/content-decay:
get:
operationId: getContentDecay
tags: [Analytics]
summary: Get content performance decay
description: |
Returns how engagement accumulates over time after a post is published.
Each bucket shows what percentage of the post's total engagement had been reached by that time window.
Useful for understanding content lifespan (e.g. "posts reach 78% of total engagement within 24 hours").
Requires the Analytics add-on.
parameters:
- name: platform
in: query
schema: { type: string }
description: Filter by platform (e.g. "instagram", "tiktok"). Omit for all platforms.
- name: profileId
in: query
schema: { type: string }
description: Filter by profile ID. Omit for all profiles.
responses:
'200':
description: Content decay buckets
content:
application/json:
schema:
type: object
properties:
buckets:
type: array
items:
type: object
properties:
bucket_order: { type: integer, description: "Sort order (0 = earliest, 6 = latest)" }
bucket_label: { type: string, description: "Human-readable label" }
avg_pct_of_final: { type: number, description: "Average % of final engagement reached (0-100)" }
post_count: { type: integer, description: "Number of posts with data in this bucket" }
examples:
success:
value:
buckets:
- bucket_order: 0
bucket_label: "0-6h"
avg_pct_of_final: 45.2
post_count: 89
- bucket_order: 1
bucket_label: "6-12h"
avg_pct_of_final: 18.7
post_count: 89
- bucket_order: 2
bucket_label: "12-24h"
avg_pct_of_final: 14.1
post_count: 85
- bucket_order: 3
bucket_label: "1-2d"
avg_pct_of_final: 9.3
post_count: 82
- bucket_order: 4
bucket_label: "2-7d"
avg_pct_of_final: 8.1
post_count: 78
- bucket_order: 5
bucket_label: "7-30d"
avg_pct_of_final: 3.8
post_count: 64
- bucket_order: 6
bucket_label: "30d+"
avg_pct_of_final: 0.8
post_count: 41
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Analytics add-on required
content:
application/json:
schema:
type: object
properties:
error: { type: string, example: "Analytics add-on required" }
requiresAddon: { type: boolean, example: true }
/v1/analytics/posting-frequency:
get:
operationId: getPostingFrequency
tags: [Analytics]
summary: Get posting frequency vs engagement
description: |
Returns the correlation between posting frequency (posts per week) and engagement rate, broken down by platform.
Helps find the optimal posting cadence for each platform. Each row represents a specific (platform, posts_per_week) combination
with the average engagement rate observed across all weeks matching that frequency.
Requires the Analytics add-on.
parameters:
- name: platform
in: query
schema: { type: string }
description: Filter by platform (e.g. "instagram", "tiktok"). Omit for all platforms.
- name: profileId
in: query
schema: { type: string }
description: Filter by profile ID. Omit for all profiles.
responses:
'200':
description: Posting frequency data
content:
application/json:
schema:
type: object
properties:
frequency:
type: array
items:
type: object
properties:
platform: { type: string, example: "instagram" }
posts_per_week: { type: integer, description: "Number of posts published that week" }
avg_engagement_rate: { type: number, description: "Average engagement rate as percentage (0-100)" }
avg_engagement: { type: number, description: "Average raw engagement (likes+comments+shares+saves)" }
weeks_count: { type: integer, description: "Number of calendar weeks observed at this frequency" }
examples:
success:
value:
frequency:
- platform: "instagram"
posts_per_week: 2
avg_engagement_rate: 44.4
avg_engagement: 512
weeks_count: 18
- platform: "instagram"
posts_per_week: 4
avg_engagement_rate: 5.9
avg_engagement: 203
weeks_count: 6
- platform: "facebook"
posts_per_week: 3
avg_engagement_rate: 12.5
avg_engagement: 87
weeks_count: 10
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Analytics add-on required
content:
application/json:
schema:
type: object
properties:
error: { type: string, example: "Analytics add-on required" }
requiresAddon: { type: boolean, example: true }
/v1/account-groups:
get:
operationId: listAccountGroups
tags: [Account Groups]
summary: List groups
description: Returns all account groups for the authenticated user, including group names and associated account IDs.
responses:
'200':
description: Groups
content:
application/json:
schema:
type: object
properties:
groups:
type: array
items:
type: object
properties:
_id: { type: string }
name: { type: string }
accountIds:
type: array
items: { type: string }
examples:
example:
value:
groups:
- _id: "6507a1b2c3d4e5f6a7b8c9d0"
name: "Marketing Accounts"
accountIds:
- "64e1f0a9e2b5af0012ab34cd"
- "64e1f0a9e2b5af0012ab34ce"
- _id: "6507a1b2c3d4e5f6a7b8c9d1"
name: "Personal Brand"
accountIds:
- "64e1f0a9e2b5af0012ab34cf"
'401': { $ref: '#/components/responses/Unauthorized' }
post:
operationId: createAccountGroup
tags: [Account Groups]
summary: Create group
description: Creates a new account group with a name and a list of social account IDs.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name, accountIds]
properties:
name: { type: string }
accountIds:
type: array
items: { type: string }
example:
name: "Marketing Accounts"
accountIds:
- "64e1f0a9e2b5af0012ab34cd"
- "64e1f0a9e2b5af0012ab34ce"
responses:
'201':
description: Created
content:
application/json:
schema:
type: object
properties:
message: { type: string }
group:
type: object
properties:
_id: { type: string }
name: { type: string }
accountIds:
type: array
items: { type: string }
example:
message: "Account group created successfully"
group:
_id: "6507a1b2c3d4e5f6a7b8c9d0"
name: "Marketing Accounts"
accountIds:
- "64e1f0a9e2b5af0012ab34cd"
- "64e1f0a9e2b5af0012ab34ce"
'400': { description: Invalid request }
'401': { $ref: '#/components/responses/Unauthorized' }
'409': { description: Group name already exists }
/v1/account-groups/{groupId}:
put:
operationId: updateAccountGroup
tags: [Account Groups]
summary: Update group
description: Updates the name or account list of an existing group. You can rename the group, change its accounts, or both.
parameters:
- name: groupId
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name: { type: string }
accountIds:
type: array
items: { type: string }
example:
name: "Updated Marketing Accounts"
accountIds:
- "64e1f0a9e2b5af0012ab34cd"
- "64e1f0a9e2b5af0012ab34ce"
- "64e1f0a9e2b5af0012ab34cf"
responses:
'200':
description: Updated
content:
application/json:
schema:
type: object
properties:
message: { type: string }
group:
type: object
example:
message: "Account group updated successfully"
group:
_id: "6507a1b2c3d4e5f6a7b8c9d0"
name: "Updated Marketing Accounts"
accountIds:
- "64e1f0a9e2b5af0012ab34cd"
- "64e1f0a9e2b5af0012ab34ce"
- "64e1f0a9e2b5af0012ab34cf"
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
'409': { description: Group name already exists }
delete:
operationId: deleteAccountGroup
tags: [Account Groups]
summary: Delete group
description: Permanently deletes an account group. The accounts themselves are not affected.
parameters:
- name: groupId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Deleted
content:
application/json:
schema:
type: object
properties:
message: { type: string }
example:
message: "Account group deleted successfully"
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
/v1/media/presign:
post:
operationId: getMediaPresignedUrl
tags: [Media]
summary: Get presigned upload URL
description: Get a presigned URL to upload files directly to cloud storage (up to 5GB). Returns an uploadUrl and publicUrl. PUT your file to the uploadUrl, then use the publicUrl in your posts.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [filename, contentType]
properties:
filename:
type: string
description: Name of the file to upload
example: "my-video.mp4"
contentType:
type: string
description: MIME type of the file
enum:
- image/jpeg
- image/jpg
- image/png
- image/webp
- image/gif
- video/mp4
- video/mpeg
- video/quicktime
- video/avi
- video/x-msvideo
- video/webm
- video/x-m4v
- application/pdf
example: "video/mp4"
size:
type: integer
description: Optional file size in bytes for pre-validation (max 5GB)
example: 15234567
responses:
'200':
description: Presigned URL generated successfully
content:
application/json:
schema:
type: object
properties:
uploadUrl:
type: string
format: uri
description: Presigned URL to PUT your file to (expires in 1 hour)
publicUrl:
type: string
format: uri
description: Public URL where the file will be accessible after upload
key:
type: string
description: Storage key/path of the file
type:
type: string
enum: [image, video, document]
description: Detected file type based on content type
example:
uploadUrl: "<presigned-upload-url>"
publicUrl: "https://media.getlate.dev/temp/1234567890_abc123_my-video.mp4"
key: "temp/1234567890_abc123_my-video.mp4"
type: "video"
'400':
description: Invalid request (missing filename, contentType, or unsupported content type)
content:
application/json:
schema:
type: object
properties:
error: { type: string }
examples:
missing_filename:
value: { error: "filename and contentType are required" }
invalid_type:
value: { error: "Content type not allowed: text/plain" }
file_too_large:
value: { error: "File too large. Maximum size is 5GB." }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/reddit/search:
get:
operationId: searchReddit
tags: [Reddit Search]
summary: Search posts
description: Search Reddit posts using a connected account. Optionally scope to a specific subreddit.
parameters:
- name: accountId
in: query
required: true
schema: { type: string }
- name: subreddit
in: query
schema: { type: string }
- name: q
in: query
required: true
schema: { type: string }
- name: restrict_sr
in: query
schema: { type: string, enum: ['0','1'] }
- name: sort
in: query
schema: { type: string, enum: [relevance, hot, top, new, comments], default: new }
- name: limit
in: query
schema: { type: integer, default: 25, maximum: 100 }
- name: after
in: query
schema: { type: string }
responses:
'200':
description: Search results
content:
application/json:
schema:
type: object
properties:
posts:
type: array
items:
type: object
properties:
id: { type: string }
title: { type: string }
selftext: { type: string }
author: { type: string }
subreddit: { type: string }
score: { type: integer }
num_comments: { type: integer }
created_utc: { type: number }
permalink: { type: string }
after: { type: string }
example:
posts:
- id: "1abc234"
title: "How to grow on social media in 2025"
selftext: "Here are my tips..."
author: "marketingpro"
subreddit: "socialmedia"
score: 156
num_comments: 42
created_utc: 1730000000
permalink: "/r/socialmedia/comments/1abc234/how_to_grow/"
after: "t3_1abc234"
'400': { description: Missing params }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Account not found }
/v1/reddit/feed:
get:
operationId: getRedditFeed
tags: [Reddit Search]
summary: Get subreddit feed
description: Fetch posts from a subreddit feed. Supports sorting, time filtering, and cursor-based pagination.
parameters:
- name: accountId
in: query
required: true
schema: { type: string }
- name: subreddit
in: query
schema: { type: string }
- name: sort
in: query
schema: { type: string, enum: [hot, new, top, rising], default: hot }
- name: limit
in: query
schema: { type: integer, default: 25, maximum: 100 }
- name: after
in: query
schema: { type: string }
- name: t
in: query
schema: { type: string, enum: [hour, day, week, month, year, all] }
responses:
'200':
description: Feed items
content:
application/json:
schema:
type: object
properties:
posts:
type: array
items:
type: object
after: { type: string }
example:
posts:
- id: "1xyz789"
title: "Top marketing trends this week"
author: "trendwatcher"
subreddit: "marketing"
score: 892
num_comments: 134
created_utc: 1730100000
permalink: "/r/marketing/comments/1xyz789/top_marketing_trends/"
- id: "1def456"
title: "My social media strategy that worked"
author: "growthexpert"
subreddit: "marketing"
score: 567
num_comments: 89
created_utc: 1730050000
permalink: "/r/marketing/comments/1def456/my_social_media_strategy/"
after: "t3_1def456"
'400': { description: Missing params }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Account not found }
/v1/usage-stats:
get:
operationId: getUsageStats
tags: [Usage]
summary: Get plan and usage stats
description: Returns the current plan name, billing period, plan limits, and usage counts.
responses:
'200':
description: Usage stats
content:
application/json:
schema:
$ref: '#/components/schemas/UsageStats'
example:
planName: "Pro"
billingPeriod: "monthly"
signupDate: "2024-01-15T10:30:00Z"
limits:
uploads: 500
profiles: 10
usage:
uploads: 127
profiles: 3
lastReset: "2024-11-01T00:00:00Z"
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
/v1/posts:
get:
operationId: listPosts
tags: [Posts]
summary: List posts
description: Returns a paginated list of posts. Published posts include platformPostUrl with the public URL on each platform.
parameters:
- $ref: '#/components/parameters/PageParam'
- $ref: '#/components/parameters/LimitParam'
- name: status
in: query
schema: { type: string, enum: [draft, scheduled, published, failed] }
- name: platform
in: query
schema: { type: string, example: twitter }
- name: profileId
in: query
schema: { type: string }
- name: createdBy
in: query
schema: { type: string }
- name: dateFrom
in: query
schema: { type: string, format: date }
- name: dateTo
in: query
schema: { type: string, format: date }
- name: includeHidden
in: query
schema: { type: boolean, default: false }
responses:
'200':
description: Paginated posts
content:
application/json:
schema:
$ref: '#/components/schemas/PostsListResponse'
examples:
scheduledPost:
summary: Scheduled post (pending publish)
value:
posts:
- _id: "65f1c0a9e2b5af0012ab34cd"
title: "Launch post"
content: "We just launched!"
status: "scheduled"
scheduledFor: "2024-11-01T10:00:00Z"
timezone: "UTC"
platforms:
- platform: "twitter"
accountId:
_id: "64e1f0..."
platform: "twitter"
username: "@acme"
displayName: "Acme Corp"
isActive: true
status: "pending"
tags: ["launch"]
createdAt: "2024-10-01T12:00:00Z"
updatedAt: "2024-10-01T12:00:00Z"
pagination:
page: 1
limit: 10
total: 1
pages: 1
publishedPost:
summary: Published post with platformPostUrl
value:
posts:
- _id: "65f1c0a9e2b5af0012ab34cd"
title: "Launch post"
content: "We just launched!"
status: "published"
scheduledFor: "2024-11-01T10:00:00Z"
publishedAt: "2024-11-01T10:00:05Z"
timezone: "UTC"
platforms:
- platform: "twitter"
accountId:
_id: "64e1f0a9e2b5af0012ab34de"
platform: "twitter"
username: "@acmecorp"
displayName: "Acme Corporation"
isActive: true
status: "published"
publishedAt: "2024-11-01T10:00:05Z"
platformPostId: "1852634789012345678"
platformPostUrl: "https://twitter.com/acmecorp/status/1852634789012345678"
- platform: "linkedin"
accountId:
_id: "64e1f0a9e2b5af0012ab34ef"
platform: "linkedin"
username: "acme-corporation"
displayName: "Acme Corporation"
isActive: true
status: "published"
publishedAt: "2024-11-01T10:00:06Z"
platformPostId: "urn:li:share:7123456789012345678"
platformPostUrl: "https://www.linkedin.com/feed/update/urn:li:share:7123456789012345678"
tags: ["launch"]
createdAt: "2024-10-01T12:00:00Z"
updatedAt: "2024-11-01T10:00:06Z"
pagination:
page: 1
limit: 10
total: 1
pages: 1
'401': { $ref: '#/components/responses/Unauthorized' }
post:
operationId: createPost
tags: [Posts]
summary: Create post
description: |
Create and optionally publish a post. Immediate posts (publishNow: true) include platformPostUrl in the response.
Content is optional when media is attached or all platforms have customContent. See each platform's schema for media constraints.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
title: { type: string }
content:
type: string
description: Post caption/text. Optional when media is attached or all platforms have customContent. Required for text-only posts.
mediaItems:
type: array
items:
type: object
properties:
type: { type: string, enum: [image, video, gif, document] }
url: { type: string, format: uri }
platforms:
type: array
items:
type: object
properties:
platform: { type: string, example: twitter }
accountId: { type: string }
customContent:
type: string
description: Platform-specific text override. When set, this content is used instead of the top-level post content for this platform. Useful for tailoring captions per platform (e.g. keeping tweets under 280 characters).
customMedia:
type: array
items:
type: object
properties:
type: { type: string, enum: [image, video, gif, document] }
url: { type: string, format: uri }
scheduledFor:
type: string
format: date-time
description: Optional per-platform scheduled time override. When omitted, the top-level scheduledFor is used.
platformSpecificData:
oneOf:
- $ref: '#/components/schemas/TwitterPlatformData'
- $ref: '#/components/schemas/ThreadsPlatformData'
- $ref: '#/components/schemas/FacebookPlatformData'
- $ref: '#/components/schemas/InstagramPlatformData'
- $ref: '#/components/schemas/LinkedInPlatformData'
- $ref: '#/components/schemas/PinterestPlatformData'
- $ref: '#/components/schemas/YouTubePlatformData'
- $ref: '#/components/schemas/GoogleBusinessPlatformData'
- $ref: '#/components/schemas/TikTokPlatformData'
- $ref: '#/components/schemas/TelegramPlatformData'
- $ref: '#/components/schemas/SnapchatPlatformData'
- $ref: '#/components/schemas/RedditPlatformData'
- $ref: '#/components/schemas/BlueskyPlatformData'
scheduledFor: { type: string, format: date-time }
publishNow: { type: boolean, default: false }
isDraft: { type: boolean, default: false }
timezone: { type: string, default: UTC }
tags:
type: array
description: "Tags/keywords. YouTube constraints: each tag max 100 chars, combined max 500 chars, duplicates auto-removed."
items: { type: string }
hashtags:
type: array
items: { type: string }
mentions:
type: array
items: { type: string }
crosspostingEnabled: { type: boolean, default: true }
metadata: { type: object, additionalProperties: true }
tiktokSettings:
$ref: '#/components/schemas/TikTokPlatformData'
description: Root-level TikTok settings applied to all TikTok platforms. Merged into each platform's platformSpecificData, with platform-specific settings taking precedence.
queuedFromProfile:
type: string
description: Profile ID to schedule via queue. When provided without scheduledFor, the post is auto-assigned to the next available slot. Do not call /v1/queue/next-slot and use that time in scheduledFor, as that bypasses queue locking.
queueId:
type: string
description: |
Specific queue ID to use when scheduling via queue.
Only used when queuedFromProfile is also provided.
If omitted, uses the profile's default queue.
examples:
tiktokPhotoCarousel:
summary: TikTok photo carousel with draft mode
value:
content: "Check out these photos!"
mediaItems:
- type: image
url: "https://example.com/photo1.jpg"
- type: image
url: "https://example.com/photo2.jpg"
platforms:
- platform: tiktok
accountId: "64e1f0a9e2b5af0012ab34cd"
tiktokSettings:
draft: true
privacyLevel: "PUBLIC_TO_EVERYONE"
allowComment: true
contentPreviewConfirmed: true
expressConsentGiven: true
tiktokVideo:
summary: TikTok video post (direct publish)
value:
content: "New video is live!"
mediaItems:
- type: video
url: "https://example.com/video.mp4"
platforms:
- platform: tiktok
accountId: "64e1f0a9e2b5af0012ab34cd"
tiktokSettings:
privacyLevel: "PUBLIC_TO_EVERYONE"
allowComment: true
allowDuet: true
allowStitch: true
commercialContentType: "none"
contentPreviewConfirmed: true
expressConsentGiven: true
multiPlatform:
summary: Multi-platform post (Twitter + LinkedIn)
value:
content: "We just launched our new product!"
mediaItems:
- type: image
url: "https://example.com/launch.jpg"
platforms:
- platform: twitter
accountId: "64e1f0a9e2b5af0012ab34cd"
- platform: linkedin
accountId: "64e1f0a9e2b5af0012ab34ef"
scheduledFor: "2024-11-01T10:00:00Z"
timezone: "America/New_York"
responses:
'201':
description: Post created
content:
application/json:
schema:
$ref: '#/components/schemas/PostCreateResponse'
examples:
scheduled:
summary: Scheduled post (URLs populated after publish time)
value:
post:
_id: "65f1c0a9e2b5af0012ab34cd"
title: "Launch post"
content: "We just launched!"
status: "scheduled"
scheduledFor: "2024-11-01T10:00:00Z"
timezone: "UTC"
platforms:
- platform: "twitter"
accountId:
_id: "64e1f0..."
platform: "twitter"
username: "@acme"
displayName: "Acme Corp"
isActive: true
status: "pending"
message: "Post scheduled successfully"
immediatePublish:
summary: Immediate post with publishNow=true (URLs included)
value:
post:
_id: "65f1c0a9e2b5af0012ab34cd"
title: "Launch post"
content: "We just launched!"
status: "published"
publishedAt: "2024-11-01T10:00:05Z"
timezone: "UTC"
platforms:
- platform: "twitter"
accountId:
_id: "64e1f0a9e2b5af0012ab34de"
platform: "twitter"
username: "@acmecorp"
displayName: "Acme Corporation"
isActive: true
status: "published"
publishedAt: "2024-11-01T10:00:05Z"
platformPostId: "1852634789012345678"
platformPostUrl: "https://twitter.com/acmecorp/status/1852634789012345678"
- platform: "linkedin"
accountId:
_id: "64e1f0a9e2b5af0012ab34ef"
platform: "linkedin"
username: "acme-corporation"
displayName: "Acme Corporation"
isActive: true
status: "published"
publishedAt: "2024-11-01T10:00:06Z"
platformPostId: "urn:li:share:7123456789012345678"
platformPostUrl: "https://www.linkedin.com/feed/update/urn:li:share:7123456789012345678"
message: "Post published successfully"
queueScheduled:
summary: Post scheduled via queue (using queuedFromProfile)
value:
post:
_id: "65f1c0a9e2b5af0012ab34cd"
content: "Scheduled via queue!"
status: "scheduled"
scheduledFor: "2024-11-01T09:00:00Z"
timezone: "America/New_York"
queuedFromProfile: "64f0a1b2c3d4e5f6a7b8c9d0"
queueId: "64f0a1b2c3d4e5f6a7b8c9d1"
platforms:
- platform: "linkedin"
accountId:
_id: "64e1f0..."
platform: "linkedin"
username: "acme-corp"
displayName: "Acme Corp"
isActive: true
status: "pending"
message: "Post scheduled successfully"
'400':
description: Validation error
content:
application/json:
schema:
type: object
properties:
error: { type: string }
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Forbidden
content:
application/json:
schema:
type: object
properties:
error: { type: string }
'409':
description: Duplicate content detected
content:
application/json:
schema:
type: object
properties:
error: { type: string, example: "This exact content was already posted to this account within the last 24 hours." }
details:
type: object
properties:
accountId: { type: string }
platform: { type: string }
existingPostId: { type: string }
'429':
description: "Rate limit exceeded. Possible causes: API rate limit, velocity limit (15 posts/hour per account), account cooldown, or daily platform limits."
content:
application/json:
schema:
type: object
properties:
error: { type: string }
details:
type: object
description: Additional context about the rate limit
headers:
Retry-After:
description: Seconds until the rate limit resets (for API rate limits)
schema: { type: integer }
X-RateLimit-Limit:
description: The rate limit ceiling
schema: { type: integer }
X-RateLimit-Remaining:
description: Requests remaining in current window
schema: { type: integer }
/v1/posts/{postId}:
get:
operationId: getPost
tags: [Posts]
summary: Get post
description: |
Fetch a single post by ID. For published posts, this returns platformPostUrl for each platform.
parameters:
- name: postId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Post
content:
application/json:
schema:
$ref: '#/components/schemas/PostGetResponse'
examples:
scheduledPost:
summary: Scheduled post (pending)
value:
post:
_id: "65f1c0a9e2b5af0012ab34cd"
title: "Launch post"
content: "We just launched!"
status: "scheduled"
scheduledFor: "2024-11-01T10:00:00Z"
platforms:
- platform: "twitter"
accountId:
_id: "64e1f0..."
platform: "twitter"
username: "@acme"
displayName: "Acme Corp"
isActive: true
status: "pending"
publishedPost:
summary: Published post with platformPostUrl
value:
post:
_id: "65f1c0a9e2b5af0012ab34cd"
title: "Launch post"
content: "We just launched!"
status: "published"
publishedAt: "2024-11-01T10:00:05Z"
platforms:
- platform: "twitter"
accountId:
_id: "64e1f0a9e2b5af0012ab34de"
platform: "twitter"
username: "@acmecorp"
displayName: "Acme Corporation"
isActive: true
status: "published"
publishedAt: "2024-11-01T10:00:05Z"
platformPostId: "1852634789012345678"
platformPostUrl: "https://twitter.com/acmecorp/status/1852634789012345678"
- platform: "linkedin"
accountId:
_id: "64e1f0a9e2b5af0012ab34ef"
platform: "linkedin"
username: "acme-corporation"
displayName: "Acme Corporation"
isActive: true
status: "published"
publishedAt: "2024-11-01T10:00:06Z"
platformPostId: "urn:li:share:7123456789012345678"
platformPostUrl: "https://www.linkedin.com/feed/update/urn:li:share:7123456789012345678"
failedPost:
summary: Failed post with error details
value:
post:
_id: "65f1c0a9e2b5af0012ab34cd"
content: "This post failed to publish"
status: "failed"
platforms:
- platform: "instagram"
accountId:
_id: "64e1f0a9e2b5af0012ab34de"
platform: "instagram"
username: "acmecorp"
isActive: false
status: "failed"
errorMessage: "Instagram access token has expired. Please reconnect your account."
errorCategory: "auth_expired"
errorSource: "user"
partialPost:
summary: Partial success (some platforms failed)
value:
post:
_id: "65f1c0a9e2b5af0012ab34cd"
content: "Launch announcement!"
status: "partial"
platforms:
- platform: "twitter"
accountId:
_id: "64e1f0a9e2b5af0012ab34de"
platform: "twitter"
username: "@acmecorp"
isActive: true
status: "published"
publishedAt: "2024-11-01T10:00:05Z"
platformPostId: "1852634789012345678"
platformPostUrl: "https://twitter.com/acmecorp/status/1852634789012345678"
- platform: "threads"
accountId:
_id: "64e1f0a9e2b5af0012ab34ef"
platform: "threads"
username: "acmecorp"
isActive: true
status: "failed"
errorMessage: "Post text exceeds the 500 character limit for Threads."
errorCategory: "user_content"
errorSource: "user"
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Forbidden
'404': { $ref: '#/components/responses/NotFound' }
put:
operationId: updatePost
tags: [Posts]
summary: Update post
description: |
Update an existing post. Only draft, scheduled, failed, and partial posts can be edited.
Published, publishing, and cancelled posts cannot be modified.
parameters:
- name: postId
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
content: { type: string }
scheduledFor: { type: string, format: date-time }
tiktokSettings:
$ref: '#/components/schemas/TikTokPlatformData'
description: Root-level TikTok settings applied to all TikTok platforms. Merged into each platform's platformSpecificData, with platform-specific settings taking precedence.
additionalProperties: true
example:
content: "Updated content for our launch post!"
scheduledFor: "2024-11-02T14:00:00Z"
responses:
'200':
description: Post updated
content:
application/json:
schema:
$ref: '#/components/schemas/PostUpdateResponse'
example:
message: "Post updated successfully"
post:
_id: "65f1c0a9e2b5af0012ab34cd"
content: "Updated content for our launch post!"
status: "scheduled"
scheduledFor: "2024-11-02T14:00:00Z"
'207':
description: Partial publish success
'400':
description: Invalid request
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Forbidden
'404': { $ref: '#/components/responses/NotFound' }
delete:
operationId: deletePost
tags: [Posts]
summary: Delete post
description: Delete a draft or scheduled post from Late. Published posts cannot be deleted; use the Unpublish endpoint instead. Upload quota is automatically refunded.
parameters:
- name: postId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Deleted
content:
application/json:
schema:
$ref: '#/components/schemas/PostDeleteResponse'
example:
message: "Post deleted successfully"
'400':
description: Cannot delete published posts
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Forbidden
'404': { $ref: '#/components/responses/NotFound' }
/v1/posts/bulk-upload:
post:
operationId: bulkUploadPosts
tags: [Posts]
summary: Bulk upload from CSV
description: Create multiple posts by uploading a CSV file. Use dryRun=true to validate without creating posts.
parameters:
- name: dryRun
in: query
schema: { type: boolean, default: false }
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
responses:
'200':
description: Bulk upload results
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
totalRows: { type: integer }
created: { type: integer }
failed: { type: integer }
errors:
type: array
items:
type: object
properties:
row: { type: integer }
error: { type: string }
posts:
type: array
items: { $ref: '#/components/schemas/Post' }
example:
success: true
totalRows: 10
created: 8
failed: 2
errors:
- row: 3
error: "Invalid date format"
- row: 7
error: "Account not found"
posts:
- _id: "65f1c0a9e2b5af0012ab34cd"
content: "First bulk post"
status: "scheduled"
scheduledFor: "2024-11-01T10:00:00Z"
'207':
description: Partial success
'400':
description: Invalid CSV or validation errors
'401': { $ref: '#/components/responses/Unauthorized' }
'429':
description: |
Rate limit exceeded. Possible causes: API rate limit (requests per minute) or account cooldown (one or more accounts for platforms specified in the CSV are temporarily rate-limited).
content:
application/json:
schema:
type: object
properties:
error: { type: string }
details:
type: object
/v1/posts/{postId}/retry:
post:
operationId: retryPost
tags: [Posts]
summary: Retry failed post
description: Immediately retries publishing a failed post. Returns the updated post with its new status.
parameters:
- name: postId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Retry successful
content:
application/json:
schema:
$ref: '#/components/schemas/PostRetryResponse'
example:
message: "Post published successfully"
post:
_id: "65f1c0a9e2b5af0012ab34cd"
content: "Check out our new product!"
status: "published"
publishedAt: "2024-11-01T10:00:05Z"
platforms:
- platform: "twitter"
accountId:
_id: "64e1f0..."
platform: "twitter"
username: "@acme"
displayName: "Acme Corp"
isActive: true
status: "published"
platformPostId: "1234567890"
platformPostUrl: "https://twitter.com/acme/status/1234567890"
'207':
description: Partial success
'400':
description: Invalid state
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Forbidden
'404': { $ref: '#/components/responses/NotFound' }
'409':
description: Post is currently publishing
'429':
description: |
Rate limit exceeded. Possible causes: API rate limit (requests per minute), velocity limit (15 posts/hour per account), or account cooldown (temporarily rate-limited due to repeated errors).
content:
application/json:
schema:
type: object
properties:
error: { type: string }
details:
type: object
/v1/posts/{postId}/unpublish:
post:
operationId: unpublishPost
tags: [Posts]
summary: Unpublish post
description: |
Deletes a published post from the specified platform. The post record in Late is kept but its status is updated to cancelled.
Not supported on Instagram, TikTok, or Snapchat. Threaded posts delete all items. YouTube deletion is permanent.
parameters:
- name: postId
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [platform]
properties:
platform:
type: string
description: The platform to delete the post from
enum:
- threads
- facebook
- twitter
- linkedin
- youtube
- pinterest
- reddit
- bluesky
- googlebusiness
- telegram
example:
platform: "threads"
responses:
'200':
description: Post deleted from platform
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
message: { type: string }
example:
success: true
message: "Post deleted from threads successfully"
'400':
description: "Invalid request: platform not supported for deletion, post not on that platform, not published, no platform post ID, or no access token."
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Forbidden
'404': { $ref: '#/components/responses/NotFound' }
'500':
description: Platform API deletion failed
/v1/users:
get:
operationId: listUsers
tags: [Users]
summary: List users
description: Returns all users in the workspace including roles and profile access. Also returns the currentUserId of the caller.
responses:
'200':
description: Users
content:
application/json:
schema:
type: object
properties:
currentUserId: { type: string }
users:
type: array
items:
type: object
properties:
_id: { type: string }
name: { type: string }
email: { type: string }
role: { type: string }
isRoot: { type: boolean }
profileAccess:
type: array
items: { type: string }
createdAt: { type: string, format: date-time }
example:
currentUserId: "6507a1b2c3d4e5f6a7b8c9d0"
users:
- _id: "6507a1b2c3d4e5f6a7b8c9d0"
name: "John Doe"
email: "john@example.com"
role: "owner"
isRoot: true
profileAccess: ["all"]
createdAt: "2024-01-15T10:30:00Z"
- _id: "6507a1b2c3d4e5f6a7b8c9d1"
name: "Jane Smith"
email: "jane@example.com"
role: "member"
isRoot: false
profileAccess:
- "64f0a1b2c3d4e5f6a7b8c9d0"
- "64f0a1b2c3d4e5f6a7b8c9d1"
createdAt: "2024-03-20T14:45:00Z"
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/users/{userId}:
get:
operationId: getUser
tags: [Users]
summary: Get user
description: Returns a single user's details by ID, including name, email, and role.
parameters:
- name: userId
in: path
required: true
schema: { type: string }
responses:
'200':
description: User
content:
application/json:
schema:
type: object
properties:
user:
type: object
properties:
_id: { type: string }
name: { type: string }
email: { type: string }
role: { type: string }
isRoot: { type: boolean }
profileAccess:
type: array
items: { type: string }
example:
user:
_id: "6507a1b2c3d4e5f6a7b8c9d0"
name: "John Doe"
email: "john@example.com"
role: "owner"
isRoot: true
profileAccess: ["all"]
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Forbidden
'404': { $ref: '#/components/responses/NotFound' }
/v1/profiles:
get:
operationId: listProfiles
tags: [Profiles]
summary: List profiles
description: Returns profiles sorted by creation date. Use includeOverLimit=true to include profiles that exceed the plan limit.
parameters:
- name: includeOverLimit
in: query
required: false
schema:
type: boolean
default: false
description: "When true, includes over-limit profiles (marked with isOverLimit: true)."
responses:
'200':
description: Profiles
content:
application/json:
schema:
$ref: '#/components/schemas/ProfilesListResponse'
examples:
example:
value:
profiles:
- _id: "64f0..."
name: "Personal Brand"
color: "#ffeda0"
isDefault: true
'401': { $ref: '#/components/responses/Unauthorized' }
post:
operationId: createProfile
tags: [Profiles]
summary: Create profile
description: Creates a new profile with a name, optional description, and color.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name]
properties:
name: { type: string }
description: { type: string }
color: { type: string, example: '#ffeda0' }
example:
name: "Marketing Team"
description: "Profile for marketing campaigns"
color: "#4CAF50"
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/ProfileCreateResponse'
example:
message: "Profile created successfully"
profile:
_id: "64f0a1b2c3d4e5f6a7b8c9d0"
userId: "6507a1b2c3d4e5f6a7b8c9d0"
name: "Marketing Team"
description: "Profile for marketing campaigns"
color: "#4CAF50"
isDefault: false
createdAt: "2024-11-01T10:00:00Z"
'400': { description: Invalid request }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { description: Profile limit exceeded }
/v1/profiles/{profileId}:
get:
operationId: getProfile
tags: [Profiles]
summary: Get profile
description: Returns a single profile by ID, including its name, color, and default status.
parameters:
- name: profileId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Profile
content:
application/json:
schema:
type: object
properties:
profile: { $ref: '#/components/schemas/Profile' }
example:
profile:
_id: "64f0a1b2c3d4e5f6a7b8c9d0"
userId: "6507a1b2c3d4e5f6a7b8c9d0"
name: "Marketing Team"
description: "Profile for marketing campaigns"
color: "#4CAF50"
isDefault: false
createdAt: "2024-11-01T10:00:00Z"
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
put:
operationId: updateProfile
tags: [Profiles]
summary: Update profile
description: Updates a profile's name, description, color, or default status.
parameters:
- name: profileId
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name: { type: string }
description: { type: string }
color: { type: string }
isDefault: { type: boolean }
example:
name: "Marketing Team (Updated)"
color: "#2196F3"
isDefault: true
responses:
'200':
description: Updated
content:
application/json:
schema:
type: object
properties:
message: { type: string }
profile: { $ref: '#/components/schemas/Profile' }
example:
message: "Profile updated successfully"
profile:
_id: "64f0a1b2c3d4e5f6a7b8c9d0"
userId: "6507a1b2c3d4e5f6a7b8c9d0"
name: "Marketing Team (Updated)"
description: "Profile for marketing campaigns"
color: "#2196F3"
isDefault: true
createdAt: "2024-11-01T10:00:00Z"
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
delete:
operationId: deleteProfile
tags: [Profiles]
summary: Delete profile
description: Permanently deletes a profile by ID.
parameters:
- name: profileId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Deleted
content:
application/json:
schema:
type: object
properties:
message: { type: string }
example:
message: "Profile deleted successfully"
'400': { description: Has connected accounts }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { description: Forbidden }
'404': { $ref: '#/components/responses/NotFound' }
/v1/accounts:
get:
operationId: listAccounts
tags: [Accounts]
summary: List accounts
description: Returns connected social accounts. Only includes accounts within the plan limit by default. Follower data requires analytics add-on.
parameters:
- name: profileId
in: query
schema: { type: string }
description: Filter accounts by profile ID
- name: includeOverLimit
in: query
required: false
schema:
type: boolean
default: false
description: When true, includes accounts from over-limit profiles.
responses:
'200':
description: Accounts
content:
application/json:
schema:
type: object
properties:
accounts:
type: array
items: { $ref: '#/components/schemas/SocialAccount' }
hasAnalyticsAccess:
type: boolean
description: Whether user has analytics add-on access
examples:
example:
value:
accounts:
- _id: "64e1..."
platform: "twitter"
profileId:
_id: "64f0..."
name: "My Brand"
slug: "my-brand"
username: "@acme"
displayName: "Acme"
profileUrl: "https://x.com/acme"
isActive: true
hasAnalyticsAccess: false
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/accounts/follower-stats:
get:
operationId: getFollowerStats
tags: [Accounts, Analytics]
summary: Get follower stats
description: |
Returns follower count history and growth metrics for connected social accounts.
Requires analytics add-on subscription. Follower counts are refreshed once per day.
parameters:
- name: accountIds
in: query
schema: { type: string }
description: Comma-separated list of account IDs (optional, defaults to all user's accounts)
- name: profileId
in: query
schema: { type: string }
description: Filter by profile ID
- name: fromDate
in: query
schema: { type: string, format: date }
description: Start date in YYYY-MM-DD format (defaults to 30 days ago)
- name: toDate
in: query
schema: { type: string, format: date }
description: End date in YYYY-MM-DD format (defaults to today)
- name: granularity
in: query
schema: { type: string, enum: [daily, weekly, monthly], default: daily }
description: Data aggregation level
responses:
'200':
description: Follower stats
content:
application/json:
schema:
type: object
properties:
accounts:
type: array
items:
$ref: '#/components/schemas/AccountWithFollowerStats'
stats:
type: object
additionalProperties:
type: array
items:
type: object
properties:
date: { type: string, format: date }
followers: { type: number }
dateRange:
type: object
properties:
from: { type: string, format: date-time }
to: { type: string, format: date-time }
granularity: { type: string }
examples:
example:
value:
accounts:
- _id: "64e1..."
platform: "twitter"
username: "@acme"
currentFollowers: 1250
growth: 50
growthPercentage: 4.17
dataPoints: 30
stats:
"64e1...":
- date: "2024-01-01"
followers: 1200
- date: "2024-01-02"
followers: 1250
dateRange:
from: "2024-01-01T00:00:00.000Z"
to: "2024-01-31T23:59:59.999Z"
granularity: "daily"
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Analytics add-on required
content:
application/json:
schema:
type: object
properties:
error: { type: string, example: Analytics add-on required }
message: { type: string, example: Follower stats tracking requires the Analytics add-on. Please upgrade to access this feature. }
requiresAddon: { type: boolean, example: true }
/v1/accounts/{accountId}:
put:
operationId: updateAccount
tags: [Accounts]
summary: Update account
description: Updates a connected social account's display name or username override.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
username: { type: string }
displayName: { type: string }
example:
displayName: "Acme Corporation Official"
responses:
'200':
description: Updated
content:
application/json:
schema:
type: object
properties:
message: { type: string }
username: { type: string }
displayName: { type: string }
example:
message: "Account updated successfully"
username: "@acmecorp"
displayName: "Acme Corporation Official"
'400': { description: Invalid request }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
delete:
operationId: deleteAccount
tags: [Accounts]
summary: Disconnect account
description: Disconnects and removes a connected social account.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Disconnected
content:
application/json:
schema:
type: object
properties:
message: { type: string }
example:
message: "Account disconnected successfully"
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
/v1/accounts/health:
get:
operationId: getAllAccountsHealth
tags: [Accounts]
summary: Check accounts health
description: Returns health status of all connected accounts including token validity, permissions, and issues needing attention.
parameters:
- name: profileId
in: query
description: Filter by profile ID
schema: { type: string }
- name: platform
in: query
description: Filter by platform
schema:
type: string
enum: [facebook, instagram, linkedin, twitter, tiktok, youtube, threads, pinterest, reddit, bluesky, googlebusiness, telegram, snapchat]
- name: status
in: query
description: Filter by health status
schema:
type: string
enum: [healthy, warning, error]
responses:
'200':
description: Account health summary
content:
application/json:
schema:
type: object
properties:
summary:
type: object
properties:
total: { type: integer, description: Total number of accounts }
healthy: { type: integer, description: Number of healthy accounts }
warning: { type: integer, description: Number of accounts with warnings }
error: { type: integer, description: Number of accounts with errors }
needsReconnect: { type: integer, description: Number of accounts needing reconnection }
accounts:
type: array
items:
type: object
properties:
accountId: { type: string }
platform: { type: string }
username: { type: string }
displayName: { type: string }
profileId: { type: string }
status: { type: string, enum: [healthy, warning, error] }
canPost: { type: boolean }
canFetchAnalytics: { type: boolean }
tokenValid: { type: boolean }
tokenExpiresAt: { type: string, format: date-time }
needsReconnect: { type: boolean }
issues: { type: array, items: { type: string } }
example:
summary:
total: 5
healthy: 3
warning: 1
error: 1
needsReconnect: 1
accounts:
- accountId: "abc123"
platform: "instagram"
username: "myaccount"
status: "healthy"
canPost: true
canFetchAnalytics: true
tokenValid: true
tokenExpiresAt: "2025-06-15T00:00:00Z"
needsReconnect: false
issues: []
- accountId: "def456"
platform: "twitter"
username: "mytwitter"
status: "error"
canPost: false
canFetchAnalytics: false
tokenValid: false
needsReconnect: true
issues: ["Token expired"]
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/accounts/{accountId}/health:
get:
operationId: getAccountHealth
tags: [Accounts]
summary: Check account health
description: Returns detailed health info for a specific account including token status, permissions, and recommendations.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
description: The account ID to check
responses:
'200':
description: Account health details
content:
application/json:
schema:
type: object
properties:
accountId: { type: string }
platform: { type: string }
username: { type: string }
displayName: { type: string }
status:
type: string
enum: [healthy, warning, error]
description: Overall health status
tokenStatus:
type: object
properties:
valid: { type: boolean, description: Whether the token is valid }
expiresAt: { type: string, format: date-time }
expiresIn: { type: string, description: Human-readable time until expiry }
needsRefresh: { type: boolean, description: Whether token expires within 24 hours }
permissions:
type: object
properties:
posting:
type: array
items:
type: object
properties:
scope: { type: string }
granted: { type: boolean }
required: { type: boolean }
analytics:
type: array
items:
type: object
properties:
scope: { type: string }
granted: { type: boolean }
required: { type: boolean }
optional:
type: array
items:
type: object
properties:
scope: { type: string }
granted: { type: boolean }
required: { type: boolean }
canPost: { type: boolean }
canFetchAnalytics: { type: boolean }
missingRequired: { type: array, items: { type: string } }
issues:
type: array
items: { type: string }
description: List of issues found
recommendations:
type: array
items: { type: string }
description: Actionable recommendations to fix issues
example:
accountId: "abc123"
platform: "instagram"
username: "myaccount"
displayName: "My Account"
status: "healthy"
tokenStatus:
valid: true
expiresAt: "2025-06-15T00:00:00Z"
expiresIn: "180 days"
needsRefresh: false
permissions:
posting:
- scope: "instagram_basic"
granted: true
required: true
- scope: "instagram_content_publish"
granted: true
required: true
analytics:
- scope: "instagram_manage_insights"
granted: true
required: false
optional: []
canPost: true
canFetchAnalytics: true
missingRequired: []
issues: []
recommendations: []
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
/v1/api-keys:
get:
operationId: listApiKeys
tags: [API Keys]
summary: List keys
description: Returns all API keys for the authenticated user. Keys are returned with a preview only, not the full key value.
responses:
'200':
description: API keys
content:
application/json:
schema:
type: object
properties:
apiKeys:
type: array
items: { $ref: '#/components/schemas/ApiKey' }
example:
apiKeys:
- id: "6507a1b2c3d4e5f6a7b8c9d0"
name: "Production API Key"
keyPreview: "sk_12345678...abcdef01"
expiresAt: "2025-12-31T23:59:59Z"
createdAt: "2024-01-15T10:30:00Z"
scope: "full"
profileIds: []
permission: "read-write"
- id: "6507a1b2c3d4e5f6a7b8c9d1"
name: "Analytics Read-Only"
keyPreview: "sk_87654321...12345678"
expiresAt: null
createdAt: "2024-03-20T14:45:00Z"
scope: "profiles"
profileIds:
- _id: "6507a1b2c3d4e5f6a7b8c9d0"
name: "Main Brand"
color: "#ffeda0"
permission: "read"
'401': { $ref: '#/components/responses/Unauthorized' }
post:
operationId: createApiKey
tags: [API Keys]
summary: Create key
description: Creates a new API key with an optional expiry. The full key value is only returned once in the response.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name]
properties:
name: { type: string }
expiresIn:
type: integer
description: Days until expiry
scope:
type: string
enum: [full, profiles]
description: "'full' grants access to all profiles (default), 'profiles' restricts to specific profiles"
default: full
profileIds:
type: array
items: { type: string }
description: Profile IDs this key can access. Required when scope is 'profiles'.
permission:
type: string
enum: [read-write, read]
description: "'read-write' allows all operations (default), 'read' restricts to GET requests only"
default: read-write
example:
name: "Analytics Read-Only Key"
scope: "profiles"
profileIds: ["6507a1b2c3d4e5f6a7b8c9d0"]
permission: "read"
responses:
'201':
description: Created
content:
application/json:
schema:
type: object
properties:
message: { type: string }
apiKey: { $ref: '#/components/schemas/ApiKey' }
example:
message: "API key created successfully"
apiKey:
id: "6507a1b2c3d4e5f6a7b8c9d0"
name: "Analytics Read-Only Key"
keyPreview: "sk_12345678...90abcdef"
key: "sk_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
expiresAt: null
createdAt: "2024-01-15T10:30:00Z"
scope: "profiles"
profileIds:
- _id: "6507a1b2c3d4e5f6a7b8c9d0"
name: "Main Brand"
color: "#ffeda0"
permission: "read"
'400': { description: Invalid request (missing name, invalid scope/permission, or missing profileIds when scope is 'profiles') }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/api-keys/{keyId}:
delete:
operationId: deleteApiKey
tags: [API Keys]
summary: Delete key
description: Permanently revokes and deletes an API key.
parameters:
- name: keyId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Deleted
content:
application/json:
schema:
type: object
properties:
message: { type: string }
example:
message: "API key deleted successfully"
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
/v1/invite/tokens:
post:
operationId: createInviteToken
tags: [Invites]
summary: Create invite token
description: |
Generate a secure invite link to grant team members access to your profiles.
Invites expire after 7 days and are single-use.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [scope]
properties:
scope:
type: string
enum: [all, profiles]
description: "'all' grants access to all profiles, 'profiles' restricts to specific profiles"
profileIds:
type: array
items: { type: string }
description: Required if scope is 'profiles'. Array of profile IDs to grant access to.
example:
scope: "profiles"
profileIds:
- "64f0a1b2c3d4e5f6a7b8c9d0"
- "64f0a1b2c3d4e5f6a7b8c9d1"
responses:
'201':
description: Invite token created
content:
application/json:
schema:
type: object
properties:
token: { type: string }
scope: { type: string }
invitedProfileIds:
type: array
items: { type: string }
expiresAt: { type: string, format: date-time }
inviteUrl: { type: string, format: uri }
example:
token: "inv_abc123def456ghi789"
scope: "profiles"
invitedProfileIds:
- "64f0a1b2c3d4e5f6a7b8c9d0"
- "64f0a1b2c3d4e5f6a7b8c9d1"
expiresAt: "2024-11-08T10:30:00Z"
inviteUrl: "https://getlate.dev/invite/inv_abc123def456ghi789"
'400': { description: Invalid request }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { description: One or more profiles not found or not owned }
/v1/connect/{platform}:
get:
operationId: getConnectUrl
tags: [Connect]
summary: Get OAuth connect URL
description: |
Initiate an OAuth connection flow. Returns an authUrl to redirect the user to.
Standard flow: Late hosts the selection UI, then redirects to your redirect_url. Headless mode (headless=true): user is redirected to your redirect_url with OAuth data for custom UI. Use the platform-specific selection endpoints to complete.
parameters:
- name: platform
in: path
required: true
schema:
type: string
enum: [facebook, instagram, linkedin, twitter, tiktok, youtube, threads, reddit, pinterest, bluesky, googlebusiness, telegram, snapchat]
description: Social media platform to connect
- name: profileId
in: query
required: true
schema: { type: string }
description: Your Late profile ID (get from /v1/profiles)
- name: redirect_url
in: query
schema: { type: string, format: uri }
description: Your custom redirect URL after connection completes. Standard mode appends ?connected={platform}&profileId=X&username=Y. Headless mode appends OAuth data params.
security:
- bearerAuth: []
responses:
'200':
description: OAuth authorization URL to redirect user to
content:
application/json:
schema:
type: object
properties:
authUrl:
type: string
format: uri
description: URL to redirect your user to for OAuth authorization
state:
type: string
description: State parameter for security (handled automatically)
example:
authUrl: "https://www.facebook.com/v21.0/dialog/oauth?client_id=..."
state: "user123-profile456-1234567890-https://yourdomain.com/callback"
'400':
description: "Missing/invalid parameters (e.g., invalid profileId format)"
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: "No access to profile, or BYOK required for AppSumo Twitter"
'404':
description: Profile not found
post:
operationId: handleOAuthCallback
tags: [Connect]
summary: Complete OAuth callback
description: Exchange the OAuth authorization code for tokens and connect the account to the specified profile.
parameters:
- name: platform
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [code, state, profileId]
properties:
code: { type: string }
state: { type: string }
profileId: { type: string }
responses:
'200': { description: Account connected }
'400': { description: Invalid params }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { description: BYOK required for AppSumo Twitter }
'500': { description: Failed to connect account }
/v1/connect/facebook/select-page:
get:
operationId: listFacebookPages
tags: [Connect]
summary: List Facebook pages
description: Returns the list of Facebook Pages the user can manage after OAuth. Extract tempToken and userProfile from the OAuth redirect params and pass them here. Use the X-Connect-Token header if connecting via API key.
parameters:
- name: profileId
in: query
required: true
schema: { type: string }
description: Profile ID from your connection flow
- name: tempToken
in: query
required: true
schema: { type: string }
description: Temporary Facebook access token from the OAuth callback redirect
security:
- bearerAuth: []
- connectToken: []
responses:
'200':
description: List of Facebook Pages available for connection
content:
application/json:
schema:
type: object
properties:
pages:
type: array
items:
type: object
properties:
id: { type: string, description: Facebook Page ID }
name: { type: string, description: Page name }
username: { type: string, description: Page username/handle (may be null) }
access_token: { type: string, description: Page-specific access token }
category: { type: string, description: Page category }
tasks: { type: array, items: { type: string }, description: User permissions for this page }
example:
pages:
- id: "123456789"
name: "My Brand Page"
username: "mybrand"
access_token: "EAAxxxxx..."
category: "Brand"
tasks: ["MANAGE", "CREATE_CONTENT"]
'400': { description: Missing required parameters (profileId or tempToken) }
'401': { $ref: '#/components/responses/Unauthorized' }
'500':
description: Failed to fetch pages (e.g., invalid token, insufficient permissions)
content:
application/json:
schema:
type: object
properties:
error: { type: string }
post:
operationId: selectFacebookPage
tags: [Connect]
summary: Select Facebook page
description: Complete the headless flow by saving the user's selected Facebook page. Pass the userProfile from the OAuth redirect and use X-Connect-Token if connecting via API key.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [profileId, pageId, tempToken]
properties:
profileId:
type: string
description: Profile ID from your connection flow
pageId:
type: string
description: The Facebook Page ID selected by the user
tempToken:
type: string
description: Temporary Facebook access token from OAuth
userProfile:
type: object
description: Decoded user profile object from the OAuth callback
properties:
id: { type: string }
name: { type: string }
profilePicture: { type: string }
redirect_url:
type: string
format: uri
description: Optional custom redirect URL to return to after selection
example:
profileId: "507f1f77bcf86cd799439011"
pageId: "123456789"
tempToken: "EAAxxxxx..."
userProfile:
id: "987654321"
name: "John Doe"
profilePicture: "https://..."
redirect_url: "https://yourdomain.com/integrations/callback"
security:
- bearerAuth: []
- connectToken: []
responses:
'200':
description: Facebook Page connected successfully
content:
application/json:
schema:
type: object
properties:
message: { type: string }
redirect_url:
type: string
description: Redirect URL if custom redirect_url was provided
account:
type: object
properties:
accountId:
type: string
description: ID of the created SocialAccount
platform: { type: string, enum: [facebook] }
username: { type: string }
displayName: { type: string }
profilePicture: { type: string }
isActive: { type: boolean }
selectedPageName: { type: string }
example:
message: "Facebook page connected successfully"
redirect_url: "https://yourdomain.com/integrations/callback?connected=facebook&profileId=507f1f77bcf86cd799439011&username=My+Brand+Page"
account:
accountId: "64e1f0a9e2b5af0012ab34cd"
platform: "facebook"
username: "mybrand"
displayName: "My Brand Page"
profilePicture: "https://..."
isActive: true
selectedPageName: "My Brand Page"
'400':
description: "Missing required fields (profileId, pageId, or tempToken)"
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: User does not have access to the specified profile
'404':
description: Selected page not found in available pages
'500':
description: Failed to save Facebook connection
/v1/connect/googlebusiness/locations:
get:
operationId: listGoogleBusinessLocations
tags: [Connect]
summary: List GBP locations
description: For headless flows. Returns the list of GBP locations the user can manage. Use X-Connect-Token if connecting via API key.
parameters:
- name: profileId
in: query
required: true
schema: { type: string }
description: Profile ID from your connection flow
- name: tempToken
in: query
required: true
schema: { type: string }
description: Temporary Google access token from the OAuth callback redirect
security:
- bearerAuth: []
- connectToken: []
responses:
'200':
description: List of Google Business locations available for connection
content:
application/json:
schema:
type: object
properties:
locations:
type: array
items:
type: object
properties:
id: { type: string, description: Location ID }
name: { type: string, description: Business name }
accountId: { type: string, description: Google Business Account ID }
accountName: { type: string, description: Account name }
address: { type: string, description: Business address }
category: { type: string, description: Business category }
example:
locations:
- id: "9281089117903930794"
name: "My Coffee Shop"
accountId: "accounts/113303573364907650416"
accountName: "My Business Account"
address: "123 Main St, City, Country"
category: "Coffee shop"
'400': { description: Missing required parameters (profileId or tempToken) }
'401': { $ref: '#/components/responses/Unauthorized' }
'500':
description: Failed to fetch locations (e.g., invalid token, insufficient permissions)
content:
application/json:
schema:
type: object
properties:
error: { type: string }
/v1/connect/googlebusiness/select-location:
post:
operationId: selectGoogleBusinessLocation
tags: [Connect]
summary: Select GBP location
description: Complete the headless flow by saving the user's selected GBP location. Include userProfile from the OAuth redirect (contains refresh token). Use X-Connect-Token if connecting via API key.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [profileId, locationId, tempToken]
properties:
profileId:
type: string
description: Profile ID from your connection flow
locationId:
type: string
description: The Google Business location ID selected by the user
tempToken:
type: string
description: Temporary Google access token from OAuth
userProfile:
type: object
description: Decoded user profile from the OAuth callback. Contains the refresh token. Always include this field.
properties:
id: { type: string }
name: { type: string }
refreshToken: { type: string, description: Google refresh token for long-lived access }
tokenExpiresIn: { type: integer, description: Token expiration time in seconds }
scope: { type: string, description: Granted OAuth scopes }
redirect_url:
type: string
format: uri
description: Optional custom redirect URL to return to after selection
example:
profileId: "507f1f77bcf86cd799439011"
locationId: "9281089117903930794"
tempToken: "ya29.xxxxx..."
userProfile:
id: "113303573364907650416"
name: "John Doe"
refreshToken: "1//0gxxxxx..."
tokenExpiresIn: 3599
scope: "https://www.googleapis.com/auth/business.manage"
redirect_url: "https://yourdomain.com/integrations/callback"
security:
- bearerAuth: []
- connectToken: []
responses:
'200':
description: Google Business location connected successfully
content:
application/json:
schema:
type: object
properties:
message: { type: string }
redirect_url:
type: string
description: Redirect URL if custom redirect_url was provided
account:
type: object
properties:
accountId:
type: string
description: ID of the created SocialAccount
platform: { type: string, enum: [googlebusiness] }
username: { type: string }
displayName: { type: string }
isActive: { type: boolean }
selectedLocationName: { type: string }
selectedLocationId: { type: string }
example:
message: "Google Business location connected successfully"
redirect_url: "https://yourdomain.com/integrations/callback?connected=googlebusiness&profileId=507f1f77bcf86cd799439011&username=My+Coffee+Shop"
account:
accountId: "64e1f0a9e2b5af0012ab34cd"
platform: "googlebusiness"
username: "My Coffee Shop"
displayName: "My Coffee Shop"
isActive: true
selectedLocationName: "My Coffee Shop"
selectedLocationId: "9281089117903930794"
'400':
description: "Missing required fields (profileId, locationId, or tempToken)"
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: User does not have access to the specified profile
'404':
description: Selected location not found in available locations
'500':
description: Failed to save Google Business connection
/v1/accounts/{accountId}/gmb-reviews:
get:
operationId: getGoogleBusinessReviews
tags: [GMB Reviews]
summary: Get reviews
description: Returns reviews for a GBP account including ratings, comments, and owner replies. Use nextPageToken for pagination.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
description: The Late account ID (from /v1/accounts)
- name: pageSize
in: query
schema: { type: integer, minimum: 1, maximum: 50, default: 50 }
description: Number of reviews to fetch per page (max 50)
- name: pageToken
in: query
schema: { type: string }
description: Pagination token from previous response
security:
- bearerAuth: []
responses:
'200':
description: Reviews fetched successfully
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
accountId: { type: string }
locationId: { type: string }
reviews:
type: array
items:
type: object
properties:
id: { type: string, description: Review ID }
name: { type: string, description: Full resource name }
reviewer:
type: object
properties:
displayName: { type: string }
profilePhotoUrl: { type: string, nullable: true }
isAnonymous: { type: boolean }
rating: { type: integer, minimum: 1, maximum: 5, description: Numeric star rating }
starRating: { type: string, enum: [ONE, TWO, THREE, FOUR, FIVE], description: Google's string rating }
comment: { type: string, description: Review text }
createTime: { type: string, format: date-time }
updateTime: { type: string, format: date-time }
reviewReply:
type: object
nullable: true
properties:
comment: { type: string, description: Business owner reply }
updateTime: { type: string, format: date-time }
averageRating: { type: number, description: Overall average rating }
totalReviewCount: { type: integer, description: Total number of reviews }
nextPageToken: { type: string, nullable: true, description: Token for next page }
example:
success: true
accountId: "64e1f0a9e2b5af0012ab34cd"
locationId: "9281089117903930794"
reviews:
- id: "AIe9_BGx1234567890"
name: "accounts/123456789/locations/9281089117903930794/reviews/AIe9_BGx1234567890"
reviewer:
displayName: "John Smith"
profilePhotoUrl: "https://lh3.googleusercontent.com/a/..."
isAnonymous: false
rating: 5
starRating: "FIVE"
comment: "Great service and friendly staff! Highly recommend."
createTime: "2024-01-15T10:30:00Z"
updateTime: "2024-01-15T10:30:00Z"
reviewReply:
comment: "Thank you for your kind words! We appreciate your support."
updateTime: "2024-01-16T08:00:00Z"
- id: "AIe9_BGx0987654321"
name: "accounts/123456789/locations/9281089117903930794/reviews/AIe9_BGx0987654321"
reviewer:
displayName: "Anonymous"
profilePhotoUrl: null
isAnonymous: true
rating: 4
starRating: "FOUR"
comment: "Good experience overall."
createTime: "2024-01-10T14:20:00Z"
updateTime: "2024-01-10T14:20:00Z"
reviewReply: null
averageRating: 4.5
totalReviewCount: 125
nextPageToken: "CiAKHAoUMTIzNDU2Nzg5"
'400':
description: Invalid request - not a Google Business account or missing location
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
example:
error: "This endpoint is only available for Google Business Profile accounts"
'401':
description: Unauthorized or token expired
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
example:
error: "Access token expired. Please reconnect your Google Business Profile account."
'403':
description: Permission denied for this location
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
example:
error: "You do not have permission to access reviews for this location."
'404': { $ref: '#/components/responses/NotFound' }
'500':
description: Failed to fetch reviews
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
/v1/accounts/{accountId}/gmb-food-menus:
get:
operationId: getGoogleBusinessFoodMenus
tags: [GMB Food Menus]
summary: Get food menus
description: Returns food menus for a GBP location including sections, items, pricing, and dietary info. Only for locations with food menu support.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
description: The Late account ID (from /v1/accounts)
security:
- bearerAuth: []
responses:
'200':
description: Food menus fetched successfully
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
accountId: { type: string }
locationId: { type: string }
name: { type: string, description: Resource name of the food menus }
menus:
type: array
items:
$ref: '#/components/schemas/FoodMenu'
example:
success: true
accountId: "64e1f0a9e2b5af0012ab34cd"
locationId: "9281089117903930794"
name: "accounts/123456789/locations/9281089117903930794/foodMenus"
menus:
- labels:
- displayName: "Lunch Menu"
description: "Available 11am-3pm"
languageCode: "en"
sections:
- labels:
- displayName: "Appetizers"
items:
- labels:
- displayName: "Caesar Salad"
description: "Romaine, parmesan, croutons"
attributes:
price:
currencyCode: "USD"
units: "12"
dietaryRestriction: ["VEGETARIAN"]
'400':
description: Invalid request - not a Google Business account or missing location
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
example:
error: "This endpoint is only available for Google Business Profile accounts"
'401':
description: Unauthorized or token expired
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
example:
error: "Access token expired. Please reconnect your Google Business Profile account."
'403':
description: Permission denied for this location
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
example:
error: "You do not have permission to access food menus for this location."
'404': { $ref: '#/components/responses/NotFound' }
'500':
description: Failed to fetch food menus
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
put:
operationId: updateGoogleBusinessFoodMenus
tags: [GMB Food Menus]
summary: Update food menus
description: Updates food menus for a GBP location. Send the full menus array. Use updateMask for partial updates.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
description: The Late account ID (from /v1/accounts)
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [menus]
properties:
menus:
type: array
items:
$ref: '#/components/schemas/FoodMenu'
description: Array of food menus to set
updateMask:
type: string
description: Field mask for partial updates (e.g. "menus")
example:
menus:
- labels:
- displayName: "Dinner Menu"
languageCode: "en"
sections:
- labels:
- displayName: "Mains"
items:
- labels:
- displayName: "Grilled Salmon"
description: "With seasonal vegetables"
attributes:
price:
currencyCode: "USD"
units: "24"
allergen: ["FISH"]
updateMask: "menus"
responses:
'200':
description: Food menus updated successfully
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
accountId: { type: string }
locationId: { type: string }
name: { type: string }
menus:
type: array
items:
$ref: '#/components/schemas/FoodMenu'
'400':
description: Invalid request
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
example:
error: "Request body must include a \"menus\" array"
'401':
description: Unauthorized or token expired
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
'403':
description: Permission denied for this location
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
'404': { $ref: '#/components/responses/NotFound' }
'500':
description: Failed to update food menus
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
/v1/accounts/{accountId}/gmb-location-details:
get:
operationId: getGoogleBusinessLocationDetails
tags: [GMB Location Details]
summary: Get location details
description: Returns detailed GBP location info (hours, description, phone, website, categories). Use readMask to request specific fields.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
description: The Late account ID (from /v1/accounts)
- name: readMask
in: query
required: false
schema: { type: string }
description: "Comma-separated fields to return. Available: name, title, phoneNumbers, categories, storefrontAddress, websiteUri, regularHours, specialHours, serviceArea, profile, openInfo, metadata, moreHours."
security:
- bearerAuth: []
responses:
'200':
description: Location details fetched successfully
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
accountId: { type: string }
locationId: { type: string }
title: { type: string, description: Business name }
regularHours:
type: object
properties:
periods:
type: array
items:
type: object
properties:
openDay: { type: string, enum: [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY] }
openTime: { type: string, description: "Opening time in HH:MM format" }
closeDay: { type: string }
closeTime: { type: string }
specialHours:
type: object
properties:
specialHourPeriods:
type: array
items:
type: object
properties:
startDate: { type: object, properties: { year: { type: integer }, month: { type: integer }, day: { type: integer } } }
endDate: { type: object, properties: { year: { type: integer }, month: { type: integer }, day: { type: integer } } }
openTime: { type: string }
closeTime: { type: string }
closed: { type: boolean }
profile:
type: object
properties:
description: { type: string, description: Business description }
websiteUri: { type: string }
phoneNumbers:
type: object
properties:
primaryPhone: { type: string }
additionalPhones: { type: array, items: { type: string } }
example:
success: true
accountId: "64e1f0a9e2b5af0012ab34cd"
locationId: "9281089117903930794"
title: "Joe's Pizza"
regularHours:
periods:
- openDay: "MONDAY"
openTime: "11:00"
closeDay: "MONDAY"
closeTime: "22:00"
- openDay: "TUESDAY"
openTime: "11:00"
closeDay: "TUESDAY"
closeTime: "22:00"
specialHours:
specialHourPeriods:
- startDate: { year: 2026, month: 12, day: 25 }
closed: true
profile:
description: "Authentic New York style pizza since 1985"
websiteUri: "https://joespizza.com"
'400':
description: Invalid request
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
'401':
description: Unauthorized or token expired
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
'404': { $ref: '#/components/responses/NotFound' }
put:
operationId: updateGoogleBusinessLocationDetails
tags: [GMB Location Details]
summary: Update location details
description: Updates GBP location details (hours, description, phone, website). The updateMask field is required and specifies which fields to update.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
description: The Late account ID (from /v1/accounts)
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [updateMask]
properties:
updateMask:
type: string
description: "Required. Comma-separated fields to update (e.g. 'regularHours', 'specialHours', 'profile.description')"
regularHours:
type: object
properties:
periods:
type: array
items:
type: object
properties:
openDay: { type: string }
openTime: { type: string }
closeDay: { type: string }
closeTime: { type: string }
specialHours:
type: object
properties:
specialHourPeriods:
type: array
items:
type: object
properties:
startDate: { type: object, properties: { year: { type: integer }, month: { type: integer }, day: { type: integer } } }
endDate: { type: object, properties: { year: { type: integer }, month: { type: integer }, day: { type: integer } } }
openTime: { type: string }
closeTime: { type: string }
closed: { type: boolean }
profile:
type: object
properties:
description: { type: string }
websiteUri: { type: string }
phoneNumbers:
type: object
properties:
primaryPhone: { type: string }
additionalPhones: { type: array, items: { type: string } }
example:
updateMask: "regularHours,specialHours"
regularHours:
periods:
- openDay: "MONDAY"
openTime: "09:00"
closeDay: "MONDAY"
closeTime: "17:00"
- openDay: "SATURDAY"
openTime: "10:00"
closeDay: "SATURDAY"
closeTime: "14:00"
specialHours:
specialHourPeriods:
- startDate: { year: 2026, month: 12, day: 25 }
closed: true
- startDate: { year: 2026, month: 12, day: 31 }
openTime: "09:00"
closeTime: "15:00"
responses:
'200':
description: Location updated successfully
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
accountId: { type: string }
locationId: { type: string }
'400':
description: Invalid request or missing updateMask
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
'401':
description: Unauthorized or token expired
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
'404': { $ref: '#/components/responses/NotFound' }
/v1/accounts/{accountId}/gmb-media:
get:
operationId: listGoogleBusinessMedia
tags: [GMB Media]
summary: List media
description: |
Lists media items (photos) for a Google Business Profile location.
Returns photo URLs, descriptions, categories, and metadata.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
- name: pageSize
in: query
schema: { type: integer, maximum: 100, default: 100 }
description: Number of items to return (max 100)
- name: pageToken
in: query
schema: { type: string }
description: Pagination token from previous response
security:
- bearerAuth: []
responses:
'200':
description: Media items fetched successfully
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
accountId: { type: string }
locationId: { type: string }
mediaItems:
type: array
items:
type: object
properties:
name: { type: string, description: Resource name }
mediaFormat: { type: string, enum: [PHOTO, VIDEO] }
sourceUrl: { type: string }
googleUrl: { type: string, description: Google-hosted URL }
thumbnailUrl: { type: string }
description: { type: string }
createTime: { type: string, format: date-time }
locationAssociation:
type: object
properties:
category: { type: string }
nextPageToken: { type: string }
totalMediaItemsCount: { type: integer }
'400':
description: Invalid request
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
'401':
description: Unauthorized
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
post:
operationId: createGoogleBusinessMedia
tags: [GMB Media]
summary: Upload photo
description: |
Creates a media item (photo) for a location from a publicly accessible URL.
Categories determine where the photo appears: COVER, PROFILE, LOGO, EXTERIOR, INTERIOR, FOOD_AND_DRINK, MENU, PRODUCT, TEAMS, ADDITIONAL.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [sourceUrl]
properties:
sourceUrl: { type: string, description: Publicly accessible image URL }
mediaFormat: { type: string, enum: [PHOTO, VIDEO], default: PHOTO }
description: { type: string, description: Photo description }
category:
type: string
enum: [COVER, PROFILE, LOGO, EXTERIOR, INTERIOR, FOOD_AND_DRINK, MENU, PRODUCT, TEAMS, ADDITIONAL]
description: Where the photo appears on the listing
example:
sourceUrl: "https://example.com/photos/restaurant-interior.jpg"
description: "Dining area with outdoor seating"
category: "INTERIOR"
responses:
'200':
description: Media created successfully
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
name: { type: string }
mediaFormat: { type: string }
googleUrl: { type: string }
'400':
description: Invalid request or unsupported media format
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
'401':
description: Unauthorized
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
delete:
operationId: deleteGoogleBusinessMedia
tags: [GMB Media]
summary: Delete photo
description: Deletes a photo or media item from a GBP location.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
- name: mediaId
in: query
required: true
schema: { type: string }
description: The media item ID to delete
security:
- bearerAuth: []
responses:
'200':
description: Media deleted successfully
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
deleted: { type: boolean }
mediaId: { type: string }
'400':
description: Invalid request
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
'401':
description: Unauthorized
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
/v1/accounts/{accountId}/gmb-attributes:
get:
operationId: getGoogleBusinessAttributes
tags: [GMB Attributes]
summary: Get attributes
description: Returns GBP location attributes (amenities, services, accessibility, payment types). Available attributes vary by business category.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
security:
- bearerAuth: []
responses:
'200':
description: Attributes fetched successfully
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
accountId: { type: string }
locationId: { type: string }
attributes:
type: array
items:
type: object
properties:
name: { type: string, description: "Attribute identifier (e.g. has_delivery)" }
valueType: { type: string, description: "Value type (BOOL, ENUM, URL, REPEATED_ENUM)" }
values: { type: array, items: {} }
repeatedEnumValue:
type: object
properties:
setValues: { type: array, items: { type: string } }
unsetValues: { type: array, items: { type: string } }
example:
success: true
attributes:
- name: "has_delivery"
valueType: "BOOL"
values: [true]
- name: "has_takeout"
valueType: "BOOL"
values: [true]
- name: "has_outdoor_seating"
valueType: "BOOL"
values: [true]
- name: "pay_credit_card_types_accepted"
valueType: "REPEATED_ENUM"
repeatedEnumValue:
setValues: ["visa", "mastercard", "amex"]
'400':
description: Invalid request
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
'401':
description: Unauthorized
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
put:
operationId: updateGoogleBusinessAttributes
tags: [GMB Attributes]
summary: Update attributes
description: |
Updates location attributes (amenities, services, etc.).
The attributeMask specifies which attributes to update (comma-separated).
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [attributes, attributeMask]
properties:
attributes:
type: array
items:
type: object
properties:
name: { type: string }
values: { type: array, items: {} }
repeatedEnumValue:
type: object
properties:
setValues: { type: array, items: { type: string } }
unsetValues: { type: array, items: { type: string } }
attributeMask:
type: string
description: "Comma-separated attribute names to update (e.g. 'has_delivery,has_takeout')"
example:
attributes:
- name: "has_delivery"
values: [true]
- name: "has_takeout"
values: [true]
- name: "has_outdoor_seating"
values: [false]
attributeMask: "has_delivery,has_takeout,has_outdoor_seating"
responses:
'200':
description: Attributes updated successfully
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
accountId: { type: string }
locationId: { type: string }
attributes: { type: array, items: { type: object } }
'400':
description: Invalid request
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
'401':
description: Unauthorized
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
/v1/accounts/{accountId}/gmb-place-actions:
get:
operationId: listGoogleBusinessPlaceActions
tags: [GMB Place Actions]
summary: List action links
description: |
Lists place action links for a Google Business Profile location.
Place actions are the booking, ordering, and reservation buttons that appear on your listing.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
- name: pageSize
in: query
schema: { type: integer, maximum: 100, default: 100 }
- name: pageToken
in: query
schema: { type: string }
security:
- bearerAuth: []
responses:
'200':
description: Place actions fetched successfully
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
accountId: { type: string }
locationId: { type: string }
placeActionLinks:
type: array
items:
type: object
properties:
name: { type: string, description: Resource name }
uri: { type: string, description: Action URL }
placeActionType: { type: string }
createTime: { type: string, format: date-time }
updateTime: { type: string, format: date-time }
nextPageToken: { type: string }
example:
success: true
placeActionLinks:
- name: "locations/123/placeActionLinks/456"
uri: "https://order.ubereats.com/joespizza"
placeActionType: "FOOD_ORDERING"
- name: "locations/123/placeActionLinks/789"
uri: "https://www.opentable.com/joespizza"
placeActionType: "DINING_RESERVATION"
'400':
description: Invalid request
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
'401':
description: Unauthorized
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
post:
operationId: createGoogleBusinessPlaceAction
tags: [GMB Place Actions]
summary: Create action link
description: |
Creates a place action link for a location.
Available action types: APPOINTMENT, ONLINE_APPOINTMENT, DINING_RESERVATION, FOOD_ORDERING, FOOD_DELIVERY, FOOD_TAKEOUT, SHOP_ONLINE.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [uri, placeActionType]
properties:
uri: { type: string, description: The action URL }
placeActionType:
type: string
enum: [APPOINTMENT, ONLINE_APPOINTMENT, DINING_RESERVATION, FOOD_ORDERING, FOOD_DELIVERY, FOOD_TAKEOUT, SHOP_ONLINE]
description: Type of action
example:
uri: "https://order.ubereats.com/joespizza"
placeActionType: "FOOD_ORDERING"
responses:
'200':
description: Place action created successfully
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
name: { type: string, description: Resource name of the created link }
uri: { type: string }
placeActionType: { type: string }
'400':
description: Invalid request
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
'401':
description: Unauthorized
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
delete:
operationId: deleteGoogleBusinessPlaceAction
tags: [GMB Place Actions]
summary: Delete action link
description: Deletes a place action link (e.g. booking or ordering URL) from a GBP location.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
- name: name
in: query
required: true
schema: { type: string }
description: "The resource name of the place action link (e.g. locations/123/placeActionLinks/456)"
security:
- bearerAuth: []
responses:
'200':
description: Place action deleted successfully
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
deleted: { type: boolean }
name: { type: string }
'400':
description: Invalid request
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
'401':
description: Unauthorized
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
/v1/connect/pending-data:
get:
operationId: getPendingOAuthData
tags: [Connect]
summary: Get pending OAuth data
description: Fetch pending OAuth data for headless mode using the pendingDataToken from the redirect URL. One-time use, expires after 10 minutes. No authentication required.
parameters:
- name: token
in: query
required: true
schema: { type: string }
description: The pending data token from the OAuth redirect URL (pendingDataToken parameter)
responses:
'200':
description: OAuth data fetched successfully
content:
application/json:
schema:
type: object
properties:
platform:
type: string
description: The platform (e.g., "linkedin")
profileId:
type: string
description: The Late profile ID
tempToken:
type: string
description: Temporary access token for the platform
refreshToken:
type: string
description: Refresh token (if available)
expiresIn:
type: number
description: Token expiry in seconds
userProfile:
type: object
description: User profile data (id, username, displayName, profilePicture)
selectionType:
type: string
enum: [organizations, pages, boards, locations, profiles]
description: Type of selection data
organizations:
type: array
description: LinkedIn organizations (when selectionType is "organizations")
items:
type: object
properties:
id: { type: string }
urn: { type: string }
name: { type: string }
vanityName: { type: string }
example:
platform: "linkedin"
profileId: "abc123"
tempToken: "AQV..."
refreshToken: "AQW..."
expiresIn: 5183999
userProfile:
id: "ABC123"
username: "John Doe"
displayName: "John Doe"
profilePicture: "https://..."
selectionType: "organizations"
organizations:
- id: "12345"
urn: "urn:li:organization:12345"
name: "Acme Corp"
vanityName: "acme-corp"
- id: "67890"
urn: "urn:li:organization:67890"
name: "Example Inc"
vanityName: "example-inc"
'400':
description: Missing token parameter
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
'404':
description: Token not found or expired
content:
application/json:
schema: { $ref: '#/components/schemas/ErrorResponse' }
/v1/connect/linkedin/organizations:
get:
operationId: listLinkedInOrganizations
tags: [Connect]
summary: List LinkedIn orgs
description: Fetch full LinkedIn organization details (logos, vanity names, websites) for custom UI. No authentication required, just the tempToken from OAuth.
parameters:
- name: tempToken
in: query
required: true
schema: { type: string }
description: The temporary LinkedIn access token from the OAuth redirect
- name: orgIds
in: query
required: true
schema: { type: string }
description: Comma-separated list of organization IDs to fetch details for (max 100)
example: "12345678,87654321,11111111"
responses:
'200':
description: Organization details fetched successfully
content:
application/json:
schema:
type: object
properties:
organizations:
type: array
items:
type: object
properties:
id: { type: string, description: Organization ID }
logoUrl: { type: string, format: uri, description: Logo URL (may be absent if no logo) }
vanityName: { type: string, description: Organization's vanity name/slug }
website: { type: string, format: uri, description: Organization's website URL }
industry: { type: string, description: Organization's primary industry }
description: { type: string, description: Organization's description }
example:
organizations:
- id: "12345678"
logoUrl: "https://media.licdn.com/dms/image/v2/..."
vanityName: "acme-corp"
website: "https://acme.com"
industry: "Technology"
description: "Leading provider of innovative solutions"
- id: "87654321"
logoUrl: "https://media.licdn.com/dms/image/v2/..."
vanityName: "example-inc"
website: "https://example.com"
- id: "11111111"
'400':
description: Missing required parameters or too many organization IDs
content:
application/json:
schema:
type: object
properties:
error: { type: string }
example:
error: "Missing tempToken parameter"
'500':
description: Failed to fetch organization details
/v1/connect/linkedin/select-organization:
post:
operationId: selectLinkedInOrganization
tags: [Connect]
summary: Select LinkedIn org
description: Complete the LinkedIn connection flow. Set accountType to "personal" or "organization" to connect as a company page. Use X-Connect-Token if connecting via API key.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [profileId, tempToken, userProfile, accountType]
properties:
profileId: { type: string }
tempToken: { type: string }
userProfile: { type: object }
accountType: { type: string, enum: [personal, organization] }
selectedOrganization: { type: object }
redirect_url: { type: string, format: uri }
examples:
personalAccount:
summary: Connect as personal LinkedIn profile
description: For personal accounts, set accountType to "personal" and omit selectedOrganization
value:
profileId: "64f0a1b2c3d4e5f6a7b8c9d0"
tempToken: "AQX..."
userProfile:
id: "abc123"
username: "johndoe"
displayName: "John Doe"
profilePicture: "https://media.licdn.com/dms/image/v2/..."
accountType: "personal"
organizationAccount:
summary: Connect as org/company page
description: For organization pages, include the selectedOrganization object
value:
profileId: "64f0a1b2c3d4e5f6a7b8c9d0"
tempToken: "AQX..."
userProfile:
id: "abc123"
username: "johndoe"
displayName: "John Doe"
profilePicture: "https://media.licdn.com/dms/image/v2/..."
accountType: "organization"
selectedOrganization:
id: "12345678"
urn: "urn:li:organization:12345678"
name: "Acme Corporation"
redirect_url: "https://yourapp.com/callback"
responses:
'200':
description: LinkedIn account connected
content:
application/json:
schema:
type: object
properties:
message: { type: string }
redirect_url:
type: string
description: The redirect URL with connection params appended (only if redirect_url was provided in request)
account:
type: object
properties:
accountId:
type: string
description: ID of the created SocialAccount
platform: { type: string, enum: [linkedin] }
username: { type: string }
displayName: { type: string }
profilePicture: { type: string }
isActive: { type: boolean }
accountType: { type: string, enum: [personal, organization] }
bulkRefresh:
type: object
properties:
updatedCount: { type: integer }
errors: { type: integer }
examples:
personalAccountResponse:
summary: Personal account connected
value:
message: "LinkedIn account connected successfully"
account:
accountId: "64e1f0a9e2b5af0012ab34cd"
platform: "linkedin"
username: "johndoe"
displayName: "John Doe"
profilePicture: "https://media.licdn.com/..."
isActive: true
accountType: "personal"
organizationWithRedirect:
summary: Org account with redirect URL
value:
message: "LinkedIn account connected successfully"
redirect_url: "https://yourapp.com/callback?connected=linkedin&profileId=507f1f77bcf86cd799439011&accountId=64e1f0a9e2b5af0012ab34cd&username=Acme+Corporation"
account:
accountId: "64e1f0a9e2b5af0012ab34cd"
platform: "linkedin"
username: "acme-corp"
displayName: "Acme Corporation"
profilePicture: "https://media.licdn.com/..."
isActive: true
accountType: "organization"
bulkRefresh:
updatedCount: 5
errors: 0
'400': { description: Missing required fields }
'401': { $ref: '#/components/responses/Unauthorized' }
'500': { description: Failed to connect LinkedIn account }
/v1/connect/pinterest/select-board:
get:
operationId: listPinterestBoardsForSelection
tags: [Connect]
summary: List Pinterest boards
description: For headless flows. Returns Pinterest boards the user can post to. Use X-Connect-Token from the redirect URL.
parameters:
- name: X-Connect-Token
in: header
required: true
schema: { type: string }
description: Short-lived connect token from the OAuth redirect
- name: profileId
in: query
required: true
schema: { type: string }
description: Your Late profile ID
- name: tempToken
in: query
required: true
schema: { type: string }
description: Temporary Pinterest access token from the OAuth callback redirect
responses:
'200':
description: List of Pinterest Boards available for connection
content:
application/json:
schema:
type: object
properties:
boards:
type: array
items:
type: object
properties:
id: { type: string, description: Pinterest Board ID }
name: { type: string, description: Board name }
description: { type: string, description: Board description }
privacy: { type: string, description: Board privacy setting }
example:
boards:
- id: "123456789012345678"
name: "Marketing Ideas"
description: "Collection of marketing inspiration"
privacy: "PUBLIC"
- id: "234567890123456789"
name: "Product Photos"
description: "Product photography"
privacy: "PUBLIC"
'400': { description: Missing required parameters }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { description: No access to profile }
'500': { description: Failed to fetch boards }
post:
operationId: selectPinterestBoard
tags: [Connect]
summary: Select Pinterest board
description: |
Complete the Pinterest connection flow. After OAuth, use this endpoint to save the selected board and complete the account connection. Use the X-Connect-Token header if you initiated the connection via API key.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [profileId, boardId, tempToken]
properties:
profileId:
type: string
description: Your Late profile ID
boardId:
type: string
description: The Pinterest Board ID selected by the user
boardName:
type: string
description: The board name (for display purposes)
tempToken:
type: string
description: Temporary Pinterest access token from OAuth
userProfile:
type: object
description: User profile data from OAuth redirect
refreshToken:
type: string
description: Pinterest refresh token (if available)
expiresIn:
type: integer
description: Token expiration time in seconds
redirect_url:
type: string
format: uri
description: Custom redirect URL after connection completes
example:
profileId: "64f0a1b2c3d4e5f6a7b8c9d0"
boardId: "123456789012345678"
boardName: "Marketing Ideas"
tempToken: "pina_..."
userProfile:
id: "user123"
username: "mybrand"
displayName: "My Brand"
profilePicture: "https://i.pinimg.com/..."
redirect_url: "https://yourapp.com/callback"
responses:
'200':
description: Pinterest Board connected successfully
content:
application/json:
schema:
type: object
properties:
message: { type: string }
redirect_url: { type: string, description: Redirect URL with connection params (if provided) }
account:
type: object
properties:
accountId:
type: string
description: ID of the created SocialAccount
platform: { type: string, enum: [pinterest] }
username: { type: string }
displayName: { type: string }
profilePicture: { type: string }
isActive: { type: boolean }
defaultBoardName: { type: string }
example:
message: "Pinterest connected successfully with default board"
redirect_url: "https://yourdomain.com/integrations/callback?connected=pinterest&profileId=507f1f77bcf86cd799439011&board=Marketing+Ideas"
account:
accountId: "64e1f0a9e2b5af0012ab34cd"
platform: "pinterest"
username: "mybrand"
displayName: "My Brand"
profilePicture: "https://i.pinimg.com/..."
isActive: true
defaultBoardName: "Marketing Ideas"
'400':
description: Missing required fields
content:
application/json:
example:
error: "Missing required fields"
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: No access to profile or profile limit exceeded
content:
application/json:
examples:
forbidden:
value: { error: "Forbidden" }
limitExceeded:
value:
error: "Cannot connect to this profile. It exceeds your Pro plan limit of 5 profiles."
code: "PROFILE_LIMIT_EXCEEDED"
'500':
description: Failed to save Pinterest connection
/v1/connect/snapchat/select-profile:
get:
operationId: listSnapchatProfiles
tags: [Connect]
summary: List Snapchat profiles
description: For headless flows. Returns Snapchat Public Profiles the user can post to. Use X-Connect-Token from the redirect URL.
parameters:
- name: X-Connect-Token
in: header
required: true
schema: { type: string }
description: Short-lived connect token from the OAuth redirect
- name: profileId
in: query
required: true
schema: { type: string }
description: Your Late profile ID
- name: tempToken
in: query
required: true
schema: { type: string }
description: Temporary Snapchat access token from the OAuth callback redirect
responses:
'200':
description: List of Snapchat Public Profiles available for connection
content:
application/json:
schema:
type: object
properties:
publicProfiles:
type: array
items:
type: object
properties:
id: { type: string, description: Snapchat Public Profile ID }
display_name: { type: string, description: Public profile display name }
username: { type: string, description: Public profile username/handle }
profile_image_url: { type: string, description: Profile image URL }
subscriber_count: { type: integer, description: Number of subscribers }
example:
publicProfiles:
- id: "abc123-def456"
display_name: "My Brand"
username: "mybrand"
profile_image_url: "https://cf-st.sc-cdn.net/..."
subscriber_count: 15000
- id: "xyz789-uvw012"
display_name: "Side Project"
username: "sideproject"
profile_image_url: "https://cf-st.sc-cdn.net/..."
subscriber_count: 5000
'400': { description: Missing required parameters (profileId or tempToken) }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { description: No access to profile }
'500': { description: Failed to fetch public profiles }
post:
operationId: selectSnapchatProfile
tags: [Connect]
summary: Select Snapchat profile
description: Complete the Snapchat connection flow by saving the selected Public Profile. Snapchat requires a Public Profile to publish content. Use X-Connect-Token if connecting via API key.
parameters:
- name: X-Connect-Token
in: header
required: false
schema: { type: string }
description: Short-lived connect token from the OAuth redirect (for API users)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [profileId, selectedPublicProfile, tempToken, userProfile]
properties:
profileId:
type: string
description: Your Late profile ID
selectedPublicProfile:
type: object
description: The selected Snapchat Public Profile
required: [id, display_name]
properties:
id:
type: string
description: Snapchat Public Profile ID
display_name:
type: string
description: Display name of the public profile
username:
type: string
description: Username/handle
profile_image_url:
type: string
description: Profile image URL
subscriber_count:
type: integer
description: Number of subscribers
tempToken:
type: string
description: Temporary Snapchat access token from OAuth
userProfile:
type: object
description: User profile data from OAuth redirect
refreshToken:
type: string
description: Snapchat refresh token (if available)
expiresIn:
type: integer
description: Token expiration time in seconds
redirect_url:
type: string
format: uri
description: Custom redirect URL after connection completes
example:
profileId: "64f0a1b2c3d4e5f6a7b8c9d0"
selectedPublicProfile:
id: "abc123-def456"
display_name: "My Brand"
username: "mybrand"
profile_image_url: "https://cf-st.sc-cdn.net/..."
subscriber_count: 15000
tempToken: "eyJ..."
userProfile:
id: "user123"
username: "mybrand"
displayName: "My Brand"
profilePicture: "https://cf-st.sc-cdn.net/..."
redirect_url: "https://yourapp.com/callback"
responses:
'200':
description: Snapchat Public Profile connected successfully
content:
application/json:
schema:
type: object
properties:
message: { type: string }
redirect_url: { type: string, description: Redirect URL with connection params (if provided in request) }
account:
type: object
properties:
accountId:
type: string
description: ID of the created SocialAccount
platform: { type: string, enum: [snapchat] }
username: { type: string }
displayName: { type: string }
profilePicture: { type: string }
isActive: { type: boolean }
publicProfileName: { type: string }
example:
message: "Snapchat connected successfully with public profile"
redirect_url: "https://yourdomain.com/integrations/callback?connected=snapchat&profileId=507f1f77bcf86cd799439011&publicProfile=My+Brand"
account:
accountId: "64e1f0a9e2b5af0012ab34cd"
platform: "snapchat"
username: "mybrand"
displayName: "My Brand"
profilePicture: "https://cf-st.sc-cdn.net/..."
isActive: true
publicProfileName: "My Brand"
'400':
description: Missing required fields
content:
application/json:
example:
error: "Missing required fields"
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: No access to profile or profile limit exceeded
content:
application/json:
examples:
forbidden:
value: { error: "Forbidden" }
limitExceeded:
value:
error: "Cannot connect to this profile. It exceeds your Pro plan limit of 5 profiles."
code: "PROFILE_LIMIT_EXCEEDED"
'500':
description: Failed to connect Snapchat account
/v1/connect/bluesky/credentials:
post:
operationId: connectBlueskyCredentials
tags: [Connect]
summary: Connect Bluesky account
description: |
Connect a Bluesky account using identifier (handle or email) and an app password.
To get your userId for the state parameter, call GET /v1/users which includes a currentUserId field.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [identifier, appPassword, state]
properties:
identifier:
type: string
description: Your Bluesky handle (e.g. user.bsky.social) or email address
appPassword:
type: string
description: App password generated from Bluesky Settings > App Passwords
state:
type: string
description: Required state formatted as {userId}-{profileId}. Get userId from GET /v1/users and profileId from GET /v1/profiles.
example: "6507a1b2c3d4e5f6a7b8c9d0-6507a1b2c3d4e5f6a7b8c9d1"
redirectUri:
type: string
format: uri
description: Optional URL to redirect to after successful connection
example:
identifier: "yourhandle.bsky.social"
appPassword: "xxxx-xxxx-xxxx-xxxx"
state: "6507a1b2c3d4e5f6a7b8c9d0-6507a1b2c3d4e5f6a7b8c9d1"
redirectUri: "https://yourapp.com/connected"
responses:
'200':
description: Bluesky connected successfully
content:
application/json:
schema:
type: object
properties:
message: { type: string }
account: { $ref: '#/components/schemas/SocialAccount' }
example:
message: "Bluesky connected successfully"
account:
platform: "bluesky"
username: "yourhandle.bsky.social"
displayName: "Your Name"
isActive: true
redirectUrl: "https://getlate.dev/dashboard/profiles/64f0.../accounts"
'400': { description: Invalid request - missing fields or invalid state format }
'401': { $ref: '#/components/responses/Unauthorized' }
'500': { description: Internal error }
/v1/connect/telegram:
get:
operationId: getTelegramConnectStatus
tags: [Connect]
summary: Generate Telegram code
description: Generate an access code (valid 15 minutes) for connecting a Telegram channel or group. Add the bot as admin, then send the code + @yourchannel to the bot. Poll PATCH /v1/connect/telegram to check status.
parameters:
- name: profileId
in: query
required: true
schema: { type: string }
description: The profile ID to connect the Telegram account to
responses:
'200':
description: Access code generated
content:
application/json:
schema:
type: object
properties:
code:
type: string
description: The access code to send to the Telegram bot
example: "LATE-ABC123"
expiresAt:
type: string
format: date-time
description: When the code expires
expiresIn:
type: integer
description: Seconds until expiration
example: 900
botUsername:
type: string
description: The Telegram bot username to message
example: "LateScheduleBot"
instructions:
type: array
items: { type: string }
description: Step-by-step connection instructions
example:
code: "LATE-ABC123"
expiresAt: "2024-01-15T12:30:00.000Z"
expiresIn: 900
botUsername: "LateScheduleBot"
instructions:
- "1. Add @LateScheduleBot as an administrator in your channel/group"
- "2. Open a private chat with @LateScheduleBot"
- "3. Send: LATE-ABC123 @yourchannel (replace @yourchannel with your channel username)"
- "4. Wait for confirmation - the connection will appear in your dashboard"
- "Tip: If your channel has no public username, forward a message from it along with the code"
'400': { description: Profile ID required or invalid format }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { description: No access to this profile }
'404': { description: Profile not found }
'500': { description: Internal error }
post:
operationId: initiateTelegramConnect
tags: [Connect]
summary: Connect Telegram directly
description: Connect a Telegram channel/group directly using the chat ID. Alternative to the access code flow. The bot must already be an admin in the channel/group.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [chatId, profileId]
properties:
chatId:
type: string
description: The Telegram chat ID. Numeric ID (e.g. "-1001234567890") or username with @ prefix (e.g. "@mychannel").
profileId:
type: string
description: The profile ID to connect the account to
example:
chatId: "-1001234567890"
profileId: "6507a1b2c3d4e5f6a7b8c9d0"
responses:
'200':
description: Telegram channel connected successfully
content:
application/json:
schema:
type: object
properties:
message: { type: string }
account:
type: object
properties:
_id: { type: string }
platform: { type: string, enum: [telegram] }
username: { type: string }
displayName: { type: string }
isActive: { type: boolean }
chatType: { type: string, enum: [channel, group, supergroup, private] }
example:
message: "Telegram channel connected successfully"
account:
_id: "64e1f0a9e2b5af0012ab34cd"
platform: "telegram"
username: "mychannel"
displayName: "My Channel"
isActive: true
chatType: "channel"
'400': { description: Chat ID required, bot not admin, or cannot access chat }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { description: No access to this profile }
'404': { description: Profile not found }
'500': { description: Internal error }
patch:
operationId: completeTelegramConnect
tags: [Connect]
summary: Check Telegram status
description: |
Poll this endpoint to check if a Telegram access code has been used to connect a channel/group. Recommended polling interval: 3 seconds.
Status values: pending (waiting for user), connected (channel/group linked), expired (generate a new code).
parameters:
- name: code
in: query
required: true
schema: { type: string }
description: The access code to check status for
example: "LATE-ABC123"
responses:
'200':
description: Connection status
content:
application/json:
schema:
oneOf:
- type: object
title: Pending
properties:
status: { type: string, enum: [pending] }
expiresAt: { type: string, format: date-time }
expiresIn: { type: integer, description: Seconds until expiration }
- type: object
title: Connected
properties:
status: { type: string, enum: [connected] }
chatId: { type: string }
chatTitle: { type: string }
chatType: { type: string, enum: [channel, group, supergroup] }
account:
type: object
properties:
_id: { type: string }
platform: { type: string }
username: { type: string }
displayName: { type: string }
- type: object
title: Expired
properties:
status: { type: string, enum: [expired] }
message: { type: string }
examples:
pending:
summary: Waiting for connection
value:
status: "pending"
expiresAt: "2024-01-15T12:30:00.000Z"
expiresIn: 542
connected:
summary: Successfully connected
value:
status: "connected"
chatId: "-1001234567890"
chatTitle: "My Channel"
chatType: "channel"
account:
_id: "64e1f0a9e2b5af0012ab34cd"
platform: "telegram"
username: "mychannel"
displayName: "My Channel"
expired:
summary: Code expired
value:
status: "expired"
message: "Access code has expired. Please generate a new one."
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Code not found }
'500': { description: Internal error }
/v1/accounts/{accountId}/facebook-page:
get:
operationId: getFacebookPages
tags: [Connect]
summary: List Facebook pages
description: Returns all Facebook pages the connected account has access to, including the currently selected page.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Pages list
content:
application/json:
schema:
type: object
properties:
pages:
type: array
items:
type: object
properties:
id: { type: string }
name: { type: string }
username: { type: string }
category: { type: string }
fan_count: { type: integer }
selectedPageId: { type: string }
cached: { type: boolean }
example:
pages:
- id: "123456789012345"
name: "My Brand Page"
username: "mybrand"
category: "Brand"
fan_count: 5000
- id: "234567890123456"
name: "My Other Page"
username: "myotherpage"
category: "Business"
fan_count: 1200
selectedPageId: "123456789012345"
cached: true
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Account not found }
put:
operationId: updateFacebookPage
tags: [Connect]
summary: Update Facebook page
description: Switch which Facebook Page is active for a connected account.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [selectedPageId]
properties:
selectedPageId: { type: string }
example:
selectedPageId: "123456789012345"
responses:
'200':
description: Page updated
content:
application/json:
schema:
type: object
properties:
message: { type: string }
selectedPage:
type: object
properties:
id: { type: string }
name: { type: string }
example:
message: "Facebook page updated successfully"
selectedPage:
id: "123456789012345"
name: "My Brand Page"
'400': { description: Page not in available pages }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Account not found }
/v1/accounts/{accountId}/linkedin-organizations:
get:
operationId: getLinkedInOrganizations
tags: [Connect]
summary: List LinkedIn orgs
description: Returns LinkedIn organizations (company pages) the connected account has admin access to.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Organizations list
content:
application/json:
schema:
type: object
properties:
organizations:
type: array
items:
type: object
properties:
id: { type: string }
name: { type: string }
vanityName: { type: string }
localizedName: { type: string }
example:
organizations:
- id: "12345678"
name: "Acme Corporation"
vanityName: "acme-corp"
localizedName: "Acme Corporation"
- id: "87654321"
name: "Acme Subsidiary"
vanityName: "acme-sub"
localizedName: "Acme Subsidiary"
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Account not found }
/v1/accounts/{accountId}/linkedin-aggregate-analytics:
get:
operationId: getLinkedInAggregateAnalytics
tags: [Analytics]
summary: Get LinkedIn aggregate stats
description: Returns aggregate analytics across all posts for a LinkedIn personal account. Org accounts should use /v1/analytics instead. Requires r_member_postAnalytics scope.
parameters:
- name: accountId
in: path
required: true
description: The ID of the LinkedIn personal account
schema: { type: string }
- name: aggregation
in: query
required: false
description: TOTAL (default, lifetime totals) or DAILY (time series). MEMBERS_REACHED not available with DAILY.
schema:
type: string
enum: [TOTAL, DAILY]
default: TOTAL
- name: startDate
in: query
required: false
description: Start date (YYYY-MM-DD). If omitted, returns lifetime analytics.
schema:
type: string
format: date
example: "2024-01-01"
- name: endDate
in: query
required: false
description: End date (YYYY-MM-DD, exclusive). Defaults to today if omitted.
schema:
type: string
format: date
example: "2024-01-31"
- name: metrics
in: query
required: false
description: "Comma-separated metrics: IMPRESSION, MEMBERS_REACHED, REACTION, COMMENT, RESHARE. Omit for all."
schema:
type: string
example: "IMPRESSION,REACTION,COMMENT"
responses:
'200':
description: Aggregate analytics data
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/LinkedInAggregateAnalyticsTotalResponse'
- $ref: '#/components/schemas/LinkedInAggregateAnalyticsDailyResponse'
examples:
totalAggregation:
summary: TOTAL aggregation (lifetime totals)
value:
accountId: "64abc123def456"
platform: "linkedin"
accountType: "personal"
username: "John Doe"
aggregation: "TOTAL"
dateRange: null
analytics:
impressions: 1250000
reach: 450000
reactions: 7500
comments: 2500
shares: 1200
engagementRate: 0.90
note: "Aggregate analytics across all posts on this LinkedIn personal account (lifetime totals)."
lastUpdated: "2025-01-15T10:30:00.000Z"
totalWithDateRange:
summary: TOTAL aggregation with date range
value:
accountId: "64abc123def456"
platform: "linkedin"
accountType: "personal"
username: "John Doe"
aggregation: "TOTAL"
dateRange:
startDate: "2024-01-01"
endDate: "2024-01-31"
analytics:
impressions: 125000
reach: 45000
reactions: 750
comments: 250
shares: 120
engagementRate: 0.90
note: "Aggregate analytics for the specified date range."
lastUpdated: "2025-01-15T10:30:00.000Z"
dailyAggregation:
summary: DAILY aggregation (time series)
value:
accountId: "64abc123def456"
platform: "linkedin"
accountType: "personal"
username: "John Doe"
aggregation: "DAILY"
dateRange:
startDate: "2024-05-04"
endDate: "2024-05-06"
analytics:
impressions:
- date: "2024-05-04"
count: 1500
- date: "2024-05-05"
count: 2300
reactions:
- date: "2024-05-04"
count: 10
- date: "2024-05-05"
count: 20
comments:
- date: "2024-05-04"
count: 3
- date: "2024-05-05"
count: 5
shares:
- date: "2024-05-04"
count: 2
- date: "2024-05-05"
count: 4
skippedMetrics:
- "MEMBERS_REACHED (not supported with DAILY aggregation)"
note: "Daily breakdown of analytics across all posts. MEMBERS_REACHED is not available with DAILY aggregation per LinkedIn API limitations."
lastUpdated: "2025-01-15T10:30:00.000Z"
'400':
description: Invalid request
content:
application/json:
schema:
type: object
properties:
error: { type: string }
code: { type: string }
validOptions: { type: array, items: { type: string } }
examples:
not_linkedin:
summary: Not a LinkedIn account
value:
error: "This endpoint only supports LinkedIn accounts"
code: "invalid_platform"
organization:
summary: Org account not supported
value:
error: "Aggregate analytics only available for LinkedIn personal accounts. Organization accounts can use per-post analytics via /v1/analytics."
code: "organization_not_supported"
invalid_aggregation:
summary: Invalid aggregation type
value:
error: "Invalid aggregation type. Must be one of: TOTAL, DAILY"
code: "invalid_aggregation"
validOptions: ["TOTAL", "DAILY"]
invalid_date:
summary: Invalid date format
value:
error: "Invalid date format. Use YYYY-MM-DD format."
code: "invalid_date_format"
example:
startDate: "2024-01-01"
endDate: "2024-01-31"
invalid_metrics:
summary: Invalid metrics requested
value:
error: "Invalid metrics: INVALID_METRIC. Valid options: IMPRESSION, MEMBERS_REACHED, REACTION, COMMENT, RESHARE"
code: "invalid_metrics"
validOptions: ["IMPRESSION", "MEMBERS_REACHED", "REACTION", "COMMENT", "RESHARE"]
'401': { $ref: '#/components/responses/Unauthorized' }
'402':
description: Analytics add-on required
content:
application/json:
schema:
type: object
properties:
error: { type: string }
code: { type: string }
'403':
description: Missing required LinkedIn scope
content:
application/json:
schema:
type: object
properties:
error: { type: string }
code: { type: string, example: missing_scope }
requiredScope: { type: string, example: r_member_postAnalytics }
action: { type: string, example: reconnect }
example:
error: "Missing r_member_postAnalytics scope. Please reconnect your LinkedIn account to grant analytics permissions."
code: "missing_scope"
requiredScope: "r_member_postAnalytics"
action: "reconnect"
'404': { description: Account not found }
/v1/accounts/{accountId}/linkedin-post-analytics:
get:
operationId: getLinkedInPostAnalytics
tags: [Analytics]
summary: Get LinkedIn post stats
description: Returns analytics for a specific LinkedIn post by URN. Works for both personal and organization accounts.
parameters:
- name: accountId
in: path
required: true
description: The ID of the LinkedIn account
schema: { type: string }
- name: urn
in: query
required: true
description: The LinkedIn post URN
schema: { type: string }
example: "urn:li:share:7123456789012345678"
responses:
'200':
description: Post analytics data
content:
application/json:
schema:
type: object
properties:
accountId: { type: string }
platform: { type: string, example: linkedin }
accountType: { type: string, enum: [personal, organization] }
username: { type: string }
postUrn: { type: string }
analytics:
type: object
properties:
impressions: { type: integer, description: Times the post was shown }
reach: { type: integer, description: Unique members who saw the post }
likes: { type: integer, description: Reactions on the post }
comments: { type: integer, description: Comments on the post }
shares: { type: integer, description: Reshares of the post }
clicks: { type: integer, description: Clicks on the post (organization accounts only) }
views: { type: integer, description: Video views (video posts only) }
engagementRate: { type: number, description: Engagement rate as percentage }
lastUpdated: { type: string, format: date-time }
example:
accountId: "64abc123def456"
platform: "linkedin"
accountType: "personal"
username: "John Doe"
postUrn: "urn:li:share:7123456789012345678"
analytics:
impressions: 5420
reach: 3200
likes: 156
comments: 23
shares: 12
clicks: 0
views: 1250
engagementRate: 5.17
lastUpdated: "2025-01-15T10:30:00.000Z"
'400':
description: Invalid request
content:
application/json:
schema:
type: object
properties:
error: { type: string }
code: { type: string, enum: [missing_urn, invalid_urn, invalid_platform] }
examples:
missing_urn:
value:
error: "Missing required parameter: urn"
code: "missing_urn"
example: "urn:li:share:7123456789012345678 or urn:li:ugcPost:7123456789012345678"
invalid_urn:
value:
error: "Invalid URN format. Must be urn:li:share:ID or urn:li:ugcPost:ID"
code: "invalid_urn"
providedUrn: "invalid-urn"
'401': { $ref: '#/components/responses/Unauthorized' }
'402':
description: Analytics add-on required
'403':
description: Missing required LinkedIn scope
content:
application/json:
schema:
type: object
properties:
error: { type: string }
code: { type: string, example: missing_scope }
requiredScope: { type: string }
action: { type: string, example: reconnect }
'404':
description: Account or post not found
content:
application/json:
schema:
type: object
properties:
error: { type: string }
code: { type: string }
examples:
account_not_found:
value:
error: "Account not found"
post_not_found:
value:
error: "Post not found. The URN may be invalid or the post may have been deleted."
code: "post_not_found"
postUrn: "urn:li:share:123"
/v1/accounts/{accountId}/linkedin-organization:
put:
operationId: updateLinkedInOrganization
tags: [Connect]
summary: Switch LinkedIn account type
description: Switch a LinkedIn account between personal profile and organization (company page) posting.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [accountType]
properties:
accountType: { type: string, enum: [personal, organization] }
selectedOrganization: { type: object }
example:
accountType: "organization"
selectedOrganization:
id: "12345678"
name: "Acme Corporation"
vanityName: "acme-corp"
responses:
'200':
description: Account updated
content:
application/json:
schema:
type: object
properties:
message: { type: string }
account: { $ref: '#/components/schemas/SocialAccount' }
example:
message: "LinkedIn account type updated successfully"
account:
_id: "64e1f0a9e2b5af0012ab34cd"
platform: "linkedin"
username: "acme-corp"
displayName: "Acme Corporation"
isActive: true
'400': { description: Invalid request }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Account not found }
/v1/accounts/{accountId}/linkedin-mentions:
get:
operationId: getLinkedInMentions
tags: [LinkedIn Mentions]
summary: Resolve LinkedIn mention
description: Converts a LinkedIn profile or company URL to a URN for @mentions in posts. Person mentions require org admin access. Use the returned mentionFormat in post content.
parameters:
- name: accountId
in: path
required: true
description: The LinkedIn account ID
schema: { type: string }
- name: url
in: query
required: true
description: LinkedIn profile URL, company URL, or vanity name.
schema: { type: string }
examples:
personVanityName:
value: "miquelpalet"
summary: Person - just the vanity name
personFullUrl:
value: "https://www.linkedin.com/in/miquelpalet"
summary: Person - full LinkedIn URL
orgShortUrl:
value: "company/microsoft"
summary: Org - short format
orgFullUrl:
value: "https://www.linkedin.com/company/microsoft"
summary: Org - full LinkedIn URL
- name: displayName
in: query
required: false
description: Exact display name as shown on LinkedIn. Required for person mentions to be clickable. Optional for org mentions.
schema: { type: string }
examples:
personName:
value: "Miquel Palet"
summary: Exact name as shown on LinkedIn profile
orgName:
value: "Microsoft"
summary: Company name (optional for orgs)
responses:
'200':
description: URN resolved successfully
content:
application/json:
schema:
type: object
properties:
urn:
type: string
description: The LinkedIn URN (person or organization)
example: "urn:li:person:4qj5ox-agD"
type:
type: string
enum: [person, organization]
description: The type of entity (person or organization)
example: "person"
displayName:
type: string
description: Display name (provided, from API, or derived from vanity URL)
example: "Miquel Palet"
mentionFormat:
type: string
description: Ready-to-use mention format for post content
example: "@[Miquel Palet](urn:li:person:4qj5ox-agD)"
vanityName:
type: string
description: The vanity name/slug (only for organization mentions)
example: "microsoft"
warning:
type: string
description: Warning about clickable mentions (only present for person mentions if displayName was not provided)
example: "For clickable person mentions, provide the displayName parameter with the exact name as shown on their LinkedIn profile."
examples:
personWithDisplayName:
summary: Person mention with displayName (recommended)
value:
urn: "urn:li:person:4qj5ox-agD"
type: "person"
displayName: "Miquel Palet"
mentionFormat: "@[Miquel Palet](urn:li:person:4qj5ox-agD)"
personWithoutDisplayName:
summary: Person mention without displayName (may not be clickable)
value:
urn: "urn:li:person:4qj5ox-agD"
type: "person"
displayName: "Miquelpalet"
mentionFormat: "@[Miquelpalet](urn:li:person:4qj5ox-agD)"
warning: "For clickable person mentions, provide the displayName parameter with the exact name as shown on their LinkedIn profile."
organization:
summary: Org mention
value:
urn: "urn:li:organization:1035"
type: "organization"
displayName: "Microsoft"
mentionFormat: "@[Microsoft](urn:li:organization:1035)"
vanityName: "microsoft"
'400':
description: Invalid request or no organization found (for person mentions)
content:
application/json:
schema:
type: object
properties:
error: { type: string }
examples:
missingUrl:
value: { error: "url parameter is required" }
noOrgForPersonMention:
value: { error: "No organization found. You need to be an admin of a LinkedIn organization to use person mentions. Organization mentions work without this requirement." }
'401': { $ref: '#/components/responses/Unauthorized' }
'404':
description: Person or organization not found
content:
application/json:
schema:
type: object
properties:
error: { type: string }
examples:
memberNotFound:
value: { error: "Member not found. Check the LinkedIn URL is correct." }
orgNotFound:
value: { error: "Organization not found. Check the LinkedIn company URL is correct." }
/v1/accounts/{accountId}/pinterest-boards:
get:
operationId: getPinterestBoards
tags: [Connect]
summary: List Pinterest boards
description: Returns the boards available for a connected Pinterest account. Use this to get a board ID when creating a Pinterest post.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Boards list
content:
application/json:
schema:
type: object
properties:
boards:
type: array
items:
type: object
properties:
id: { type: string }
name: { type: string }
description: { type: string }
privacy: { type: string }
example:
boards:
- id: "123456789012345678"
name: "Marketing Ideas"
description: "Collection of marketing inspiration"
privacy: "PUBLIC"
- id: "234567890123456789"
name: "Product Photos"
description: "Product photography"
privacy: "PUBLIC"
'400': { description: Not a Pinterest account }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Account not found }
put:
operationId: updatePinterestBoards
tags: [Connect]
summary: Set default Pinterest board
description: Sets the default board used when publishing pins for this account.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [defaultBoardId]
properties:
defaultBoardId: { type: string }
defaultBoardName: { type: string }
example:
defaultBoardId: "123456789012345678"
defaultBoardName: "Marketing Ideas"
responses:
'200':
description: Default board set
content:
application/json:
schema:
type: object
properties:
message: { type: string }
account: { $ref: '#/components/schemas/SocialAccount' }
example:
message: "Default Pinterest board updated successfully"
account:
_id: "64e1f0a9e2b5af0012ab34cd"
platform: "pinterest"
username: "mybrand"
displayName: "My Brand"
isActive: true
'400': { description: Invalid request }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Account not found }
/v1/accounts/{accountId}/gmb-locations:
get:
operationId: getGmbLocations
tags: [Connect]
summary: List GBP locations
description: Returns all Google Business Profile locations the connected account has access to, including the currently selected location.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Locations list
content:
application/json:
schema:
type: object
properties:
locations:
type: array
items:
type: object
properties:
id: { type: string }
name: { type: string }
accountId: { type: string }
accountName: { type: string }
address: { type: string }
category: { type: string }
websiteUrl: { type: string }
selectedLocationId: { type: string }
cached: { type: boolean }
example:
locations:
- id: "12345678901234567890"
name: "My Business Location"
accountId: "accounts/123456789"
accountName: "My Business Account"
address: "123 Main St, San Francisco, CA"
category: "Restaurant"
websiteUrl: "https://mybusiness.com"
selectedLocationId: "12345678901234567890"
cached: true
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Account not found }
put:
operationId: updateGmbLocation
tags: [Connect]
summary: Update GBP location
description: Switch which GBP location is active for a connected account.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [selectedLocationId]
properties:
selectedLocationId: { type: string }
example:
selectedLocationId: "12345678901234567890"
responses:
'200':
description: Location updated
content:
application/json:
schema:
type: object
properties:
message: { type: string }
selectedLocation:
type: object
properties:
id: { type: string }
name: { type: string }
example:
message: "Google Business location updated successfully"
selectedLocation:
id: "12345678901234567890"
name: "My Business Location"
'400': { description: Location not in available locations }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Account not found }
/v1/accounts/{accountId}/reddit-subreddits:
get:
operationId: getRedditSubreddits
tags: [Connect]
summary: List Reddit subreddits
description: Returns the subreddits the connected Reddit account can post to. Use this to get a subreddit name when creating a Reddit post.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Subreddits list
content:
application/json:
schema:
type: object
properties:
subreddits:
type: array
items:
type: object
properties:
id: { type: string, description: Reddit subreddit ID }
name: { type: string, description: Subreddit name without r/ prefix }
title: { type: string, description: Subreddit title }
url: { type: string, description: Subreddit URL path }
over18: { type: boolean, description: Whether the subreddit is NSFW }
defaultSubreddit:
type: string
description: Currently set default subreddit for posting
example:
subreddits:
- id: "2qh1i"
name: "marketing"
title: "Marketing"
url: "/r/marketing/"
over18: false
- id: "2qh3l"
name: "socialmedia"
title: "Social Media"
url: "/r/socialmedia/"
over18: false
defaultSubreddit: "marketing"
'400': { description: Not a Reddit account }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Account not found }
put:
operationId: updateRedditSubreddits
tags: [Connect]
summary: Set default subreddit
description: Sets the default subreddit used when publishing posts for this Reddit account.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [defaultSubreddit]
properties:
defaultSubreddit: { type: string }
example:
defaultSubreddit: "marketing"
responses:
'200':
description: Default subreddit set
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
example:
success: true
'400': { description: Invalid request }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Account not found }
/v1/accounts/{accountId}/reddit-flairs:
get:
operationId: getRedditFlairs
tags: [Connect]
summary: List subreddit flairs
description: Returns available post flairs for a subreddit. Some subreddits require a flair when posting.
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
- name: subreddit
in: query
required: true
schema: { type: string }
description: Subreddit name (without "r/" prefix) to fetch flairs for
responses:
'200':
description: Flairs list
content:
application/json:
schema:
type: object
properties:
flairs:
type: array
items:
type: object
properties:
id: { type: string, description: Flair ID to pass as flairId in platformSpecificData }
text: { type: string, description: Flair display text }
textColor: { type: string, description: "Text color: 'dark' or 'light'" }
backgroundColor: { type: string, description: "Background hex color (e.g. '#ff4500')" }
example:
flairs:
- id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
text: "Discussion"
textColor: "dark"
backgroundColor: "#edeff1"
- id: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
text: "News"
textColor: "light"
backgroundColor: "#ff4500"
'400': { description: Not a Reddit account or missing subreddit parameter }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Account not found }
/v1/queue/slots:
get:
operationId: listQueueSlots
tags: [Queue]
summary: List schedules
description: Returns queue schedules for a profile. Use all=true for all queues, or queueId for a specific one. Defaults to the default queue.
parameters:
- name: profileId
in: query
required: true
schema: { type: string }
description: Profile ID to get queues for
- name: queueId
in: query
required: false
schema: { type: string }
description: Specific queue ID to retrieve (optional)
- name: all
in: query
required: false
schema: { type: string, enum: ['true'] }
description: Set to 'true' to list all queues for the profile
responses:
'200':
description: Queue schedule(s) retrieved
content:
application/json:
schema:
oneOf:
- type: object
description: Single queue response (default behavior)
properties:
exists: { type: boolean }
schedule:
$ref: '#/components/schemas/QueueSchedule'
nextSlots:
type: array
items: { type: string, format: date-time }
- type: object
description: All queues response (when all=true)
properties:
queues:
type: array
items:
$ref: '#/components/schemas/QueueSchedule'
count: { type: integer }
examples:
singleQueue:
summary: Single queue response
value:
exists: true
schedule:
_id: "64f0a1b2c3d4e5f6a7b8c9d1"
profileId: "64f0a1b2c3d4e5f6a7b8c9d0"
name: "Morning Posts"
timezone: "America/New_York"
slots:
- dayOfWeek: 1
time: "09:00"
- dayOfWeek: 3
time: "09:00"
- dayOfWeek: 5
time: "10:00"
active: true
isDefault: true
nextSlots:
- "2024-11-04T09:00:00-05:00"
- "2024-11-06T09:00:00-05:00"
allQueues:
summary: All queues response (all=true)
value:
queues:
- _id: "64f0a1b2c3d4e5f6a7b8c9d1"
name: "Morning Posts"
isDefault: true
timezone: "America/New_York"
slots: [{ dayOfWeek: 1, time: "09:00" }]
active: true
- _id: "64f0a1b2c3d4e5f6a7b8c9d2"
name: "Evening Content"
isDefault: false
timezone: "America/New_York"
slots: [{ dayOfWeek: 1, time: "18:00" }]
active: true
count: 2
'400': { description: Missing profileId }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Profile not found }
post:
operationId: createQueueSlot
tags: [Queue]
summary: Create schedule
description: |
Create an additional queue for a profile. The first queue created becomes the default.
Subsequent queues are non-default unless explicitly set.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [profileId, name, timezone, slots]
properties:
profileId: { type: string, description: Profile ID }
name: { type: string, description: "Queue name (e.g., Evening Posts)" }
timezone: { type: string, description: IANA timezone }
slots:
type: array
items:
$ref: '#/components/schemas/QueueSlot'
active: { type: boolean, default: true }
example:
profileId: "64f0a1b2c3d4e5f6a7b8c9d0"
name: "Evening Posts"
timezone: "America/New_York"
slots:
- dayOfWeek: 1
time: "18:00"
- dayOfWeek: 3
time: "18:00"
- dayOfWeek: 5
time: "18:00"
active: true
responses:
'201':
description: Queue created
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
schedule:
$ref: '#/components/schemas/QueueSchedule'
nextSlots:
type: array
items: { type: string, format: date-time }
'400': { description: Invalid request or validation error }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Profile not found }
put:
operationId: updateQueueSlot
tags: [Queue]
summary: Update schedule
description: |
Create a new queue or update an existing one. Without queueId, creates/updates the default queue. With queueId, updates a specific queue. With setAsDefault=true, makes this queue the default for the profile.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [profileId, timezone, slots]
properties:
profileId: { type: string }
queueId: { type: string, description: Queue ID to update (optional) }
name: { type: string, description: Queue name }
timezone: { type: string }
slots:
type: array
items:
$ref: '#/components/schemas/QueueSlot'
active: { type: boolean, default: true }
setAsDefault: { type: boolean, description: Make this queue the default }
reshuffleExisting:
type: boolean
default: false
description: Whether to reschedule existing queued posts to match new slots
example:
profileId: "64f0a1b2c3d4e5f6a7b8c9d0"
queueId: "64f0a1b2c3d4e5f6a7b8c9d1"
name: "Morning Posts"
timezone: "America/New_York"
slots:
- dayOfWeek: 1
time: "09:00"
- dayOfWeek: 3
time: "09:00"
- dayOfWeek: 5
time: "10:00"
active: true
setAsDefault: false
responses:
'200':
description: Queue schedule updated
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
schedule:
$ref: '#/components/schemas/QueueSchedule'
nextSlots:
type: array
items: { type: string, format: date-time }
reshuffledCount: { type: integer }
example:
success: true
schedule:
_id: "64f0a1b2c3d4e5f6a7b8c9d1"
profileId: "64f0a1b2c3d4e5f6a7b8c9d0"
name: "Morning Posts"
timezone: "America/New_York"
slots:
- dayOfWeek: 1
time: "09:00"
- dayOfWeek: 3
time: "09:00"
- dayOfWeek: 5
time: "10:00"
active: true
isDefault: true
nextSlots:
- "2024-11-04T09:00:00-05:00"
- "2024-11-06T09:00:00-05:00"
reshuffledCount: 0
'400': { description: Invalid request }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Profile not found }
delete:
operationId: deleteQueueSlot
tags: [Queue]
summary: Delete schedule
description: |
Delete a queue from a profile. Requires queueId to specify which queue to delete.
If deleting the default queue, another queue will be promoted to default.
parameters:
- name: profileId
in: query
required: true
schema: { type: string }
- name: queueId
in: query
required: true
schema: { type: string }
description: Queue ID to delete
responses:
'200':
description: Queue schedule deleted
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
deleted: { type: boolean }
example:
success: true
deleted: true
'400': { description: Missing profileId or queueId }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/queue/preview:
get:
operationId: previewQueue
tags: [Queue]
summary: Preview upcoming slots
description: Returns the next N upcoming queue slot times for a profile as ISO datetime strings.
parameters:
- name: profileId
in: query
required: true
schema: { type: string }
- name: count
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
responses:
'200':
description: Queue slots preview
content:
application/json:
schema:
type: object
properties:
profileId: { type: string }
count: { type: integer }
slots:
type: array
items: { type: string, format: date-time }
example:
profileId: "64f0a1b2c3d4e5f6a7b8c9d0"
count: 10
slots:
- "2024-11-04T09:00:00-05:00"
- "2024-11-04T14:00:00-05:00"
- "2024-11-06T09:00:00-05:00"
- "2024-11-08T10:00:00-05:00"
- "2024-11-11T09:00:00-05:00"
- "2024-11-11T14:00:00-05:00"
- "2024-11-13T09:00:00-05:00"
- "2024-11-15T10:00:00-05:00"
- "2024-11-18T09:00:00-05:00"
- "2024-11-18T14:00:00-05:00"
'400': { description: Invalid parameters }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Profile or queue schedule not found }
/v1/queue/next-slot:
get:
operationId: getNextQueueSlot
tags: [Queue]
summary: Get next available slot
description: Returns the next available queue slot for preview purposes. To create a queue post, use POST /v1/posts with queuedFromProfile instead of scheduledFor.
parameters:
- name: profileId
in: query
required: true
schema: { type: string }
- name: queueId
in: query
required: false
schema: { type: string }
description: Specific queue ID (optional, defaults to profile's default queue)
responses:
'200':
description: Next available slot
content:
application/json:
schema:
type: object
properties:
profileId: { type: string }
nextSlot: { type: string, format: date-time }
timezone: { type: string }
queueId: { type: string, description: Queue ID this slot belongs to }
queueName: { type: string, description: Queue name }
example:
profileId: "64f0a1b2c3d4e5f6a7b8c9d0"
nextSlot: "2024-11-04T09:00:00-05:00"
timezone: "America/New_York"
queueId: "64f0a1b2c3d4e5f6a7b8c9d1"
queueName: "Morning Posts"
'400':
description: Invalid parameters or inactive queue
'401': { $ref: '#/components/responses/Unauthorized' }
'404':
description: "Profile or queue schedule not found, or no available slots"
/v1/webhooks/settings:
get:
operationId: getWebhookSettings
tags: [Webhooks]
summary: List webhooks
description: Retrieve all configured webhooks for the authenticated user. Supports up to 10 webhooks per user.
security:
- bearerAuth: []
responses:
'200':
description: Webhooks retrieved successfully
content:
application/json:
schema:
type: object
properties:
webhooks:
type: array
items:
$ref: '#/components/schemas/Webhook'
example:
webhooks:
- _id: "507f1f77bcf86cd799439011"
name: "My Production Webhook"
url: "https://example.com/webhook"
events: ["post.published", "post.failed"]
isActive: true
lastFiredAt: "2024-01-15T10:30:00Z"
failureCount: 0
- _id: "507f1f77bcf86cd799439012"
name: "Slack Notifications"
url: "https://hooks.slack.com/services/xxx"
events: ["post.failed", "account.disconnected"]
isActive: true
failureCount: 0
'401': { $ref: '#/components/responses/Unauthorized' }
post:
operationId: createWebhookSettings
tags: [Webhooks]
summary: Create webhook
description: |
Create a new webhook configuration. Maximum 10 webhooks per user.
Webhooks are automatically disabled after 10 consecutive delivery failures.
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: Webhook name (max 50 characters)
maxLength: 50
url:
type: string
format: uri
description: Webhook endpoint URL (must be HTTPS in production)
secret:
type: string
description: Secret key for HMAC-SHA256 signature verification
events:
type: array
items:
type: string
enum: [post.scheduled, post.published, post.failed, post.partial, account.connected, account.disconnected, message.received, comment.received]
description: Events to subscribe to
isActive:
type: boolean
description: Enable or disable webhook delivery
customHeaders:
type: object
additionalProperties:
type: string
description: Custom headers to include in webhook requests
examples:
createWebhook:
summary: Create webhook with all events
value:
name: "My Production Webhook"
url: "https://example.com/webhook"
secret: "your-secret-key"
events: ["post.scheduled", "post.published", "post.failed", "post.partial", "account.connected", "account.disconnected", "message.received", "comment.received"]
isActive: true
responses:
'200':
description: Webhook created successfully
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
webhook:
$ref: '#/components/schemas/Webhook'
'400': { description: Validation error or maximum webhooks reached }
'401': { $ref: '#/components/responses/Unauthorized' }
put:
operationId: updateWebhookSettings
tags: [Webhooks]
summary: Update webhook
description: |
Update an existing webhook configuration. All fields except _id are optional; only provided fields will be updated.
Webhooks are automatically disabled after 10 consecutive delivery failures.
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- _id
properties:
_id:
type: string
description: Webhook ID to update (required)
name:
type: string
description: Webhook name (max 50 characters)
maxLength: 50
url:
type: string
format: uri
description: Webhook endpoint URL (must be HTTPS in production)
secret:
type: string
description: Secret key for HMAC-SHA256 signature verification
events:
type: array
items:
type: string
enum: [post.scheduled, post.published, post.failed, post.partial, account.connected, account.disconnected, message.received, comment.received]
description: Events to subscribe to
isActive:
type: boolean
description: Enable or disable webhook delivery
customHeaders:
type: object
additionalProperties:
type: string
description: Custom headers to include in webhook requests
examples:
updateWebhook:
summary: Update webhook URL and events
value:
_id: "507f1f77bcf86cd799439011"
url: "https://new-example.com/webhook"
events: ["post.published", "post.failed"]
toggleWebhook:
summary: Enable/disable webhook
value:
_id: "507f1f77bcf86cd799439011"
isActive: false
responses:
'200':
description: Webhook updated successfully
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
webhook:
$ref: '#/components/schemas/Webhook'
'400': { description: Validation error or missing webhook ID }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Webhook not found }
delete:
operationId: deleteWebhookSettings
tags: [Webhooks]
summary: Delete webhook
description: Permanently delete a webhook configuration.
security:
- bearerAuth: []
parameters:
- name: id
in: query
required: true
description: Webhook ID to delete
schema:
type: string
responses:
'200':
description: Webhook deleted successfully
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
'400': { description: Webhook ID required }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/webhooks/test:
post:
operationId: testWebhook
tags: [Webhooks]
summary: Send test webhook
description: |
Send a test webhook to verify your endpoint is configured correctly. The test payload includes event: "webhook.test" to distinguish it from real events.
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- webhookId
properties:
webhookId:
type: string
description: ID of the webhook to test
example:
webhookId: "507f1f77bcf86cd799439011"
responses:
'200':
description: Test webhook sent successfully
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
message: { type: string }
example:
success: true
message: "Test webhook sent successfully"
'400': { description: Webhook ID required }
'401': { $ref: '#/components/responses/Unauthorized' }
'500':
description: Test webhook failed to deliver
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
message: { type: string }
example:
success: false
message: "Test webhook failed"
/v1/webhooks/logs:
get:
operationId: getWebhookLogs
tags: [Webhooks]
summary: Get delivery logs
description: |
Retrieve webhook delivery history. Logs are automatically deleted after 7 days.
security:
- bearerAuth: []
parameters:
- name: limit
in: query
description: Maximum number of logs to return (max 100)
schema:
type: integer
minimum: 1
maximum: 100
default: 50
- name: status
in: query
description: Filter by delivery status
schema:
type: string
enum: [success, failed]
- name: event
in: query
description: Filter by event type
schema:
type: string
enum: [post.scheduled, post.published, post.failed, post.partial, account.connected, account.disconnected, message.received, comment.received, webhook.test]
- name: webhookId
in: query
description: Filter by webhook ID
schema:
type: string
responses:
'200':
description: Webhook logs retrieved successfully
content:
application/json:
schema:
type: object
properties:
logs:
type: array
items:
$ref: '#/components/schemas/WebhookLog'
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/posts/logs:
get:
operationId: listPostsLogs
tags: [Logs]
summary: List publishing logs
description: |
Retrieve publishing logs for all posts with detailed information about each publishing attempt. Filter by status, platform, or action. Logs are automatically deleted after 7 days.
security:
- bearerAuth: []
parameters:
- name: status
in: query
description: Filter by log status
schema:
type: string
enum: [success, failed, pending, skipped, all]
- name: platform
in: query
description: Filter by platform
schema:
type: string
enum: [tiktok, instagram, facebook, youtube, linkedin, twitter, threads, pinterest, reddit, bluesky, googlebusiness, telegram, snapchat, all]
- name: action
in: query
description: Filter by action type
schema:
type: string
enum: [publish, retry, media_upload, rate_limit_pause, token_refresh, cancelled, all]
- name: days
in: query
description: Number of days to look back (max 7)
schema:
type: integer
minimum: 1
maximum: 7
default: 7
- name: limit
in: query
description: Maximum number of logs to return (max 100)
schema:
type: integer
minimum: 1
maximum: 100
default: 50
- name: skip
in: query
description: Number of logs to skip (for pagination)
schema:
type: integer
minimum: 0
default: 0
responses:
'200':
description: Publishing logs retrieved successfully
content:
application/json:
schema:
type: object
properties:
logs:
type: array
items:
$ref: '#/components/schemas/PostLog'
pagination:
type: object
properties:
total:
type: integer
description: Total number of logs matching the query
limit:
type: integer
skip:
type: integer
pages:
type: integer
description: Total number of pages
hasMore:
type: boolean
example:
logs:
- _id: "675f1c0a9e2b5af0012ab34cd"
postId:
_id: "65f1c0a9e2b5af0012ab34cd"
content: "Check out our new feature!"
status: "published"
userId: "64e1f0a9e2b5af0012ab34de"
platform: "instagram"
accountId: "64e1f0a9e2b5af0012ab34ef"
accountUsername: "@acmecorp"
action: "publish"
status: "success"
statusCode: 200
endpoint: "graph.facebook.com/me/media_publish"
request:
contentPreview: "Check out our new feature!"
mediaCount: 1
mediaTypes: ["image"]
mediaUrls: ["https://storage.getlate.dev/abc123.jpg"]
response:
platformPostId: "17895695668004550"
platformPostUrl: "https://www.instagram.com/p/ABC123/"
durationMs: 2340
attemptNumber: 1
createdAt: "2024-11-01T10:00:05Z"
pagination:
total: 150
limit: 50
skip: 0
pages: 3
hasMore: true
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/connections/logs:
get:
operationId: listConnectionLogs
tags: [Logs]
summary: List connection logs
description: |
Retrieve connection event logs showing account connection and disconnection history. Event types: connect_success, connect_failed, disconnect, reconnect_success, reconnect_failed.
Logs are automatically deleted after 7 days.
security:
- bearerAuth: []
parameters:
- name: platform
in: query
description: Filter by platform
schema:
type: string
enum: [tiktok, instagram, facebook, youtube, linkedin, twitter, threads, pinterest, reddit, bluesky, googlebusiness, telegram, snapchat, all]
- name: eventType
in: query
description: Filter by event type
schema:
type: string
enum: [connect_success, connect_failed, disconnect, reconnect_success, reconnect_failed, all]
- name: status
in: query
description: Filter by status (shorthand for event types)
schema:
type: string
enum: [success, failed, all]
description: success = connect_success + reconnect_success, failed = connect_failed + reconnect_failed
- name: days
in: query
description: Number of days to look back (max 7)
schema:
type: integer
minimum: 1
maximum: 7
default: 7
- name: limit
in: query
description: Maximum number of logs to return (max 100)
schema:
type: integer
minimum: 1
maximum: 100
default: 50
- name: skip
in: query
description: Number of logs to skip (for pagination)
schema:
type: integer
minimum: 0
default: 0
responses:
'200':
description: Connection logs retrieved successfully
content:
application/json:
schema:
type: object
properties:
logs:
type: array
items:
$ref: '#/components/schemas/ConnectionLog'
pagination:
type: object
properties:
total:
type: integer
description: Total number of logs matching the query
limit:
type: integer
skip:
type: integer
pages:
type: integer
description: Total number of pages
hasMore:
type: boolean
example:
logs:
- _id: "675f1c0a9e2b5af0012ab34cd"
userId: "64e1f0a9e2b5af0012ab34de"
profileId: "64e1f0a9e2b5af0012ab34ef"
accountId: "64e1f0a9e2b5af0012ab3500"
platform: "instagram"
eventType: "connect_success"
connectionMethod: "oauth"
success:
displayName: "Acme Corp"
username: "acmecorp"
profilePicture: "https://..."
permissions: ["instagram_basic", "instagram_content_publish"]
tokenExpiresAt: "2024-12-01T10:00:00Z"
accountType: "business"
context:
hasCustomRedirectUrl: false
createdAt: "2024-11-01T10:00:00Z"
- _id: "675f1c0a9e2b5af0012ab34ce"
userId: "64e1f0a9e2b5af0012ab34de"
profileId: "64e1f0a9e2b5af0012ab34ef"
platform: "twitter"
eventType: "connect_failed"
connectionMethod: "oauth"
error:
code: "oauth_denied"
message: "OAuth error: access_denied"
context:
hasCustomRedirectUrl: true
createdAt: "2024-11-01T09:00:00Z"
pagination:
total: 25
limit: 50
skip: 0
pages: 1
hasMore: false
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/posts/{postId}/logs:
get:
operationId: getPostLogs
tags: [Logs]
summary: Get post logs
description: |
Retrieve all publishing logs for a specific post. Shows the complete history
of publishing attempts for that post across all platforms.
security:
- bearerAuth: []
parameters:
- name: postId
in: path
required: true
description: The post ID
schema:
type: string
- name: limit
in: query
description: Maximum number of logs to return (max 100)
schema:
type: integer
minimum: 1
maximum: 100
default: 50
responses:
'200':
description: Post logs retrieved successfully
content:
application/json:
schema:
type: object
properties:
logs:
type: array
items:
$ref: '#/components/schemas/PostLog'
count:
type: integer
description: Number of logs returned
postId:
type: string
example:
logs:
- _id: "675f1c0a9e2b5af0012ab34cd"
postId: "65f1c0a9e2b5af0012ab34cd"
userId: "64e1f0a9e2b5af0012ab34de"
platform: "instagram"
accountUsername: "@acmecorp"
action: "publish"
status: "success"
statusCode: 200
durationMs: 2340
createdAt: "2024-11-01T10:00:05Z"
- _id: "675f1c0a9e2b5af0012ab34ce"
postId: "65f1c0a9e2b5af0012ab34cd"
userId: "64e1f0a9e2b5af0012ab34de"
platform: "twitter"
accountUsername: "@acme"
action: "publish"
status: "failed"
statusCode: 429
response:
errorMessage: "Rate limit exceeded"
errorCode: "RATE_LIMITED"
durationMs: 150
attemptNumber: 1
createdAt: "2024-11-01T10:00:03Z"
count: 2
postId: "65f1c0a9e2b5af0012ab34cd"
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Forbidden - not authorized to view this post
'404': { $ref: '#/components/responses/NotFound' }
/v1/inbox/conversations:
get:
operationId: listInboxConversations
summary: List conversations
description: |
Fetch conversations (DMs) from all connected messaging accounts in a single API call. Supports filtering by profile and platform. Results are aggregated and deduplicated.
Supported platforms: Facebook, Instagram, Twitter/X, Bluesky, Reddit, Telegram.
tags: [Messages]
security: [{ bearerAuth: [] }]
parameters:
- name: profileId
in: query
schema: { type: string }
description: Filter by profile ID
- name: platform
in: query
schema: { type: string, enum: [facebook, instagram, twitter, bluesky, reddit, telegram] }
description: Filter by platform
- name: status
in: query
schema: { type: string, enum: [active, archived] }
description: Filter by conversation status
- name: sortOrder
in: query
schema: { type: string, enum: [asc, desc], default: desc }
description: Sort order by updated time
- name: limit
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
description: Maximum number of conversations to return
- name: cursor
in: query
schema: { type: string }
description: Pagination cursor for next page
- name: accountId
in: query
schema: { type: string }
description: Filter by specific social account ID
responses:
'200':
description: Aggregated conversations
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
type: object
properties:
id: { type: string }
platform: { type: string }
accountId: { type: string }
accountUsername: { type: string }
participantId: { type: string }
participantName: { type: string }
participantPicture: { type: string, nullable: true }
lastMessage: { type: string }
updatedTime: { type: string, format: date-time }
status: { type: string, enum: [active, archived] }
unreadCount: { type: integer, nullable: true, description: Number of unread messages }
url:
type: string
nullable: true
description: Direct link to open the conversation on the platform (if available)
instagramProfile:
type: object
nullable: true
description: Instagram profile data for the participant. Only present for Instagram conversations.
properties:
isFollower:
type: boolean
nullable: true
description: Whether the participant follows your Instagram business account
isFollowing:
type: boolean
nullable: true
description: Whether your Instagram business account follows the participant
followerCount:
type: integer
nullable: true
description: The participant's follower count on Instagram
isVerified:
type: boolean
nullable: true
description: Whether the participant is a verified Instagram user
fetchedAt:
type: string
format: date-time
nullable: true
description: When this profile data was last fetched from Instagram
pagination:
type: object
properties:
hasMore: { type: boolean }
nextCursor: { type: string, nullable: true }
meta:
type: object
properties:
accountsQueried: { type: integer }
accountsFailed: { type: integer }
failedAccounts:
type: array
items:
type: object
properties:
accountId: { type: string }
accountUsername: { type: string, nullable: true }
platform: { type: string }
error: { type: string }
code: { type: string, nullable: true, description: Error code if available }
retryAfter: { type: integer, nullable: true, description: Seconds to wait before retry (rate limits) }
lastUpdated: { type: string, format: date-time }
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required
/v1/inbox/conversations/{conversationId}:
get:
operationId: getInboxConversation
summary: Get conversation
description: Retrieve details and metadata for a specific conversation. Requires accountId query parameter.
tags: [Messages]
security: [{ bearerAuth: [] }]
parameters:
- name: conversationId
in: path
required: true
schema: { type: string }
description: The conversation ID (id field from list conversations endpoint). This is the platform-specific conversation identifier, not an internal database ID.
- name: accountId
in: query
required: true
schema: { type: string }
description: The social account ID
responses:
'200':
description: Conversation details
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
id: { type: string }
accountId: { type: string }
accountUsername: { type: string }
platform: { type: string }
status: { type: string, enum: [active, archived] }
participantName: { type: string }
participantId: { type: string }
lastMessage: { type: string }
lastMessageAt: { type: string, format: date-time }
updatedTime: { type: string, format: date-time }
participants:
type: array
items:
type: object
properties:
id: { type: string }
name: { type: string }
instagramProfile:
type: object
nullable: true
description: Instagram profile data for the participant. Only present for Instagram conversations.
properties:
isFollower:
type: boolean
nullable: true
description: Whether the participant follows your Instagram business account
isFollowing:
type: boolean
nullable: true
description: Whether your Instagram business account follows the participant
followerCount:
type: integer
nullable: true
description: The participant's follower count on Instagram
isVerified:
type: boolean
nullable: true
description: Whether the participant is a verified Instagram user
fetchedAt:
type: string
format: date-time
nullable: true
description: When this profile data was last fetched from Instagram
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required
'404':
description: Conversation not found
put:
operationId: updateInboxConversation
summary: Update conversation status
description: Archive or activate a conversation. Requires accountId in request body.
tags: [Messages]
security: [{ bearerAuth: [] }]
parameters:
- name: conversationId
in: path
required: true
schema: { type: string }
description: The conversation ID (id field from list conversations endpoint). This is the platform-specific conversation identifier, not an internal database ID.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [accountId, status]
properties:
accountId: { type: string, description: Social account ID }
status: { type: string, enum: [active, archived] }
responses:
'200':
description: Conversation updated
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
data:
type: object
properties:
id: { type: string }
accountId: { type: string }
status: { type: string, enum: [active, archived] }
platform: { type: string }
updatedAt: { type: string, format: date-time }
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required
/v1/inbox/conversations/{conversationId}/messages:
get:
operationId: getInboxConversationMessages
summary: List messages
description: Fetch messages for a specific conversation. Requires accountId query parameter.
tags: [Messages]
security: [{ bearerAuth: [] }]
parameters:
- name: conversationId
in: path
required: true
schema: { type: string }
description: The conversation ID (id field from list conversations endpoint). This is the platform-specific conversation identifier, not an internal database ID.
- name: accountId
in: query
required: true
schema: { type: string }
description: Social account ID
responses:
'200':
description: Messages in conversation
content:
application/json:
schema:
type: object
properties:
status: { type: string }
messages:
type: array
items:
type: object
properties:
id: { type: string }
conversationId: { type: string }
accountId: { type: string }
platform: { type: string }
message: { type: string }
senderId: { type: string }
senderName: { type: string, nullable: true }
direction: { type: string, enum: [incoming, outgoing] }
createdAt: { type: string, format: date-time }
attachments:
type: array
items:
type: object
properties:
id: { type: string }
type: { type: string, enum: [image, video, audio, file, sticker, share] }
url: { type: string }
filename: { type: string, nullable: true }
previewUrl: { type: string, nullable: true }
subject: { type: string, nullable: true, description: Reddit message subject }
storyReply: { type: boolean, nullable: true, description: Instagram story reply }
isStoryMention: { type: boolean, nullable: true, description: Instagram story mention }
lastUpdated: { type: string, format: date-time }
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required
post:
operationId: sendInboxMessage
summary: Send message
description: Send a message in a conversation. Supports text, attachments, quick replies, buttons, and message tags. Attachment and interactive message support varies by platform.
tags: [Messages]
security: [{ bearerAuth: [] }]
parameters:
- name: conversationId
in: path
required: true
schema: { type: string }
description: The conversation ID (id field from list conversations endpoint). This is the platform-specific conversation identifier, not an internal database ID.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [accountId]
properties:
accountId: { type: string, description: Social account ID }
message: { type: string, description: Message text }
quickReplies:
type: array
maxItems: 13
description: Quick reply buttons. Mutually exclusive with buttons. Max 13 items.
items:
type: object
required: [title, payload]
properties:
title: { type: string, maxLength: 20, description: Button label (max 20 chars) }
payload: { type: string, description: Payload sent back on tap }
imageUrl: { type: string, description: Optional icon URL (Meta only) }
buttons:
type: array
maxItems: 3
description: Action buttons. Mutually exclusive with quickReplies. Max 3 items.
items:
type: object
required: [type, title]
properties:
type: { type: string, enum: [url, postback, phone], description: Button type. phone is Facebook only. }
title: { type: string, maxLength: 20, description: Button label (max 20 chars) }
url: { type: string, description: URL for url-type buttons }
payload: { type: string, description: Payload for postback-type buttons }
phone: { type: string, description: Phone number for phone-type buttons (Facebook only) }
template:
type: object
description: Generic template for carousels (Instagram/Facebook only, ignored on Telegram).
properties:
type: { type: string, enum: [generic], description: Template type }
elements:
type: array
maxItems: 10
items:
type: object
required: [title]
properties:
title: { type: string, maxLength: 80, description: Element title (max 80 chars) }
subtitle: { type: string, description: Element subtitle }
imageUrl: { type: string, description: Element image URL }
buttons:
type: array
maxItems: 3
items:
type: object
properties:
type: { type: string, enum: [url, postback] }
title: { type: string, maxLength: 20 }
url: { type: string }
payload: { type: string }
replyMarkup:
type: object
description: Telegram-native keyboard markup. Ignored on other platforms.
properties:
type: { type: string, enum: [inline_keyboard, reply_keyboard], description: Keyboard type }
keyboard:
type: array
description: Array of rows, each row is an array of buttons
items:
type: array
items:
type: object
properties:
text: { type: string, description: Button text }
callbackData: { type: string, maxLength: 64, description: Callback data (inline_keyboard only, max 64 bytes) }
url: { type: string, description: URL to open (inline_keyboard only) }
oneTime: { type: boolean, default: true, description: Hide keyboard after use (reply_keyboard only) }
messagingType:
type: string
enum: [RESPONSE, UPDATE, MESSAGE_TAG]
description: Facebook messaging type. Required when using messageTag.
messageTag:
type: string
enum: [CONFIRMED_EVENT_UPDATE, POST_PURCHASE_UPDATE, ACCOUNT_UPDATE, HUMAN_AGENT]
description: Facebook message tag for messaging outside 24h window. Requires messagingType MESSAGE_TAG. Instagram only supports HUMAN_AGENT.
replyTo:
type: string
description: Platform message ID to reply to (Telegram only).
multipart/form-data:
schema:
type: object
required: [accountId]
properties:
accountId: { type: string, description: Social account ID }
message: { type: string, description: Message text (optional when sending attachment) }
attachment:
type: string
format: binary
description: "File attachment (images, videos, documents). Supported formats: JPEG, PNG, GIF, MP4, AAC, WAV. Max 25MB."
quickReplies:
type: string
description: JSON string of quick replies array (same schema as application/json body)
buttons:
type: string
description: JSON string of buttons array (same schema as application/json body)
template:
type: string
description: JSON string of template object (same schema as application/json body)
replyMarkup:
type: string
description: JSON string of replyMarkup object (same schema as application/json body)
messagingType:
type: string
description: Messaging type (Facebook only). RESPONSE, UPDATE, or MESSAGE_TAG.
messageTag:
type: string
description: Message tag (requires messagingType MESSAGE_TAG)
replyTo:
type: string
description: Platform message ID to reply to (Telegram only)
responses:
'200':
description: Message sent
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
data:
type: object
properties:
messageId: { type: string, description: ID of the sent message (not returned for Reddit) }
conversationId: { type: string, nullable: true, description: Twitter conversation ID }
sentAt: { type: string, format: date-time, nullable: true, description: Bluesky sent timestamp }
message: { type: string, nullable: true, description: Success message (Reddit only) }
'400':
description: Bad request (e.g., attachment not supported for platform, validation error)
content:
application/json:
schema:
type: object
properties:
error: { type: string }
code:
type: string
enum: [PLATFORM_LIMITATION]
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required
/v1/inbox/conversations/{conversationId}/messages/{messageId}:
patch:
operationId: editInboxMessage
summary: Edit message
description: |
Edit the text and/or reply markup of a previously sent Telegram message.
Only supported for Telegram. Returns 400 for other platforms.
tags: [Messages]
security: [{ bearerAuth: [] }]
parameters:
- name: conversationId
in: path
required: true
schema: { type: string }
description: The conversation ID
- name: messageId
in: path
required: true
schema: { type: string }
description: The Telegram message ID to edit
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [accountId]
properties:
accountId: { type: string, description: Social account ID }
text: { type: string, description: New message text }
replyMarkup:
type: object
description: New inline keyboard markup
properties:
type: { type: string, enum: [inline_keyboard] }
keyboard:
type: array
items:
type: array
items:
type: object
properties:
text: { type: string }
callbackData: { type: string }
url: { type: string }
responses:
'200':
description: Message edited
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
data:
type: object
properties:
messageId: { type: integer }
'400':
description: Not supported or invalid request
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required
/v1/accounts/{accountId}/messenger-menu:
get:
operationId: getMessengerMenu
summary: Get FB persistent menu
description: Get the persistent menu configuration for a Facebook Messenger account.
tags: [Account Settings]
security: [{ bearerAuth: [] }]
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Persistent menu configuration
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { type: object } }
'400':
description: Not a Facebook account
'401': { $ref: '#/components/responses/Unauthorized' }
put:
operationId: setMessengerMenu
summary: Set FB persistent menu
description: Set the persistent menu for a Facebook Messenger account. Max 3 top-level items, max 5 nested items.
tags: [Account Settings]
security: [{ bearerAuth: [] }]
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [persistent_menu]
properties:
persistent_menu:
type: array
description: Persistent menu configuration array (Meta format)
items: { type: object }
responses:
'200':
description: Menu set successfully
'400':
description: Invalid request
'401': { $ref: '#/components/responses/Unauthorized' }
delete:
operationId: deleteMessengerMenu
description: Removes the persistent menu from Facebook Messenger conversations for this account.
summary: Delete FB persistent menu
tags: [Account Settings]
security: [{ bearerAuth: [] }]
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Menu deleted
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/accounts/{accountId}/instagram-ice-breakers:
get:
operationId: getInstagramIceBreakers
summary: Get IG ice breakers
description: Get the ice breaker configuration for an Instagram account.
tags: [Account Settings]
security: [{ bearerAuth: [] }]
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Ice breaker configuration
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { type: object } }
'400':
description: Not an Instagram account
'401': { $ref: '#/components/responses/Unauthorized' }
put:
operationId: setInstagramIceBreakers
summary: Set IG ice breakers
description: Set ice breakers for an Instagram account. Max 4 ice breakers, question max 80 chars.
tags: [Account Settings]
security: [{ bearerAuth: [] }]
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [ice_breakers]
properties:
ice_breakers:
type: array
maxItems: 4
items:
type: object
required: [question, payload]
properties:
question: { type: string, maxLength: 80 }
payload: { type: string }
responses:
'200':
description: Ice breakers set successfully
'400':
description: Invalid request
'401': { $ref: '#/components/responses/Unauthorized' }
delete:
operationId: deleteInstagramIceBreakers
description: Removes the ice breaker questions from an Instagram account's Messenger experience.
summary: Delete IG ice breakers
tags: [Account Settings]
security: [{ bearerAuth: [] }]
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Ice breakers deleted
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/accounts/{accountId}/telegram-commands:
get:
operationId: getTelegramCommands
summary: Get TG bot commands
description: Get the bot commands configuration for a Telegram account.
tags: [Account Settings]
security: [{ bearerAuth: [] }]
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Bot commands list
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
type: object
properties:
command: { type: string }
description: { type: string }
'400':
description: Not a Telegram account
'401': { $ref: '#/components/responses/Unauthorized' }
put:
operationId: setTelegramCommands
summary: Set TG bot commands
description: Set bot commands for a Telegram account.
tags: [Account Settings]
security: [{ bearerAuth: [] }]
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [commands]
properties:
commands:
type: array
items:
type: object
required: [command, description]
properties:
command: { type: string, description: Bot command without leading slash }
description: { type: string, description: Command description }
responses:
'200':
description: Commands set successfully
'400':
description: Invalid request
'401': { $ref: '#/components/responses/Unauthorized' }
delete:
operationId: deleteTelegramCommands
description: Clears all bot commands configured for a Telegram bot account.
summary: Delete TG bot commands
tags: [Account Settings]
security: [{ bearerAuth: [] }]
parameters:
- name: accountId
in: path
required: true
schema: { type: string }
responses:
'200':
description: Commands deleted
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/inbox/comments:
get:
operationId: listInboxComments
summary: List commented posts
description: Returns posts with comment counts from all connected accounts. Aggregates data across multiple accounts.
tags: [Comments]
security: [{ bearerAuth: [] }]
parameters:
- name: profileId
in: query
schema: { type: string }
description: Filter by profile ID
- name: platform
in: query
schema: { type: string, enum: [facebook, instagram, twitter, bluesky, threads, youtube, linkedin, reddit] }
description: Filter by platform
- name: minComments
in: query
schema: { type: integer, minimum: 0 }
description: Minimum comment count
- name: since
in: query
schema: { type: string, format: date-time }
description: Posts created after this date
- name: sortBy
in: query
schema: { type: string, enum: [date, comments], default: date }
description: Sort field
- name: sortOrder
in: query
schema: { type: string, enum: [asc, desc], default: desc }
description: Sort order
- name: limit
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
- name: cursor
in: query
schema: { type: string }
- name: accountId
in: query
schema: { type: string }
description: Filter by specific social account ID
responses:
'200':
description: Aggregated posts with comments
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
type: object
properties:
id: { type: string }
platform: { type: string }
accountId: { type: string }
accountUsername: { type: string }
content: { type: string }
picture: { type: string, nullable: true }
permalink: { type: string, nullable: true }
createdTime: { type: string, format: date-time }
commentCount: { type: integer }
likeCount: { type: integer }
cid: { type: string, nullable: true, description: Bluesky content identifier }
subreddit: { type: string, nullable: true, description: Reddit subreddit name }
pagination:
type: object
properties:
hasMore: { type: boolean }
nextCursor: { type: string, nullable: true }
meta:
type: object
properties:
accountsQueried: { type: integer }
accountsFailed: { type: integer }
failedAccounts:
type: array
items:
type: object
properties:
accountId: { type: string }
accountUsername: { type: string, nullable: true }
platform: { type: string }
error: { type: string }
code: { type: string, nullable: true, description: Error code if available }
retryAfter: { type: integer, nullable: true, description: Seconds to wait before retry (rate limits) }
lastUpdated: { type: string, format: date-time }
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required
/v1/inbox/comments/{postId}:
get:
operationId: getInboxPostComments
summary: Get post comments
description: Fetch comments for a specific post. Requires accountId query parameter.
tags: [Comments]
security: [{ bearerAuth: [] }]
parameters:
- name: postId
in: path
required: true
description: Late post ID or platform-specific post ID. Late IDs are auto-resolved. LinkedIn third-party posts accept full activity URN or numeric ID.
schema: { type: string }
- name: accountId
in: query
required: true
schema: { type: string }
- name: subreddit
in: query
schema: { type: string }
description: (Reddit only) Subreddit name
- name: limit
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 25 }
description: Maximum number of comments to return
- name: cursor
in: query
schema: { type: string }
description: Pagination cursor
- name: commentId
in: query
schema: { type: string }
description: (Reddit only) Get replies to a specific comment
responses:
'200':
description: Comments for the post
content:
application/json:
schema:
type: object
properties:
status: { type: string }
comments:
type: array
items:
type: object
properties:
id: { type: string }
message: { type: string }
createdTime: { type: string, format: date-time }
from:
type: object
properties:
id: { type: string }
name: { type: string }
username: { type: string }
picture: { type: string, nullable: true }
isOwner: { type: boolean }
likeCount: { type: integer }
replyCount: { type: integer }
platform: { type: string, description: The platform this comment is from }
url:
type: string
nullable: true
description: Direct link to the comment on the platform (if available)
replies:
type: array
items: { type: object }
canReply: { type: boolean }
canDelete: { type: boolean }
canHide: { type: boolean, description: Whether this comment can be hidden (Facebook, Instagram, Threads) }
canLike: { type: boolean, description: Whether this comment can be liked (Facebook, Twitter/X, Bluesky, Reddit) }
isHidden: { type: boolean, description: Whether the comment is currently hidden }
isLiked: { type: boolean, description: Whether the current user has liked this comment }
likeUri: { type: string, nullable: true, description: Bluesky like URI for unliking }
cid: { type: string, nullable: true, description: Bluesky content identifier }
parentId: { type: string, nullable: true, description: Parent comment ID for nested replies }
rootUri: { type: string, nullable: true, description: Bluesky root post URI }
rootCid: { type: string, nullable: true, description: Bluesky root post CID }
pagination:
type: object
properties:
hasMore: { type: boolean }
cursor: { type: string, nullable: true }
meta:
type: object
properties:
platform: { type: string }
postId: { type: string }
accountId: { type: string }
subreddit: { type: string, nullable: true, description: (Reddit only) Subreddit name }
lastUpdated: { type: string, format: date-time }
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required
post:
operationId: replyToInboxPost
summary: Reply to comment
description: Post a reply to a post or specific comment. Requires accountId in request body.
tags: [Comments]
security: [{ bearerAuth: [] }]
parameters:
- name: postId
in: path
required: true
description: Late post ID or platform-specific post ID. LinkedIn third-party posts accept full activity URN or numeric ID.
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [accountId, message]
properties:
accountId: { type: string }
message: { type: string }
commentId: { type: string, description: Reply to specific comment (optional) }
parentCid: { type: string, description: (Bluesky only) Parent content identifier }
rootUri: { type: string, description: (Bluesky only) Root post URI }
rootCid: { type: string, description: (Bluesky only) Root post CID }
responses:
'200':
description: Reply posted
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
data:
type: object
properties:
commentId: { type: string }
isReply: { type: boolean }
cid: { type: string, nullable: true, description: Bluesky CID }
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required
delete:
operationId: deleteInboxComment
summary: Delete comment
description: |
Delete a comment on a post. Supported by Facebook, Instagram, Bluesky, Reddit, YouTube, and LinkedIn.
Requires accountId and commentId query parameters.
tags: [Comments]
security: [{ bearerAuth: [] }]
parameters:
- name: postId
in: path
required: true
description: Late post ID or platform-specific post ID. LinkedIn third-party posts accept full activity URN or numeric ID.
schema: { type: string }
- name: accountId
in: query
required: true
schema: { type: string }
- name: commentId
in: query
required: true
schema: { type: string }
responses:
'200':
description: Comment deleted
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
data:
type: object
properties:
message: { type: string }
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required
/v1/inbox/comments/{postId}/{commentId}/hide:
post:
operationId: hideInboxComment
summary: Hide comment
description: |
Hide a comment on a post. Supported by Facebook, Instagram, and Threads.
Hidden comments are only visible to the commenter and page admin.
tags: [Comments]
security: [{ bearerAuth: [] }]
parameters:
- name: postId
in: path
required: true
schema: { type: string }
- name: commentId
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [accountId]
properties:
accountId: { type: string, description: The social account ID }
responses:
'200':
description: Comment hidden
content:
application/json:
schema:
type: object
properties:
status: { type: string }
commentId: { type: string }
hidden: { type: boolean }
platform: { type: string }
'400':
description: Platform does not support hiding comments
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required
delete:
operationId: unhideInboxComment
summary: Unhide comment
description: |
Unhide a previously hidden comment. Supported by Facebook, Instagram, and Threads.
tags: [Comments]
security: [{ bearerAuth: [] }]
parameters:
- name: postId
in: path
required: true
schema: { type: string }
- name: commentId
in: path
required: true
schema: { type: string }
- name: accountId
in: query
required: true
schema: { type: string }
responses:
'200':
description: Comment unhidden
content:
application/json:
schema:
type: object
properties:
status: { type: string }
commentId: { type: string }
hidden: { type: boolean }
platform: { type: string }
'400':
description: Platform does not support unhiding comments
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required
/v1/inbox/comments/{postId}/{commentId}/like:
post:
operationId: likeInboxComment
summary: Like comment
description: |
Like or upvote a comment on a post. Supported platforms: Facebook, Twitter/X, Bluesky, Reddit.
For Bluesky, the cid (content identifier) is required in the request body.
tags: [Comments]
security: [{ bearerAuth: [] }]
parameters:
- name: postId
in: path
required: true
schema: { type: string }
- name: commentId
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [accountId]
properties:
accountId: { type: string, description: The social account ID }
cid: { type: string, description: (Bluesky only) Content identifier for the comment }
responses:
'200':
description: Comment liked
content:
application/json:
schema:
type: object
properties:
status: { type: string }
commentId: { type: string }
liked: { type: boolean }
likeUri: { type: string, description: (Bluesky only) URI to use for unliking }
platform: { type: string }
'400':
description: Platform does not support liking comments
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required
delete:
operationId: unlikeInboxComment
summary: Unlike comment
description: |
Remove a like from a comment. Supported platforms: Facebook, Twitter/X, Bluesky, Reddit.
For Bluesky, the likeUri query parameter is required.
tags: [Comments]
security: [{ bearerAuth: [] }]
parameters:
- name: postId
in: path
required: true
schema: { type: string }
- name: commentId
in: path
required: true
schema: { type: string }
- name: accountId
in: query
required: true
schema: { type: string }
- name: likeUri
in: query
schema: { type: string }
description: (Bluesky only) The like URI returned when liking
responses:
'200':
description: Comment unliked
content:
application/json:
schema:
type: object
properties:
status: { type: string }
commentId: { type: string }
liked: { type: boolean }
platform: { type: string }
'400':
description: Platform does not support unliking comments
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required
/v1/inbox/comments/{postId}/{commentId}/private-reply:
post:
operationId: sendPrivateReplyToComment
summary: Send private reply
description: Send a private message to the author of a comment. Supported on Instagram and Facebook only. One reply per comment, must be sent within 7 days, text only.
tags: [Comments]
security: [{ bearerAuth: [] }]
parameters:
- name: postId
in: path
required: true
schema: { type: string }
description: The media/post ID (Instagram media ID or Facebook post ID)
- name: commentId
in: path
required: true
schema: { type: string }
description: The comment ID to send a private reply to
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [accountId, message]
properties:
accountId:
type: string
description: The social account ID (Instagram or Facebook)
message:
type: string
description: The message text to send as a private DM
example:
accountId: "507f1f77bcf86cd799439011"
message: "Hi! Thanks for your comment. I wanted to reach out privately to help with your question."
responses:
'200':
description: Private reply sent successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: success
messageId:
type: string
description: The ID of the sent message
commentId:
type: string
description: The comment ID that was replied to
platform:
type: string
enum: [instagram, facebook]
example: instagram
'400':
description: Bad request
content:
application/json:
schema:
type: object
properties:
error:
type: string
code:
type: string
enum: [PLATFORM_LIMITATION]
examples:
platformNotSupported:
summary: Platform not supported
value:
error: "Private replies to comments are only supported on Instagram and Facebook."
code: "PLATFORM_LIMITATION"
alreadyReplied:
summary: Already sent a private reply
value:
error: "A private reply has already been sent to this comment, or the 7-day reply window has expired. Only one private reply per comment is allowed within 7 days."
commentTooOld:
summary: Comment older than 7 days
value:
error: "The comment is older than 7 days. Private replies can only be sent within 7 days of the comment being posted."
missingMessage:
summary: Missing message
value:
error: "message is required and must be a non-empty string"
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required
'404':
description: Account not found
/v1/inbox/reviews:
get:
operationId: listInboxReviews
summary: List reviews
description: |
Fetch reviews from all connected Facebook Pages and Google Business accounts. Aggregates data with filtering and sorting options.
Supported platforms: Facebook, Google Business.
tags: [Reviews]
security: [{ bearerAuth: [] }]
parameters:
- name: profileId
in: query
schema: { type: string }
- name: platform
in: query
schema: { type: string, enum: [facebook, googlebusiness] }
- name: minRating
in: query
schema: { type: integer, minimum: 1, maximum: 5 }
- name: maxRating
in: query
schema: { type: integer, minimum: 1, maximum: 5 }
- name: hasReply
in: query
schema: { type: boolean }
description: Filter by reply status
- name: sortBy
in: query
schema: { type: string, enum: [date, rating], default: date }
- name: sortOrder
in: query
schema: { type: string, enum: [asc, desc], default: desc }
- name: limit
in: query
schema: { type: integer, minimum: 1, maximum: 50, default: 25 }
- name: cursor
in: query
schema: { type: string }
- name: accountId
in: query
schema: { type: string }
description: Filter by specific social account ID
responses:
'200':
description: Aggregated reviews
content:
application/json:
schema:
type: object
properties:
status: { type: string }
data:
type: array
items:
type: object
properties:
id: { type: string }
platform: { type: string }
accountId: { type: string }
accountUsername: { type: string }
reviewer:
type: object
properties:
id: { type: string, nullable: true }
name: { type: string }
profileImage: { type: string, nullable: true }
rating: { type: integer }
text: { type: string }
created: { type: string, format: date-time }
hasReply: { type: boolean }
reply:
type: object
nullable: true
properties:
id: { type: string }
text: { type: string }
created: { type: string, format: date-time }
reviewUrl: { type: string, nullable: true }
pagination:
type: object
properties:
hasMore: { type: boolean }
nextCursor: { type: string, nullable: true }
meta:
type: object
properties:
accountsQueried: { type: integer }
accountsFailed: { type: integer }
failedAccounts:
type: array
items:
type: object
properties:
accountId: { type: string }
accountUsername: { type: string, nullable: true }
platform: { type: string }
error: { type: string }
code: { type: string, nullable: true, description: Error code if available }
retryAfter: { type: integer, nullable: true, description: Seconds to wait before retry (rate limits) }
lastUpdated: { type: string, format: date-time }
summary:
type: object
properties:
totalReviews: { type: integer }
averageRating: { type: number, nullable: true }
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required
/v1/inbox/reviews/{reviewId}/reply:
post:
operationId: replyToInboxReview
summary: Reply to review
description: Post a reply to a review. Requires accountId in request body.
tags: [Reviews]
security: [{ bearerAuth: [] }]
parameters:
- name: reviewId
in: path
required: true
schema: { type: string }
description: Review ID (URL-encoded for Google Business)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [accountId, message]
properties:
accountId: { type: string }
message: { type: string }
responses:
'200':
description: Reply posted
content:
application/json:
schema:
type: object
properties:
status: { type: string }
reply:
type: object
properties:
id: { type: string }
text: { type: string }
created: { type: string, format: date-time }
platform: { type: string }
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required
delete:
operationId: deleteInboxReviewReply
summary: Delete review reply
description: Delete a reply to a review (Google Business only). Requires accountId in request body.
tags: [Reviews]
security: [{ bearerAuth: [] }]
parameters:
- name: reviewId
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [accountId]
properties:
accountId: { type: string }
responses:
'200':
description: Reply deleted
content:
application/json:
schema:
type: object
properties:
status: { type: string }
message: { type: string }
platform: { type: string }
'401': { $ref: '#/components/responses/Unauthorized' }
'403':
description: Inbox addon required