Upload Photos
Upload photos (and mixed media for supported platforms) to various social media platforms using this endpoint.
Endpoint
POST /api/upload_photos
Headers
| Name | Value | Description |
|---|---|---|
| Authorization | Apikey your-api-key-here | Your API key for authentication |
Common Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| user | String | Yes | User identifier |
| platform[] | Array | Yes | Platform(s) to upload to. Supported values: tiktok, instagram, linkedin, facebook, x, threads, pinterest, bluesky, reddit, google_business |
| photos[] | Array | Yes | Array of files to upload. Accepts photos (jpg, png, etc.). Note: You can also include videos (mp4, mov, etc.) ONLY for Instagram and Threads mixed carousels. |
| title | String | Conditional | Default title/caption of the post. Required for Reddit. Optional for all other platforms (TikTok, Instagram, Facebook, LinkedIn, X, Threads, Bluesky, Pinterest). |
| description | String | No | Optional extended text used on TikTok photo descriptions, LinkedIn commentary, Facebook descriptions, Pinterest notes, and Reddit bodies. Ignored elsewhere. |
| scheduled_date | String (ISO-8601) | No | Optional date/time (ISO-8601) to schedule publishing, e.g., "2024-12-31T23:45:00Z". Must be in the future (≤ 365 days). Omit for immediate upload. |
| timezone | String (IANA) | No | Optional timezone identifier (e.g., "Europe/Madrid", "America/New_York"). If provided, scheduled_date is interpreted in this timezone. Defaults to UTC if omitted. See IANA Time Zone Database for valid values. |
| async_upload | Boolean | No | If true, the request returns immediately with a request_id and processes in the background. See Upload Status. |
| add_to_queue | Boolean | No | If true, automatically schedules the post to your next available queue slot. Cannot be used with scheduled_date. See Queue System. |
| max_posts_per_slot | Integer | No | Override the profile's max posts per slot setting for this request. Only used when add_to_queue=true. See Queue System. |
| first_comment | String | No | Automatically post a first comment after publishing. Supported on Instagram, Facebook, Threads, Bluesky, Reddit, X, and YouTube. On X (Twitter) and Threads, this creates a reply to the main post. For X threads, the comment is posted as a reply to the last tweet in the thread. On YouTube, it posts as a top-level comment on the video. |
Important: If you set
async_uploadtofalsebut the upload takes longer than 59 seconds, it will automatically switch to asynchronous processing to avoid timeouts. In that case, use therequest_idwith the Upload Status endpoint to check the upload status and result.
Platform-Specific First Comments
The first_comment parameter serves as a fallback. To set a custom first comment for a particular platform, use the optional [platform]_first_comment parameter. If provided, it will override the main first_comment for that platform.
Example Optional Parameters:
instagram_first_comment: "Follow for more content! #photography"facebook_first_comment: "Let me know your thoughts in the comments!"x_first_comment: "Thread incoming! 🧵"threads_first_comment: "First comment on Threads!"reddit_first_comment: "Source in the comments."bluesky_first_comment: "More details in the replies."
Scheduling behavior: When you provide
scheduled_date, the API responds with202 Acceptedand includes ajob_id. That samejob_idwill later appear in Upload History to correlate the scheduled job with the publish record. You can also use thejob_idwith the Upload Status endpoint to check the execution status of the scheduled post.
Video Support (Mixed Carousels):
- Instagram & Threads: You can upload videos in the
photos[]array to create mixed carousels (photos + videos).- All other platforms (Facebook, TikTok, LinkedIn, X, Pinterest): Do NOT upload videos to this endpoint. Use the Upload Video endpoint instead. Uploading videos here for these platforms will result in an error.
Platform-Specific Titles
The title parameter serves as a fallback. To set a custom title for a particular platform, use the optional [platform]_title parameter. If provided, it will override the main title for that platform.
Example Optional Parameters:
instagram_title: "Check out my latest reel on Instagram! #reels"facebook_title: "Excited to share this new video with my Facebook friends and family."tiktok_title: "New TikTok video just dropped! 🔥"linkedin_title: "A professional insight on the latest industry trends, discussed in this video."x_title: "New video out now! 📢"
Platform-Specific Parameters
LinkedIn
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| linkedin_title | String | No | Specific title for the LinkedIn post. Fallbacks to title. | title |
| linkedin_description or description | String | No | Sent as the post commentary. If omitted, we reuse title. | title |
| visibility | String | No | Visibility setting for the post (accepted value: "PUBLIC") | PUBLIC |
| target_linkedin_page_id | String | No | LinkedIn page ID to upload photos to an organization | "107579166" |
Facebook
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| facebook_title | String | No | Specific title for the Facebook post. Fallbacks to title. | title |
| facebook_page_id | String | Yes | Facebook Page ID where the photos will be posted | - |
| facebook_media_type | String | No | Type of media ("POSTS" or "STORIES") | "POSTS" |
Note: The caption is applied only to the first photo uploaded. For correct posting on Facebook, ensure the Page is directly associated with your personal profile and not managed through a Business Portfolio.
Note: If
facebook_page_idis not provided, we will automatically use the user's only connected Page (if exactly one exists). If multiple Pages are connected, the API returns a helpful error with anavailable_pageslist so you can choose one. Posting to personal Facebook profiles via API is not supported by Meta; only Pages can be posted to.
X (Twitter)
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| x_title | String | No | Specific title for the tweet. Fallbacks to title. | title |
| x_long_text_as_post | Boolean | No | When true, publishes long text as a single post. Otherwise, creates a thread. | false |
| x_thread_image_layout | String | No | Comma-separated list of how many images to attach to each tweet in the thread. Each value must be 1-4, and the total must equal the number of images. Example: "4,4" puts 4 images in each of 2 tweets; "2,3,1" puts 2 in the first, 3 in the second, 1 in the third. If omitted and more than 4 images are provided, defaults to auto-chunking into groups of 4. | auto |
| reply_settings | String | No | Controls who can reply to the tweet ("following", "mentionedUsers", "subscribers", "verified") | - |
| geo_place_id | String | No | Place ID for adding geographic location to the tweet | - |
| nullcast | Boolean | No | Whether to publish without broadcasting (promotional/promoted-only posts) | false |
| for_super_followers_only | Boolean | No | Tweet exclusive for super followers | false |
| community_id | String | No | Community ID for posting to specific communities | - |
| share_with_followers | Boolean | No | Share community post with followers | false |
| direct_message_deep_link | String | No | Link to take the conversation from public timeline to private Direct Message | - |
| tagged_user_ids | Array | No | Array of user IDs to tag in the photos (max 10 users) | [] |
| reply_to_id | String | No | ID of the tweet to reply to. Creates a reply to the specified tweet. | - |
| exclude_reply_user_ids | Array | No | Array of user IDs to exclude from replying to this tweet. Requires reply_to_id. | [] |
Note: For Twitter uploads, specify the platform as "x" in the platform[] array.
More than 4 images: X supports a maximum of 4 images per tweet. If you provide more than 4 images, the API will automatically create a thread, distributing images across multiple tweets (up to 4 images each). Use
x_thread_image_layoutto control exactly how images are distributed across tweets.
The global description field is ignored for X photo uploads.
How X (Twitter) Thread Creation Works (Advanced Logic)
Note: The following describes the default thread creation logic. To override this and post long text as a single post, set the x_long_text_as_post parameter to true.
The system is engineered to create well-formatted, natural-looking threads on X (formerly Twitter). Instead of simply splitting text at every line break, it intelligently groups paragraphs to create more readable tweets.
Here's the step-by-step logic:
Intelligent Paragraph Grouping (Primary Method):
The function first identifies distinct paragraphs (any text separated by a blank line).
It then combines as many of these paragraphs as possible into a single tweet, filling it up to the 280-character limit without exceeding it. The double newline (\n\n) between combined paragraphs is preserved for formatting.
This results in fewer, more substantial tweets that flow naturally, just as if a person had written them.
Handling Exceptionally Long Paragraphs:
If a single paragraph is, by itself, longer than the 280-character limit, a more granular splitting logic is automatically triggered for that paragraph only:
- Split by Line Break: The system first attempts to break the paragraph down by its individual line breaks (
\n). - Split by Word: If any of those single lines are still too long, it will split them by words as a final resort.
Media Attachment:
Images are distributed across tweets according to the x_thread_image_layout parameter. If not specified and more than 4 images are provided, they are automatically distributed in groups of 4. Text parts and image chunks are interleaved across the thread tweets.
TikTok
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| tiktok_title | String | No | Specific title for the TikTok post (max 90 characters). Fallbacks to title. | title |
| post_mode | String | No | Controls how the upload is handled. DIRECT_POST publishes immediately; MEDIA_UPLOAD sends the media to the TikTok inbox so users can finish editing in-app. | DIRECT_POST |
| privacy_level | String | No | Accepted values: PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY. | PUBLIC_TO_EVERYONE |
| auto_add_music | Boolean | No | Automatically add background music to photos | false |
| disable_comment | Boolean | No | Disable comments on the post | false |
| brand_content_toggle | Boolean | No | Set to true for paid partnerships that promote third-party brands. | false |
| brand_organic_toggle | Boolean | No | Set to true when promoting the creator's own business. | false |
| photo_cover_index | Integer | No | Index (starting at 0) of the photo to use as the cover/thumbnail for the TikTok photo post | 0 |
| tiktok_description or description | String | No | For photo posts, used as description inside post_info (max 4,000 characters). | title |
Note on Draft Mode (
MEDIA_UPLOAD): When usingMEDIA_UPLOADmode (Draft), TikTok does not allow setting a title, caption, privacy settings, or other metadata via the API. The video is simply uploaded to your TikTok inbox/drafts, and you must add the title, caption, and settings manually within the TikTok app before publishing.
Instagram
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| instagram_title | String | No | Specific title for the Instagram post. Fallbacks to title. | title |
| media_type | String | No | Type of media ("IMAGE" or "STORIES"). Automatically handles CAROUSEL/REELS logic if mixed media is detected. | "IMAGE" |
| collaborators | String | No | Comma-separated list of collaborator usernames. | - |
| user_tags | String | No | Comma-separated list of users to tag (e.g., "@user1, user2"). | - |
| location_id | String | No | Instagram location ID. | - |
The global description field is ignored for Instagram uploads (title serves as caption).
Threads
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| threads_title | String | No | Specific title for the Threads post. Fallbacks to title. | title |
| threads_thread_media_layout | String | No | Comma-separated list of how many media items to include in each Threads post. Each value must be 1-10, and the total must equal the number of files. Example: "5,5" splits 10 items into 2 posts of 5 each; "3,4,3" splits 10 items into 3 posts. If omitted and more than 10 items are provided, defaults to auto-chunking into groups of 10. | auto |
More than 10 items: Threads supports a maximum of 10 media items per post (carousel). If you provide more than 10 items, the API will automatically create multiple posts, distributing media across posts (up to 10 items each). Use
threads_thread_media_layoutto control exactly how media items are distributed across posts.
The global description field is ignored for Threads photo uploads.
Pinterest
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| pinterest_title | String | No | Specific title for the Pinterest Pin. Fallbacks to title. | title |
| pinterest_description or description | String | No | Populates the Pin description. If omitted, we reuse title. | title |
| pinterest_board_id | String | Yes | Pinterest board ID to publish the photo to. | - |
| pinterest_alt_text | String | No | Alt text for the image. | - |
| pinterest_link | String | No | Destination link for the photo Pin. | - |
Bluesky
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| bluesky_title | String | No | Specific text for the Bluesky post. Fallbacks to title. | title |
Note: Bluesky supports up to 4 images per post.
Reddit
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| reddit_title | String | No | Specific title for the Reddit post. Fallbacks to title. | title |
| subreddit | String | Yes | Name of the subreddit to post to (without "r/"). | - |
| flair_id | String | No | ID of the flair to apply to the post. | - |
Note: Reddit photo posts support a single image. The image will be uploaded as a native Reddit image post.
Google Business Profile
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| gbp_topic_type | String | No | Post type: STANDARD (default), EVENT, or OFFER. | STANDARD |
| gbp_cta_type | String | No | Call-to-action button: BOOK, ORDER, SHOP, LEARN_MORE, SIGN_UP, CALL. | - |
| gbp_cta_url | String | Conditional | URL for the CTA button. Required if gbp_cta_type is set. | - |
Event parameters (when gbp_topic_type is EVENT):
| Name | Type | Required | Description |
|---|---|---|---|
| gbp_event_title | String | Yes | Title of the event. |
| gbp_event_start_date | String | Yes | Start date in YYYY-MM-DD format. |
| gbp_event_start_time | String | No | Start time in HH:MM format (24h). |
| gbp_event_end_date | String | Yes | End date in YYYY-MM-DD format. |
| gbp_event_end_time | String | No | End time in HH:MM format (24h). |
Offer parameters (when gbp_topic_type is OFFER):
| Name | Type | Required | Description |
|---|---|---|---|
| gbp_coupon_code | String | No | Coupon or promo code. |
| gbp_redeem_url | String | No | URL where the offer can be redeemed. |
| gbp_terms | String | No | Terms and conditions of the offer. |
Example Requests
Upload Photo and Video to Instagram (Carousel)
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'photos[]=@/path/to/image.jpg' \
-F 'photos[]=@/path/to/video.mp4' \
-F 'user="test"' \
-F 'platform[]=instagram' \
-F 'title="My Mixed Carousel"' \
-X POST https://api.upload-post.com/api/upload_photos
Upload Photos to Facebook
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'photos[]=@/path/to/image1.jpg' \
-F 'photos[]=@/path/to/image2.jpg' \
-F 'user="test"' \
-F 'platform[]=facebook' \
-F 'facebook_page_id="123456789"' \
-F 'title="My Photo Album"' \
-X POST https://api.upload-post.com/api/upload_photos
Upload Photo to Reddit
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'photos[]=@/path/to/image.jpg' \
-F 'user="test"' \
-F 'platform[]=reddit' \
-F 'subreddit="pics"' \
-F 'title="Check out this photo!"' \
-X POST https://api.upload-post.com/api/upload_photos
Responses
- 200 OK (synchronous, finished fast)
{
"success": true,
"results": {
"instagram": { "success": true, "url": "https://instagram.com/p/...", "photos_were_processed": true, "changes_per_image": [ {} ] },
"reddit": { "success": false, "error": "Subreddit is required for photo posts to Reddit." }
},
"usage": { "count": 13, "limit": 100, "last_reset": "..." }
}
- 200 OK (asynchronous/background started or sync→background fallback)
{
"success": true,
"message": "Upload initiated successfully in background.",
"request_id": "1a2b3c4d5e...",
"total_platforms": 2
}
- 202 Accepted (scheduled)
{
"success": true,
"job_id": "scheduler_job_456",
"scheduled_date": "2025-09-22T10:00:00Z"
}
-
400 Bad Request
- Missing
user,platform[], Pinterest withoutpinterest_board_id, Reddit withoutsubreddit, invalid platforms, invalidscheduled_date.
- Missing
-
401 Unauthorized:
{ "success": false, "message": "Invalid or expired token" } -
403 Forbidden (plan restrictions)
-
404 Not Found (e.g., user not found)
-
429 Too Many Requests (monthly limit exceeded; includes current usage)
{
"success": false,
"message": "This upload would exceed your monthly limit.",
"usage": { "count": 10, "limit": 10, "last_reset": "..." }
}
- 500 Internal Server Error:
{ "success": false, "error": "Detailed error message" }
Notes
- When async or when sync falls back to background, use
GET /api/uploadposts/status?request_id={request_id}to poll progress. - Per-platform results may include fields like
url,post_id(s), and platform-specific metadata orerror.