All API requests require authentication using an API key in the Authorization header.
List team users (root + invited) and fetch a specific user by ID.
Returns all users visible to the authenticated account.
Get a single user (self or invited) by ID.
Submit self or link posts to subreddits. Provide platformSpecificData.subreddit per post, or set a default subreddit in account settings.
curl -X POST https://getlate.dev/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "content": "Discussion thread for our latest article",
  "platforms": [
    {
      "platform": "reddit",
      "accountId": "REDDIT_ACCOUNT_ID",
      "platformSpecificData": { "subreddit": "reactjs", "url": "https://example.com/article" }
    }
  ]
}'Fetch subreddit posts (hot/new/top/rising) using your connected Reddit account.
Query params:
curl "https://getlate.dev/api/v1/reddit/feed?accountId=ACCOUNT_ID&subreddit=r/reactjs&sort=hot&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY"
{
  "items": [
    {
      "id": "abc123",
      "fullname": "t3_abc123",
      "title": "Interesting discussion",
      "author": "example_user",
      "subreddit": "reactjs",
      "url": "https://example.com",
      "permalink": "https://www.reddit.com/r/reactjs/comments/abc123/...",
      "selftext": "...",
      "createdUtc": 1700000000,
      "score": 123,
      "numComments": 45,
      "over18": false,
      "stickied": false,
      "flairText": null
    }
  ],
  "after": "t3_def456",
  "before": null
}Manage API keys for your account.
List API keys for the current user.
Create a new API key.
{
  "name": "CI Token",
  "permissions": ["posts:read", "posts:write"],
  "expiresIn": 30
}Delete an API key by ID.
Search posts in a subreddit or across Reddit.
Query params:
curl "https://getlate.dev/api/v1/reddit/search?accountId=ACCOUNT_ID&subreddit=r/technology&q=AI&sort=new&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY"
{
  "items": [
    {
      "id": "ghi789",
      "fullname": "t3_ghi789",
      "title": "AI is transforming workflows",
      "author": "another_user",
      "subreddit": "technology",
      "url": "https://example.com",
      "permalink": "https://www.reddit.com/r/technology/comments/ghi789/...",
      "selftext": "...",
      "createdUtc": 1700000001,
      "score": 98,
      "numComments": 12,
      "over18": false,
      "stickied": false,
      "flairText": null
    }
  ],
  "after": null,
  "before": null
}Generate secure, time-limited invite links to grant access to your profiles or allow others to connect social accounts on your behalf. All invites expire after 7 days.
Create a team member invite token. Invited users can sign up and access specified profiles or all your profiles.
{
  "scope": "all",
  "profileIds": ["profile_id_1", "profile_id_2"]
}{
  "token": "abc123def456...",
  "scope": "profiles",
  "invitedProfileIds": ["profile_id_1", "profile_id_2"],
  "expiresAt": "2024-01-22T00:00:00.000Z",
  "inviteUrl": "https://getlate.dev/signup?inviteToken=abc123def456..."
}curl -X POST https://getlate.dev/api/v1/invite/tokens \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scope": "all"
  }'curl -X POST https://getlate.dev/api/v1/invite/tokens \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scope": "profiles",
    "profileIds": ["profile_id_1", "profile_id_2"]
  }'List all platform connection invites you've created.
{
  "invites": [
    {
      "_id": "invite_id_123",
      "token": "def456ghi789...",
      "userId": "user_id",
      "profileId": {
        "_id": "profile_id",
        "name": "Client Profile"
      },
      "platform": "instagram",
      "inviterName": "Your Name",
      "inviterEmail": "you@example.com",
      "expiresAt": "2024-01-22T00:00:00.000Z",
      "isUsed": false,
      "createdAt": "2024-01-15T00:00:00.000Z"
    }
  ]
}Create a platform connection invite. Perfect for client onboarding - they connect their social account without accessing your Late account.
{
  "profileId": "profile_id_123",
  "platform": "instagram"
}{
  "invite": {
    "_id": "invite_id_123",
    "token": "def456ghi789...",
    "userId": "user_id",
    "profileId": "profile_id_123",
    "platform": "instagram",
    "inviterName": "Your Name",
    "inviterEmail": "you@example.com",
    "expiresAt": "2024-01-22T00:00:00.000Z",
    "isUsed": false,
    "createdAt": "2024-01-15T00:00:00.000Z",
    "inviteUrl": "https://getlate.dev/api/v1/platform-invites/def456.../connect"
  }
}curl -X POST https://getlate.dev/api/v1/platform-invites \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profileId": "profile_id_123",
    "platform": "instagram"
  }'Revoke an unused platform connection invite. Only unused invites can be deleted.
List and set a default subreddit for a connected Reddit account.
Returns subreddits you can post to from this account and the current default (if any).
{
  "subreddits": [
    { "name": "programming", "display_name_prefixed": "r/programming" },
    { "name": "reactjs", "display_name_prefixed": "r/reactjs" }
  ],
  "defaultSubreddit": "programming"
}Set the default subreddit used when a post doesn't specify one.
{
  "defaultSubreddit": "reactjs"
}Post image or video Pins to boards. Provide at least one image or a single video, plus a target board. Optionally set a destination link. Video Pins require a cover image (we accept a provided image URL or auto-select a key frame).
curl -X POST https://getlate.dev/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "content": "My new pin — learn more on our site",
  "platforms": [
    {
      "platform": "pinterest",
      "accountId": "PINTEREST_ACCOUNT_ID",
      "platformSpecificData": { "boardId": "BOARD_ID", "link": "https://example.com" }
    }
  ],
  "mediaItems": [
    { "type": "image", "url": "https://.../image.jpg" }
  ]
}'curl -X POST https://getlate.dev/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "content": "Quick tip video — visit our site for more",
  "platforms": [
    {
      "platform": "pinterest",
      "accountId": "PINTEREST_ACCOUNT_ID",
      "platformSpecificData": { 
        "boardId": "BOARD_ID",
        "link": "https://example.com",
        "coverImageUrl": "https://.../cover.jpg"  
      }
    }
  ],
  "mediaItems": [
    { "type": "video", "url": "https://.../video.mp4" }
  ]
}'mediaItemsmediaItemsplatformSpecificData.coverImageUrl or include an image in mediaItems; otherwise we default to cover_image_key_frame_time=0. You may override the key frame time via platformSpecificData.coverImageKeyFrameTime (seconds).platformSpecificData.boardId required (or set a default board)platformSpecificData.link sets the Pin's destination URLcontentList boards for a connected Pinterest account and the configured default.
{
  "boards": [
    { "id": "123", "name": "Inspiration" },
    { "id": "456", "name": "Engineering" }
  ],
  "defaultBoardId": "123"
}Set a default board for posts that do not include platformSpecificData.boardId.
{
  "defaultBoardId": "123",
  "defaultBoardName": "Inspiration"
}Late offers 4 plan tiers with different usage limits. All limits are enforced at the API level and reset based on your billing period.
Save 40% with annual billing! Usage limits reset on your billing anniversary.
Your usage is tracked in real-time and enforced at every API call. Usage resets automatically based on your billing period.
When you exceed your plan limits, the API returns detailed error messages with upgrade suggestions.
{
  "error": "Post limit reached. Your Free plan allows 10 posts per month. You have used 10.",
  "planName": "Free",
  "limit": 10,
  "current": 10,
  "billingPeriod": "monthly"
}{
  "error": "Profile limit reached. Your basic plan allows 10 profiles. You currently have 10.",
  "planName": "basic",
  "limit": 10,
  "current": 10,
  "details": {
    "resource": "profiles",
    "plan": "basic",
    "currentUsage": 10,
    "limit": 10,
    "window": "total"
  }
}Here's how to set up profiles and schedule your first post:
Late uses a profile-based architecture to organize your social media accounts. Each profile can have one connected account per platform, allowing you to manage multiple brands, clients, or personal accounts separately.
Get all profiles for your account.
{
  "profiles": [
    {
      "_id": "profile_id",
      "name": "Personal Brand",
      "description": "My personal accounts", 
      "color": "#ffeda0",
      "isDefault": true,
      "createdAt": "2024-01-01T00:00:00Z"
    }
  ]
}Create a new profile. Subject to plan limits.
⚠️ Plan Limits: Free (2), Basic (10), Professional (50), Advanced (150), Enterprise (250) profiles
Profile limits are enforced in real-time. Creating a profile when at your limit returns HTTP 403.
{
  "name": "Business Account",
  "description": "Company social media",
  "color": "#4ade80"
}Update an existing profile.
Delete a profile. Cannot delete profiles with connected accounts.
Monitor your current usage against plan limits in real-time.
Get current usage statistics for your account.
{
  "planName": "Accelerate",
  "billingPeriod": "yearly",
  "limits": {
    "uploads": -1,
    "profiles": 50,
  },
  "usage": {
    "uploads": 847,
    "profiles": 12,
    "lastReset": "2024-01-01T00:00:00.000Z"
  },
  "canUpload": true,
  "canCreateProfile": true
}Schedule and manage social media posts across multiple platforms. Upload limits apply based on your plan.
⚠️ Upload Limits: Free (10/month), Basic (120/month), Professional, Advanced & Enterprise (unlimited)
Retrieve a list of your scheduled and published posts with pagination.
status: "scheduled" plus queuedFromProfile in the post object.status=scheduled and filter client-side for posts where queuedFromProfile is present.Create and schedule a new post across multiple social media platforms. Subject to upload limits.
⚠️ Upload Limits: Each successful post creation counts toward your monthly/yearly limit (drafts don't count)
🔗 Getting Account IDs: To get the account IDs for your platforms, use GET /v1/accounts?profileId=PROFILE_ID. Each connected social media account has a unique ID that you need to specify in the platforms array.
Get the next available slot via GET /v1/queue/next-slot, then create your post with that scheduledFor, the returned timezone, and include queuedFromProfile to mark it as queued.
{
  "content": "Queued via API",
  "scheduledFor": "2024-01-17T22:00:00Z",
  "timezone": "America/New_York",
  "platforms": [
    {"platform": "twitter", "accountId": "ACCOUNT_ID"}
  ],
  "queuedFromProfile": "PROFILE_ID"
}Note: Queued posts are returned with status: "scheduled" and an extra queuedFromProfile field.
Note: This endpoint does not read a top-level profileId field. Resolve and pass per-platform accountId values instead.
New: If you omit the platforms field for non-draft posts, Late automatically targets all of your accessible, active accounts (invite restrictions and profile scoping are respected). Draft behavior is unchanged.
curl -X POST https://getlate.dev/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "content": "We ship default-to-all 🎉",
  "publishNow": true
}'Use platforms[].customContent to set a unique caption per Instagram account. If omitted, the main content is used as the default caption for that account.
{
  "content": "Default caption",
  "platforms": [
    {
      "platform": "instagram",
      "accountId": "ACCOUNT_ID_1",
      "customContent": "Unique caption for account 1",
      "platformSpecificData": { "contentType": "reel", "collaborators": [] }
    },
    {
      "platform": "instagram",
      "accountId": "ACCOUNT_ID_2",
      "customContent": "Unique caption for account 2",
      "platformSpecificData": { "contentType": "reel", "collaborators": [] }
    }
  ],
  "mediaItems": [{ "type": "video", "url": "https://..." }]
}collaborators supports up to 3 usernames (without @).Automatically add a first comment to your Instagram post or Reel by including platformSpecificData.firstComment. Not applied to Stories.
{
  "content": "New reel just dropped!",
  "platforms": [
    {
      "platform": "instagram",
      "accountId": "INSTAGRAM_ACCOUNT_ID",
      "platformSpecificData": {
        "contentType": "reel",
        "firstComment": "Full video and links: https://example.com ✨"
      }
    }
  ],
  "mediaItems": [
    { "type": "video", "url": "https://.../reel.mp4" }
  ]
}Tag Instagram users in photos by specifying their username and position coordinates. X and Y values range from 0.0 to 1.0, representing position from the top-left corner. Only works for image posts and carousels (not stories or videos).
{
  "content": "Amazing photo with friends!",
  "platforms": [
    {
      "platform": "instagram",
      "accountId": "INSTAGRAM_ACCOUNT_ID",
      "platformSpecificData": {
        "contentType": "post",
        "userTags": [
          { "username": "friend_username", "x": 0.3, "y": 0.5 },
          { "username": "another_friend", "x": 0.7, "y": 0.5 }
        ]
      }
    }
  ],
  "mediaItems": [{ "type": "image", "url": "https://..." }]
}Create multi-tweet threads by passing platformSpecificData.threadItems on Twitter platforms. Tweet 1 can use the main content; subsequent replies are specified in threadItems. Each tweet supports media on the first item only.
{
  "content": "Tweet 1 — intro",
  "scheduledFor": "2024-01-01T12:00:00",
  "timezone": "UTC",
  "platforms": [
    {
      "platform": "twitter",
      "accountId": "TWITTER_ACCOUNT_ID",
      "platformSpecificData": {
        "threadItems": [
          { "content": "Tweet 1 — intro", "mediaItems": [{ "type": "image", "url": "https://.../img.jpg" }] },
          { "content": "Tweet 2 — details" },
          { "content": "Tweet 3 — CTA" }
        ]
      }
    }
  ]
}thread with per-tweet permalinks in the publish result. Only the first tweet can include media currently.Create multi-post Threads conversations by passing platformSpecificData.threadItems on Threads platforms. Post 1 can use the main content; subsequent replies are specified in threadItems. Only the first post supports media in the composer.
{
  "content": "Post 1 — intro",
  "scheduledFor": "2024-01-01T12:00:00",
  "timezone": "UTC",
  "platforms": [
    {
      "platform": "threads",
      "accountId": "THREADS_ACCOUNT_ID",
      "platformSpecificData": {
        "threadItems": [
          { "content": "Post 1 — intro", "mediaItems": [{ "type": "image", "url": "https://.../img.jpg" }] },
          { "content": "Post 2 — details" },
          { "content": "Post 3 — CTA" }
        ]
      }
    }
  ]
}thread with per-post permalinks in the publish result. Requires additional OAuth scope threads_manage_replies; users may need to re-connect.Create multi-post threads on Bluesky by passing platformSpecificData.threadItems on Bluesky platforms. Post 1 can use the main content; subsequent replies are specified in threadItems. Only the first post supports media in the composer.
{
  "content": "Post 1 — intro",
  "scheduledFor": "2024-01-01T12:00:00",
  "timezone": "UTC",
  "platforms": [
    {
      "platform": "bluesky",
      "accountId": "BLUESKY_ACCOUNT_ID",
      "platformSpecificData": {
        "threadItems": [
          { "content": "Post 1 — intro", "mediaItems": [{ "type": "image", "url": "https://.../img.jpg" }] },
          { "content": "Post 2 — details" },
          { "content": "Post 3 — CTA" }
        ]
      }
    }
  ]
}thread with per-post permalinks in the publish result.Automatically add a first comment to your Facebook Page post. Great for CTAs, links, or extra context.
{
  "content": "New feature live today!",
  "platforms": [
    {
      "platform": "facebook",
      "accountId": "FACEBOOK_ACCOUNT_ID",
      "platformSpecificData": {
        "firstComment": "Read more here: https://example.com 🚀"
      }
    }
  ]
}pages_manage_engagement and pages_read_user_content; you may need to reconnect Facebook to grant them.Automatically add a first comment to your LinkedIn post. Great for CTAs, links, or extra context.
{
  "content": "New feature announcement!",
  "platforms": [
    {
      "platform": "linkedin",
      "accountId": "LINKEDIN_ACCOUNT_ID",
      "platformSpecificData": {
        "firstComment": "Learn more about this feature: https://example.com 🚀"
      }
    }
  ]
}w_member_social or w_organization_social.Automatically add a first comment to your Instagram post or Reel. Great for CTAs, links, or extra context. Not applied to Stories.
{
  "content": "New reel just dropped!",
  "platforms": [
    {
      "platform": "instagram",
      "accountId": "INSTAGRAM_ACCOUNT_ID",
      "platformSpecificData": {
        "firstComment": "Full video and links: https://example.com ✨"
      }
    }
  ],
  "mediaItems": [
    { "type": "video", "url": "https://.../reel.mp4" }
  ]
}Create clickable mentions in your LinkedIn posts that notify the mentioned person or company. Use the URN format to tag users.
{
  "content": "Great discussion with @[John Doe](urn:li:person:abc123) and @[Acme Corp](urn:li:organization:456789) about innovation! 🚀",
  "platforms": [
    {
      "platform": "linkedin",
      "accountId": "LINKEDIN_ACCOUNT_ID"
    }
  ]
}Mention Format:
@[Display Name](urn:li:person:PERSON_ID) - Tags a person (clickable, sends notification)@[Company Name](urn:li:organization:ORG_ID) - Tags a company (clickable, sends notification)@username - Plain text only, not clickable (LinkedIn API requires URN format)How to Find URNs:
linkedin.com/company/1337/urn:li:organization:1337{
  "content": "Your post content here",
  "platforms": [
    {"platform": "twitter", "accountId": "TWITTER_ACCOUNT_ID"},
    {"platform": "instagram", "accountId": "INSTAGRAM_ACCOUNT_ID"},
    {"platform": "linkedin", "accountId": "LINKEDIN_ACCOUNT_ID"},
    {"platform": "threads", "accountId": "THREADS_ACCOUNT_ID"},
    {"platform": "youtube", "accountId": "YOUTUBE_ACCOUNT_ID"}
  ],
  "scheduledFor": "2024-01-01T12:00:00",
  "timezone": "America/New_York",
  "publishNow": false,
  "isDraft": false,
  "visibility": "public|private|unlisted",
  "tags": ["programming", "tutorial", "api", "coding"],
  "mediaItems": [
    {
      "type": "image|video|gif|document",
      "url": "media_url_from_/v1/media",
      "filename": "optional_filename"
    }
  ]
}🕐 Timezone Handling: Two formats are accepted for scheduledFor:
→ Option A (recommended): Local YYYY-MM-DDTHH:mm (no Z) together with a valid timezone (e.g., "America/New_York").
→ Option B: ISO UTC with Z (e.g., 2025-01-15T16:00:00Z). In this case, timezone can be "UTC" or omitted.
Note: platformSpecificData must be nested inside each platforms[] item. Top-level platformSpecificData is ignored.
Upload a PDF (application/pdf) via /v1/media and include it as a document in mediaItems to attach it to a LinkedIn post. Limitations: one document per post; size ≤ 100MB; up to ~300 pages.
{
  "content": "Our new product brochure",
  "platforms": [
    { "platform": "linkedin", "accountId": "LINKEDIN_ACCOUNT_ID" }
  ],
  "mediaItems": [
    {
      "type": "document",
      "url": "https://.../brochure.pdf",
      "filename": "brochure.pdf",
      "mimeType": "application/pdf"
    }
  ]
}🏷️ Tags: The tags field is an optional array primarily used by YouTube for search optimization. Tags should be plain keywords that describe your content. YouTube automatically processes these according to platform constraints (duplicates removed, per-tag ≤ 100 chars, combined ≤ 500). ~15 tags are recommended, but not enforced.
→ Posts are sent using the social accounts connected to the specified profile. Each profile can have one account per platform.
When a platform publishes successfully, its public URL is exposed as post.platforms[].platformPostUrl. For scheduled posts, this appears after the job runs; fetch it later via GET /v1/posts/[postId] or GET /v1/posts.
{
  "post": {
    "platforms": [
      {
        "platform": "instagram",
        "platformPostUrl": "https://www.instagram.com/p/XXXXXXXX/"
      }
    ]
  }
}Upload a CSV to validate and schedule multiple posts at once. Supports a dry-run mode for validation without creating posts.
Authorization: Bearer YOUR_API_KEYmultipart/form-data with a single field file (text/csv)?dryRun=true to validate the CSV without creating postscurl -X POST https://getlate.dev/api/v1/posts/bulk-upload?dryRun=true \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@/absolute/path/to/bulk.csv;type=text/csv"
curl -X POST https://getlate.dev/api/v1/posts/bulk-upload \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@/absolute/path/to/bulk.csv;type=text/csv"
platforms, profiles, and either schedule_time+tz or publish_now=true/is_draft=true or use_queue=truepost_content or title (YouTube can rely on title/description fields)media_urls must be http(s) URLsprofiles accepts profile ObjectIds or exact profile namesuse_queue=true to auto-assign next available slot (requires exactly 1 profile)custom_content_[platform], custom_media_[platform], twitter_thread_items, threads_thread_items, youtube_*, instagram_*, tiktok_*{
  "total": 12,
  "valid": 10,
  "invalid": 2,
  "results": [
    { "rowIndex": 1, "ok": true, "createdPostId": "66..." },
    { "rowIndex": 2, "ok": false, "errors": ["profiles_missing"] }
  ],
  "warnings": []
}Get details of a specific post by ID.
Update a post. Only draft, scheduled, failed, and partial posts can be edited.
📝 Editable Post Statuses: draft, scheduled, failed, partial
❌ Non-editable: published, publishing, cancelled
💡 Edit Failed Posts: You can now edit failed or partial posts to fix any issues before retrying. This is useful for correcting content errors, updating media, or adjusting platform selections after a failed publish attempt.
You can set or clear queuedFromProfile when updating a draft/scheduled post. To move it into a queue, set the profile ID and provide a new scheduledFor/timezone (e.g., from /v1/queue/next-slot). To remove from queue, set queuedFromProfile to null and keep the time.
{
  "queuedFromProfile": "PROFILE_ID",
  "scheduledFor": "2024-01-17T22:00:00Z",
  "timezone": "America/New_York"
}Same structure as POST /v1/posts, all fields are optional:
{
  "content": "Updated post content",
  "scheduledFor": "2024-01-01T15:00:00",
  "timezone": "America/New_York",
  "platforms": [
    {"platform": "twitter", "accountId": "TWITTER_ACCOUNT_ID"},
    {"platform": "linkedin", "accountId": "LINKEDIN_ACCOUNT_ID"},
    {"platform": "threads", "accountId": "THREADS_ACCOUNT_ID"},
    {"platform": "youtube", "accountId": "YOUTUBE_ACCOUNT_ID"}
  ],
  "tags": ["updated", "tutorial", "new"],
  "isDraft": false,
  "publishNow": false,
  "mediaItems": [...]
}Posting to TikTok? See TikTok Direct Posting for required settings. If you omit them, we apply safe defaults automatically.
Delete a post. Published posts cannot be deleted. All other statuses (draft, scheduled, publishing, failed, cancelled) can be deleted.
⚠️ Deletable Post Statuses: draft, scheduled, publishing, failed, cancelled
❌ Non-deletable: published (posts that have been successfully published cannot be deleted)
Retry publishing a failed post. Only failed posts can be retried.
Upload media files (images/videos) for use in posts. Supports files up to 5GB. Note: large files use a JSON client-upload flow; raw multipart uploads may hit platform body-size limits.
/v1/media/v1/media (returns a direct upload token). Raw multipart may fail due to function body-size limits./v1/media to receive a direct upload token, then upload the file directly to storage. The dashboard uses this automatically. Raw multipart to the API route may fail for very large payloads.upload-large.mjs with this content:import { upload } from '@vercel/blob/client';
import fs from 'node:fs';
const apiKey = process.env.Late_API_KEY;
if (!apiKey) { console.error('Missing Late_API_KEY'); process.exit(1); }
const filepath = process.argv[2];
if (!filepath) { console.error('Usage: node upload-large.mjs <path-to-file>'); process.exit(1); }
const data = fs.readFileSync(filepath);
const filename = filepath.split('/').pop() || 'file.bin';
const res = await upload(filename, data, {
  access: 'public',
  handleUploadUrl: 'https://getlate.dev/api/v1/media',
  headers: { Authorization: 'Bearer ' + apiKey },
  multipart: true,
  // set a valid type for your file
  contentType: filename.endsWith('.mp4') ? 'video/mp4' : 'image/jpeg'
});
console.log('Uploaded to:', res.url);# Install
npm i @vercel/blob
# Set API key
export Late_API_KEY=YOUR_API_KEY
# Upload large file via token flow
node -e "import fs from 'node:fs';import('@vercel/blob/client').then(async ({upload})=>{const data=fs.readFileSync('path/to/large.mp4');const res=await upload('large.mp4',data,{access:'public',handleUploadUrl:'https://getlate.dev/api/v1/media',headers:{Authorization:'Bearer '+process.env.Late_API_KEY},multipart:true,contentType:'video/mp4'});console.log(res.url);}).catch(e=>{console.error(e);process.exit(1);})"multipart: true for large files to enable chunked upload.Authorization header must be sent to /v1/media for token generation.contentType to a valid type (e.g., video/mp4, image/jpeg).Authorization: Bearer YOUR_API_KEY./v1/media supports both flows; use JSON client-upload for large filesCommon error responses:
Get connected social media accounts, optionally filtered by profile.
{
  "accounts": [
    {
      "_id": "account_id",
      "profileId": "profile_id",
      "platform": "instagram",
      "username": "your_username",
      "displayName": "Your Display Name",
      "profilePicture": "https://...",
      "isActive": true,
      "tokenExpiresAt": "2024-12-31T23:59:59Z",
      "permissions": ["posts:write", "posts:read"]
    }
  ]
}Initiate OAuth connection for a platform to a specific profile.
By default, users are redirected to the dashboard after connecting an account. Use redirect_url to redirect to your own application instead.
Complete the OAuth token exchange manually (server-side flows).
{
  "code": "OAUTH_CODE",
  "state": "STATE_FROM_GET",
  "profileId": "PROFILE_ID"
}Connect Bluesky using identifier + app password.
{
  "identifier": "you@example.com",
  "appPassword": "xxxx-xxxx-xxxx-xxxx",
  "state": "STATE",
  "redirectUri": "https://yourapp.com/connected"
}Select LinkedIn personal vs organization after OAuth; optionally provide organization details.
{
  "profileId": "PROFILE_ID",
  "tempToken": "TEMP_TOKEN",
  "userProfile": {},
  "accountType": "organization",
  "selectedOrganization": { "id": "12345", "urn": "urn:li:organization:12345", "name": "Company" }
}Update an existing social account's metadata.
{
  "username": "@new_handle",
  "displayName": "New Display Name"
}Disconnect a social media account from its profile.
Reuse OAuth connections across multiple profiles while targeting different pages or organizations. Perfect for managing multiple brands with the same underlying social media accounts.
Import an existing connection from another profile to the target profile. Useful for reusing OAuth tokens while targeting different pages or organizations. Supports both API key and session authentication.
This endpoint allows you to reuse an existing platform connection (OAuth token) from one profile in another profile, while optionally targeting different pages (Facebook) or organizations (LinkedIn). Perfect for managing multiple brands with the same underlying social media account.
{
  "sourceAccountId": "source_account_id_123",
  "targetPageId": "facebook_page_id_456",
  "targetPageName": "Target Facebook Page",
  "targetPageAccessToken": "page_access_token...",
  "targetOrganizationId": "linkedin_org_id_789",
  "targetOrganizationUrn": "urn:li:organization:789",
  "targetOrganizationName": "Target LinkedIn Company",
  "targetAccountType": "organization"
}{
  "message": "Connection cloned successfully",
  "connection": {
    "_id": "new_connection_id",
    "platform": "facebook",
    "username": "Target Facebook Page",
    "displayName": "Target Facebook Page",
    "isActive": true,
    "profileId": "target_profile_id"
  }
}Facebook connections require special handling because users can manage multiple pages. Late provides dedicated endpoints for page selection and management.
Unlike other platforms, connecting Facebook requires selecting which page to post to. Users are redirected to a page selection interface after OAuth authorization.
Get available Facebook pages for selection during connection process.
{
  "pages": [
    {
      "id": "page_id_123",
      "name": "My Business Page",
      "access_token": "page_access_token_...",
      "category": "Business",
      "tasks": ["MANAGE", "CREATE_CONTENT"]
    }
  ]
}Connect a specific Facebook page to a profile.
{
  "profileId": "profile_id_123",
  "pageId": "page_id_456",
  "tempToken": "facebook_temp_token...",
  "userProfile": {
    "id": "user_facebook_id",
    "name": "User Name",
    "profilePicture": "https://..."
  }
}{
  "message": "Facebook page connected successfully",
  "account": {
    "platform": "facebook",
    "username": "My Business Page",
    "displayName": "My Business Page",
    "isActive": true,
    "selectedPageName": "My Business Page"
  }
}Update which Facebook page an existing account should post to.
{
  "selectedPageId": "new_page_id_789"
}Publish ephemeral 24-hour Facebook Page Stories. Stories require media (single image or video) and automatically disappear after 24 hours.
Set platformSpecificData.contentType="story" in your Facebook platform entry to publish as a Story instead of a feed post. Stories work for both immediate and scheduled posts.
curl -X POST https://getlate.dev/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "content": "Behind the scenes today! 📸",
  "platforms": [
    {
      "platform": "facebook",
      "accountId": "FACEBOOK_ACCOUNT_ID",
      "platformSpecificData": {
        "contentType": "story",
        "pageId": "PAGE_ID"
      }
    }
  ],
  "mediaItems": [
    { "type": "image", "url": "https://.../photo.jpg" }
  ],
  "scheduledFor": "2024-01-15T16:00:00",
  "timezone": "UTC"
}'curl -X POST https://getlate.dev/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "content": "Quick update video! 🎥",
  "platforms": [
    {
      "platform": "facebook",
      "accountId": "FACEBOOK_ACCOUNT_ID",
      "platformSpecificData": {
        "contentType": "story",
        "pageId": "PAGE_ID"
      }
    }
  ],
  "mediaItems": [
    { "type": "video", "url": "https://.../video.mp4" }
  ],
  "publishNow": true
}'publishNow: true) and scheduled postsTo post to Facebook feed (regular post), simply omit contentType or set it to anything other than "story".
"platformSpecificData": { "contentType": "story" }"platformSpecificData": {} (or omit contentType)Post to LinkedIn company pages instead of personal profiles.
Step 1: Connect your LinkedIn personal account first:
Step 2: Get your LinkedIn Account ID:
Copy the LinkedIn account's _id field from the response.
Step 3: Switch to company posting using the account management endpoint:
Replace LINKEDIN_ACCOUNT_ID with the account ID from Step 2.
Step 4: Posts will now go to your company page instead of personal profile.
1. Go to your LinkedIn company page
2. Click "Admin tools" or navigate to company/YOUR_ID/admin/
3. Copy the URL with the NUMERIC ID (not company name)
✅ Valid: linkedin.com/company/107655573/
❌ Invalid: linkedin.com/company/company-name/
ACCOUNT_ID from GET /v1/accounts?profileId=PROFILE_IDList organizations available for the connected LinkedIn account.
Automatically generate short-form clips from long-form videos using AI. Perfect for creating social media content from podcasts, webinars, and long videos.
Submit a video URL for AI-powered clipping. Processing is asynchronous.
{
  "video_url": "https://storage.example.com/video.mp4",
  "video_file_name": "my-video.mp4"
}{
  "success": true,
  "job_id": "abc123def456",
  "status": "processing",
  "message": "Video submitted for processing"
}curl -X POST https://getlate.dev/api/v1/video-clips/process \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "video_url": "https://storage.example.com/video.mp4",
    "video_file_name": "my-video.mp4"
  }'List all video clipping jobs with pagination.
Check status and get clips for a specific job.
{
  "job_id": "abc123def456",
  "status": "completed",
  "total_clips": 5,
  "clips": [
    {
      "index": 1,
      "url": "https://clips.example.com/clip-1-clean.mp4",
      "duration": 58.3,
      "start_time": 120.5,
      "end_time": 178.8,
      "watermarkUrl": "https://clips.example.com/clip-1-watermark.mp4",
      "cleanUrl": "https://clips.example.com/clip-1-clean.mp4"
    }
  ]
}Get current usage statistics and limits.
{
  "current": 3,
  "limit": 5,
  "remaining": 2,
  "canCreate": true,
  "hasAddon": false,
  "message": "Free tier (with watermark)"
}Instagram integration only works with Business accounts. Personal and Creator accounts cannot use automated posting APIs due to Instagram's API restrictions. You can convert to a Business account for free in your Instagram app settings.
Regular Instagram posts that appear in your profile feed. Supports images and videos with captions.
{
  "content": "Your post caption here",
  "platforms": [
    {"platform": "instagram", "accountId": "INSTAGRAM_ACCOUNT_ID"}
  ],
  "mediaItems": [
    {
      "type": "image",
      "url": "image_url_1"
    },
    {
      "type": "image", 
      "url": "image_url_2"
    }
  ]
}Instagram Stories that are visible for 24 hours. Supports images and videos with basic user tagging. Due to Instagram API limitations, programmatic stickers, text overlays, and interactive elements are not supported.
{
  "content": "Optional story caption",
  "platforms": [
    {"platform": "instagram", "accountId": "INSTAGRAM_ACCOUNT_ID", "platformSpecificData": {"contentType": "story"}}
  ],
  "mediaItems": [
    {
      "type": "image",
      "url": "image_url_1"
    }
  ]
}Instagram videos are automatically posted as Reels for maximum reach. Custom thumbnails are supported.
{
  "content": "Check out this amazing reel! 🎥",
  "platforms": [
    {"platform": "instagram", "accountId": "INSTAGRAM_ACCOUNT_ID"}
  ],
  "mediaItems": [
    {
      "type": "video",
      "url": "video_url",
      "instagramThumbnail": "thumbnail_image_url"
    }
  ]
}Invite up to 3 collaborators to co-author your Instagram posts. Great for partnerships, influencer collaborations, and multi-account content.
{
  "content": "Amazing collaboration post! 🤝",
  "platforms": [
    {
      "platform": "instagram", 
      "accountId": "INSTAGRAM_ACCOUNT_ID",
      "platformSpecificData": {
        "collaborators": ["username1", "username2", "username3"]
      }
    }
  ],
  "mediaItems": [
    {
      "type": "image",
      "url": "https://your-image-url.jpg"
    }
  ]
}Provide TikTok settings under platformSpecificData.tiktokSettings. If omitted or partially provided, we apply safe defaults and auto-select a valid privacy_level from your creator options.
{
  "content": "My new video 🎥🔥",
  "platforms": [
    {
      "platform": "tiktok",
      "accountId": "TIKTOK_ACCOUNT_ID",
      "platformSpecificData": {
        "tiktokSettings": {
          "privacy_level": "PUBLIC_TO_EVERYONE",
          "video_made_with_ai": true,
          "video_cover_timestamp_ms": 3500,
          "allow_comment": true,
          "allow_duet": true,
          "allow_stitch": true,
          "commercial_content_type": "none",
          "content_preview_confirmed": true,
          "express_consent_given": true
        }
      }
    }
  ],
  "mediaItems": [
    { "type": "video", "url": "https://.../video.mp4" }
  ],
  "scheduledFor": null,
  "timezone": "UTC"
}Notes:
privacy_level must be one of your creator's allowed values (e.g., PUBLIC_TO_EVERYONE, FRIENDS, SELF_ONLY). If not provided, we pick a valid one automatically.video_made_with_ai: optional boolean. When true, we label the post as AI-generated (sent to TikTok as post_info.is_aigc=true).video_cover_timestamp_ms: optional number for videos. Specify the timestamp in milliseconds to select which frame to use as the video thumbnail (defaults to 1000ms/1 second).photo_cover_index: optional number for photo carousels. Specify which image (0-indexed) to use as the cover (defaults to 0/first image).content; any title field under platformSpecificData is ignored.TikTok requires you to declare if your content is commercial. Use commercial_content_type to specify the type of content.
is_brand_organic_post: truebrand_partner_promote: trueprivacy_level: "SELF_ONLY" (private)Example: For a sponsored post, set "commercial_content_type": "brand_content" and "brand_partner_promote": true. The post must be public or friends-only (not private).
Standard YouTube videos that appear in your channel and search results. No duration limits. Supports custom thumbnails and tags.
{
  "content": "Amazing tutorial on building APIs! Check it out 🚀",
  "tags": ["programming", "tutorial", "api", "coding", "javascript"],
  "platforms": [
    {
      "platform": "youtube", 
      "accountId": "YOUTUBE_ACCOUNT_ID",
      "platformSpecificData": {
        "firstComment": "Thanks for watching! What did you think? Don't forget to like and subscribe! 🎥"
      }
    }
  ],
  "mediaItems": [
    {
      "type": "video",
      "url": "https://your-video-url.mp4",
      "thumbnail": "https://your-custom-thumbnail.jpg"
    }
  ],
  "scheduledFor": "2024-01-15T16:00:00Z"
}When you schedule YouTube videos for later, they are uploaded immediately as private and automatically published at your scheduled time. This gives YouTube's algorithm 1+ hours to process your video for better performance!
Short-form videos automatically detected by YouTube based on duration (≤ 3 minutes). Appear in the Shorts feed. Tags are supported. Custom thumbnails cannot be set via the YouTube API for Shorts.
{
  "content": "Check out this amazing short! #Shorts 🔥",
  "tags": ["shorts", "viral", "trending", "quick"],
  "platforms": [
    {
      "platform": "youtube", 
      "accountId": "YOUTUBE_ACCOUNT_ID",
      "platformSpecificData": {
        "title": "Amazing Short Video That Will Blow Your Mind! 🔥",
        "firstComment": "Did you enjoy this Short? Let me know in the comments! 💥"
      }
    }
  ],
  "mediaItems": [
    {
      "type": "video",
      "url": "https://your-vertical-video.mp4"
    }
  ],
  "scheduledFor": "2024-01-15T16:00:00Z"
}YouTube supports custom thumbnails via API for regular videos only. The YouTube API does not allow setting thumbnails for Shorts. For Shorts, YouTube may allow selecting a frame in the mobile app, and thumbnails uploaded in Studio may not display on Shorts feed surfaces.
YouTube supports automatic first comments that are posted immediately after your video uploads. Perfect for engagement, calls-to-action, and encouraging interaction.
firstComment in platformSpecificDataYouTube uses tags for search and recommendation algorithms. Tags help viewers discover your content through search and suggested videos.
Define weekly posting schedules and automatically assign posts to the next available time slot. Perfect for maintaining a consistent posting cadence without manual scheduling.
Get the queue schedule configuration for a specific profile.
{
  "exists": true,
  "schedule": {
    "profileId": "profile_id_123",
    "timezone": "America/New_York",
    "slots": [
      { "dayOfWeek": 1, "time": "09:00" },
      { "dayOfWeek": 3, "time": "14:00" },
      { "dayOfWeek": 5, "time": "17:00" }
    ],
    "active": true,
    "createdAt": "2024-01-01T00:00:00Z",
    "updatedAt": "2024-01-15T00:00:00Z"
  },
  "nextSlots": [
    "2024-01-15T14:00:00Z",
    "2024-01-17T22:00:00Z",
    "2024-01-22T14:00:00Z"
  ]
}Create or update a queue schedule for a profile.
{
  "profileId": "profile_id_123",
  "timezone": "America/New_York",
  "slots": [
    { "dayOfWeek": 1, "time": "09:00" },
    { "dayOfWeek": 3, "time": "14:00" },
    { "dayOfWeek": 5, "time": "17:00" }
  ],
  "active": true,
  "reshuffleExisting": false
}Set reshuffleExisting: true to automatically reschedule existing queued posts to match the new time slots. Posts keep their relative order but get new times based on the updated schedule.
If false (default), existing queued posts keep their current scheduled times and only new posts use the updated schedule.
curl -X PUT https://getlate.dev/api/v1/queue/slots \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profileId": "profile_id_123",
    "timezone": "America/New_York",
    "slots": [
      { "dayOfWeek": 1, "time": "09:00" },
      { "dayOfWeek": 3, "time": "14:00" }
    ],
    "active": true
  }'{
  "success": true,
  "schedule": {
    "profileId": "profile_id_123",
    "timezone": "America/New_York",
    "slots": [
      { "dayOfWeek": 1, "time": "09:00" },
      { "dayOfWeek": 3, "time": "14:00" }
    ],
    "active": true,
    "createdAt": "2024-01-01T00:00:00Z",
    "updatedAt": "2024-01-15T10:30:00Z"
  },
  "nextSlots": [
    "2024-01-15T14:00:00Z",
    "2024-01-17T14:00:00Z"
  ],
  "reshuffledCount": 3
}Delete the queue schedule for a profile. Existing queued posts remain scheduled at their current times.
Preview upcoming queue slots for a profile without considering existing posts.
{
  "profileId": "profile_id_123",
  "count": 10,
  "slots": [
    "2024-01-15T14:00:00Z",
    "2024-01-17T22:00:00Z",
    "2024-01-22T14:00:00Z",
    "2024-01-24T22:00:00Z",
    "2024-01-29T14:00:00Z",
    "2024-01-31T22:00:00Z",
    "2024-02-05T14:00:00Z",
    "2024-02-07T22:00:00Z",
    "2024-02-12T14:00:00Z",
    "2024-02-14T22:00:00Z"
  ]
}Get the next available queue slot for a profile, taking into account already scheduled posts to avoid conflicts. This is the endpoint used when creating posts with "Add to Queue".
{
  "profileId": "profile_id_123",
  "nextSlot": "2024-01-17T22:00:00Z",
  "timezone": "America/New_York"
}Use this endpoint to get the next available slot, then create a post with that time and include queuedFromProfile to mark it as queued:
# Step 1: Get next slot
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://getlate.dev/api/v1/queue/next-slot?profileId=PROFILE_ID"
# Response: { "nextSlot": "2024-01-17T22:00:00Z", "timezone": "America/New_York" }
# Step 2: Create post with that time
curl -X POST https://getlate.dev/api/v1/posts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "My queued post",
    "scheduledFor": "2024-01-17T22:00:00Z",
    "timezone": "America/New_York",
    "platforms": [{"platform": "twitter", "accountId": "ACCOUNT_ID"}],
    "queuedFromProfile": "PROFILE_ID"
  }'next-slot endpoint skips times that already have scheduled postsactive: false) cannot be used for schedulingUnified analytics for your posts. Fetch a single post's analytics by passing postId, or list external posts with analytics, filters, and pagination.
⚠️ Add-on: Requires the Analytics add-on. Without it, the API returns HTTP 402 withcode: "analytics_addon_required" and upgrade info.
30 requests per hour per user. When the limit is exceeded, the API returns HTTP 429 with reset time.
X-RateLimit-Limit - Maximum requests allowed (30)X-RateLimit-Remaining - Requests remaining in current windowX-RateLimit-Reset - ISO timestamp when limit resetsReturn analytics for a single post or a paginated list with overview stats.
date | engagement (default: date)asc | desc (default: desc)API requests are rate limited based on your plan to ensure fair usage:
Rate limits are separate from usage limits. Even with unlimited uploads, you're still subject to rate limits to prevent API abuse.
Have questions or need support? We're here to help!