Upload Text
Upload text posts to various social media platforms using this endpoint.
Note: Currently, this endpoint supports X (Twitter), LinkedIn, Facebook, Threads, Reddit, Bluesky, Discord, Telegram, and Google Business Profile. More platforms will be added in future updates.
Endpoint
POST /api/upload_text
Headers
| Name | Value | Description |
|---|---|---|
| Authorization | Apikey your-api-key-here | Your API key for authentication |
| Idempotency-Key | unique-string | Optional. Prevents duplicate uploads if the same request is retried (e.g., after a timeout). Can also be sent as X-Idempotency-Key or X-Request-Id. When provided, if a matching upload job already exists, the API returns the existing job instead of creating a duplicate. |
Common Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| user | String | Yes | User identifier |
| platform[] | Array | Yes | Platform(s) to upload to. Supported values: linkedin, x, facebook, threads, reddit, bluesky, discord, telegram, google_business, slack, mastodon, nostr, lemmy, devto, hashnode, wordpress, whop, listmonk |
| title | String | Yes | Default text content for the post. |
| description | String | No | Optional extended body used only on Reddit (becomes the post text). Ignored elsewhere. |
| request_id | String | No | Client-provided request identifier. If omitted, the server generates one. Returned in every response and used to track the upload via Upload Status. Useful when async_upload=true and the HTTP response might be lost (e.g., timeout). Can also be sent as an X-Request-Id header. |
| 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. |
| external_id | String | No | Your own identifier for this post (max 255 chars), echoed back by Scheduled Posts, Upload Status and Upload History. Use it to map a post back to a record in your own system instead of matching on title, which is editable. Can also be sent as an X-External-Id header. It is a label only — reusing one never blocks a publish; for that, send an Idempotency-Key header. |
| 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 Facebook, Threads, Bluesky, X, YouTube, and LinkedIn. On X (Twitter) and Threads, this creates a reply to the main post (threading). On YouTube, it posts as a top-level comment. Note: Instagram does not support text-only posts, so this parameter is not applicable for Instagram here. Reddit posting is currently unavailable (503 reddit_unavailable). |
| first_comment_media[] | File(s) | No | Image files to attach to the first comment as inline images. Reddit-only, and Reddit posting is currently unavailable (503 reddit_unavailable). Not available for scheduled or queued posts. |
| link_url | String | No | URL to include as a link preview card. When provided, platforms that support link previews (LinkedIn, Bluesky, Facebook) will display a rich preview card with the page's title, description, and thumbnail image. Platform-specific parameters (linkedin_link_url, bluesky_link_url, facebook_link_url) take priority over this generic parameter. |
Uploads that take more than 59 seconds switch to async even if async_upload is false — poll Upload Status with the request_id.
scheduled_date returns 202 Accepted and a job_id. Use that id on Upload Status while it runs and on Upload History after it publishes.
This endpoint supports simultaneous text uploads to X (Twitter), LinkedIn, Facebook, Threads, Reddit, Bluesky, Discord, and Telegram.
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:
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."linkedin_first_comment: "Source article in the comments."
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:
linkedin_title: "A professional insight on the latest industry trends."x_title: "New update out now! 📢"facebook_title: "Excited to share this with my Facebook friends."threads_title: "Just posted something new on Threads!"
Platform-Specific Parameters
LinkedIn
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| linkedin_title | String | No | Specific text for the LinkedIn post. Fallbacks to title. | title |
| target_linkedin_page_id | String | No | LinkedIn page ID to upload text to an organization's page. If not provided, posts to the user's personal profile. | |
| linkedin_link_url | String | No | URL to include as a link preview card on the LinkedIn post. LinkedIn will display a rich preview with the page's title, description, and thumbnail. Overrides the generic link_url parameter for LinkedIn. | link_url |
| linkedin_visibility | String | No | PUBLIC, CONNECTIONS, LOGGED_IN, CONTAINER. Aliases: visibility, linkedinVisibility. | PUBLIC |
| linkedin_disable_reshare | Boolean | No | When true, disable reshare. | false |
| linkedin_link_title | String | No | Link-share title (max 400 characters). | - |
| linkedin_link_description | String | No | Link-share description (max 4086 characters). | - |
| linkedin_thumbnail_alt_text | String | No | Alt text for the link thumbnail. | - |
| linkedin_poll_question | String | No | Question of a LinkedIn poll. Max 140 characters. Requires linkedin_poll_options[]. | - |
| linkedin_poll_options[] | Array | No | Poll choices: 2 to 4 options, max 30 characters each. Requires linkedin_poll_question. | - |
| linkedin_poll_duration | String | No | How long the poll stays open: ONE_DAY, THREE_DAYS, SEVEN_DAYS, FOURTEEN_DAYS (the day counts 1, 3, 7, 14 are accepted too). | SEVEN_DAYS |
LinkedIn Polls
Send linkedin_poll_question plus 2-4 linkedin_poll_options[] and the post is
published as a poll. The regular post text (linkedin_title / title) stays as
the commentary above the poll.
curl --location 'https://api.upload-post.com/api/upload_text' \
--header 'Authorization: ApiKey YOUR_API_KEY' \
--form 'user=your_profile' \
--form 'platform[]=linkedin' \
--form 'title=We are settling this once and for all.' \
--form 'linkedin_poll_question=Best day to ship a release?' \
--form 'linkedin_poll_options[]=Monday' \
--form 'linkedin_poll_options[]=Wednesday' \
--form 'linkedin_poll_options[]=Friday' \
--form 'linkedin_poll_duration=THREE_DAYS'
LinkedIn does not allow updating poll content, so the request is validated
before anything is created: a bad question, a wrong option count, an option
over 30 characters or an unknown duration comes back as a 400 and nothing is
posted.
Two more LinkedIn rules apply:
- Polls and link previews are mutually exclusive. Sending
linkedin_poll_*together withlinkedin_link_url/link_urlis rejected — LinkedIn has a single content slot per post. - API-created polls are non-sponsored only, and poll authors may not ask for political opinions, health status or other sensitive data (LinkedIn's Professional Community Policies).
Polls work on personal profiles and on company pages (target_linkedin_page_id),
and they can be scheduled or queued like any other text post.
X (Twitter)
Every URL that X would turn into a clickable link is removed from the caption,
title, and first_comment before the tweet is created — schemed URLs
(https://, http://, ftp://), www. hosts, shorteners (t.co, bit.ly,
…), bare hostnames with a path (example.com/foo), and IPs with a path.
Obfuscated forms (example[.]com, hxxp://, unicode dots) are not
stripped because X does not parse them as links — they display as plain text
and are billed at the normal $0.015 rate.
Why: X charges $0.200 per "Content: Create (with URL)" vs $0.015 for
posts without a URL. Stripping happens on every X path (video, photo, text,
scheduled, retried) so usage stays on the cheap tier. See the
Character Limits page
for the full policy. To keep URLs in your X posts, see the
X Links add-on.
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
x_title | String | No | Specific text for the tweet. Fallbacks to title. If the text is long, it will be split into a thread. | title |
x_long_text_as_post | Boolean | No | For X Premium users. When true, long text is published as a single post. When false (default), it creates a thread if text is long. | false |
reply_settings | String | No | Controls who can reply to the tweet ("following", "mentionedUsers", "subscribers", "verified") | - |
quote_tweet_id | String | No | ID of the tweet to quote in a quote tweet. Mutually exclusive with card_uri, poll_*, and direct_message_deep_link. Cannot be combined with media (see note below). | - |
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. Mutually exclusive with card_uri, quote_tweet_id, and poll_*. | - |
card_uri | String | No | Card URI for Twitter Cards/ads/promoted content. Mutually exclusive with quote_tweet_id, direct_message_deep_link, and poll_*. | - |
poll_options | Array | No | Array of poll options (2-4 options, max 25 characters each). Mutually exclusive with card_uri, quote_tweet_id, and direct_message_deep_link. | [] |
poll_duration | Integer | No | Poll duration in minutes (5-10080, i.e., 5 minutes to 7 days) | 1440 |
poll_reply_settings | String | No | Who can reply to poll ("following", "mentionedUsers", "subscribers", "verified"). Requires poll_options. | - |
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. | [] |
x_paid_partnership | Boolean | No | Paid partnership flag (applied to the first tweet). | false |
x_article_title | String | No | Publishes the post as a long-form X Article with this headline. Max 100 characters. Requires X Premium on the connected account. When you send it, the global title becomes optional — the headline stands in as the post text. | - |
x_article_body | String | No | Article body as plain text or light Markdown. Falls back to the post text (x_title / title) when omitted. Requires x_article_title. | post text |
x_article_content_state | String | No | Raw X content_state JSON ({"blocks": [...], "entities": [...]}) for callers that build DraftJS themselves. Overrides x_article_body. | - |
x_article_draft | Boolean | No | When true, the Article is saved as a draft on X instead of being published. | false |
x_article_cover_media | File, URL, or media_id | No | Cover image for the X Article (multipart ≤ 5 MB, public URL, or numeric media_id). | - |
Note: For Twitter uploads, specify the platform as "x" in the platform[] array.
X Articles (long-form)
Send x_article_title and the post is published through X's Articles API
instead of as a tweet or a thread — a full long-form article with headings,
lists and quotes. The body comes from x_article_body, or from the normal post
text if you don't send one.
curl --location 'https://api.upload-post.com/api/upload_text' \
--header 'Authorization: ApiKey YOUR_API_KEY' \
--form 'user=your_profile' \
--form 'platform[]=x' \
--form 'x_article_title=How we cut upload failures in half' \
--form 'x_article_body=## The problem
Roughly 8% of uploads failed on the first try.
- Expired tokens
- Oversized videos
> Fixing retries moved the needle more than anything else.
We now retry on the platform errors that are actually transient.'
Response (published):
{
"success": true,
"results": {
"x": {
"success": true,
"post_id": "1346889436626259968",
"article_id": "1146654567674912769",
"draft": false,
"url": "https://x.com/yourhandle/status/1346889436626259968"
}
}
}
Markdown supported in x_article_body — one line per block:
| Markdown | X block type |
|---|---|
# Heading | header-one |
## Heading | header-two |
### Heading | header-three |
- * + | unordered-list-item |
1. 2) | ordered-list-item |
> quote | blockquote |
| anything else | unstyled (paragraph) |
Blank lines are separators, not empty paragraphs. There is no code block on X.
Save as a draft instead of publishing: add x_article_draft=true. The
response carries the article_id and draft: true with no post_id or url;
finish and publish it from X when you are ready.
- Publishing an Article requires an X Premium subscription on the connected account. Without it X rejects the request and the error is surfaced as-is.
x_article_titlecannot be combined withpoll_options[],quote_tweet_id,reply_to_id,card_uriordirect_message_deep_link— an Article is published as a standalone post. Sending them together returns a400.- Unlike tweets, URLs are not stripped from
x_article_body: the article body is not the post text, so it is not affected by the X Links policy.
first_comment (and x_first_comment) works with Articles: a published
Article is a post underneath, so the comment is posted as a reply to it. A
draft has nothing to reply to yet, so the comment is skipped.
quote_tweet_id and media are mutually exclusive on X. To quote a tweet and share an image, post the quote without media, or upload the media as a separate tweet.
reply_to_id can return HTTP 403 on X's Pay-Per-Use tier if the account has not engaged with that author. Reconnecting does not fix it — reply to someone the account already follows or has interacted with.
How Twitter Threads Are Created
If your text in the title field is longer than 280 characters, our API automatically creates a Twitter thread. You don't need to do anything special. By default, x_long_text_as_post is false.
How it works:
Our system creates natural-looking threads by intelligently splitting your text:
- It groups paragraphs: The system combines as many paragraphs (text separated by a blank line) as possible into a single tweet without exceeding the character limit.
- It splits long paragraphs: If a single paragraph is too long for one tweet, it's split into smaller parts. The system first tries to split by line breaks and then by words.
This process ensures your threads are easy to read.
Example of a thread creation
If you send this text in the title:
This is the first paragraph. It is short.
This second paragraph is a bit longer. Our API tries to keep paragraphs together in one tweet.
This is a much longer third paragraph. It probably won't fit with the others. It might even be too long for a single tweet. If so, the API will split it. It will first look for line breaks. If a single line is still too long, it will split it by words. This creates a readable and well-structured Twitter thread automatically.
The API will create a thread like this:
Tweet 1:
This is the first paragraph. It is short.
This second paragraph is a bit longer. Our API tries to keep paragraphs together in one tweet.
Tweet 2:
This is a much longer third paragraph. It probably won't fit with the others. It might even be too long for a single tweet. If so, the API will split it. It will first look for line breaks.
Tweet 3:
If a single line is still too long, it will split it by words. This creates a readable and well-structured Twitter thread automatically.
Facebook
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| facebook_title | String | No | Specific text for the Facebook post. Fallbacks to title. | title |
| facebook_page_id | String | Yes | Facebook Page ID where the text will be posted. | - |
| facebook_link_url | String | No | Optional URL to include for link preview in text posts. If provided, it's sent as link to the Graph API and Facebook may render a preview card. Some URLs refused as preview cards by Facebook are published with the URL appended to the text and a warning. | - |
| facebook_call_to_action | JSON | No | Call-to-action object. Requires facebook_link_url or link_url. | - |
| facebook_child_attachments | JSON array | No | Link carousel: 2–5 objects with link. | - |
| facebook_multi_share_end_card | Boolean | No | End card on a link carousel. | - |
| facebook_place_id | String | No | Facebook place ID (place). | - |
Connecting Facebook only links the account; it does not pick a destination Page. Meta only allows posting to Pages, not personal profiles.
Pass facebook_page_id on every upload. If you omit it and exactly one Page is connected, that Page is used. If several are connected, the API returns available_pages. Look up ids with Get Facebook Pages, or pin a Page once — a pinned Page takes precedence over facebook_page_id.
Threads
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
threads_title | String | No | Specific text for the Threads post. Fallbacks to title. | title |
threads_long_text_as_post | Boolean | No | If true, long text is published as a single post. If false (default), a thread is created if the text exceeds 500 characters. | false |
threads_topic_tag | String | No | A topic tag for the post (1-50 characters). Cannot contain periods (.) or ampersands (&). One tag per post. Helps increase reach. | - |
| threads_reply_control | String | No | Who can reply: everyone, accounts_you_follow, mentioned_only, parent_post_author_only, followers_only (alias followers). | - |
| threads_reply_to_id | String | No | Numeric post ID to reply to. | - |
| threads_quote_post_id | String | No | Numeric post ID to quote. | - |
| threads_link_attachment | String | No | http(s) URL attached to a text post. Max 5 unique links per post (THREADS_API__LINK_LIMIT_EXCEEDED). | - |
| threads_poll_options | String or threads_poll_options[] | No | 2–4 options, 1–25 characters each. Text posts only. | - |
| threads_auto_publish_text | Boolean | No | Skip the second publish call on text posts. | false |
Note: To upload content to Threads, specify the platform as "threads" in the platform[] array.
How Threads Are Created
If the text you provide exceeds 500 characters and threads_long_text_as_post is false, our API will automatically create a thread on Threads, similar to how it works with X (Twitter).
How it works:
Our system creates natural-looking threads by intelligently splitting your text:
- It groups paragraphs: The system combines as many paragraphs as possible into a single post without exceeding the character limit.
- It splits long paragraphs: If a single paragraph is too long for a post, it is split into smaller parts, first trying to break by line breaks, and if that's not enough, by words.
This process ensures that your Threads are coherent and easy to read, replicating the functionality you already enjoy for X.
Reddit
Uploads with platform[]=reddit return HTTP 503 with error_code: "reddit_unavailable". Do not include reddit in platform[] until posting is restored. OAuth connect and comments with platform=reddit return the same error.
{
"success": false,
"error_code": "reddit_unavailable",
"error": "Reddit posting is currently unavailable."
}
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| subreddit | String | Yes | Destination subreddit, without r/ (e.g., python). Unused while Reddit is unavailable. | - |
| flair_id | String | No | ID of the flair template to apply to the post. Unused while Reddit is unavailable. | - |
| reddit_link_url | String | No | URL for a Reddit link post. Unused while Reddit is unavailable. | link_url |
Bluesky
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| bluesky_title | String | No | Specific text for the Bluesky post. Fallbacks to title. | title |
| bluesky_link_url | String | No | URL to include as a link preview card on the Bluesky post. Bluesky will display a rich external embed with the page's title, description, and thumbnail. Overrides the generic link_url parameter for Bluesky. | link_url |
| reply_to_id | String | No | URL or AT-URI of the post to reply to. Creates a reply to the specified post. | - |
| bluesky_langs | String | No | Up to 3 BCP-47 codes, comma-separated. | - |
| bluesky_labels | String | No | Comma list of porn, sexual, nudity, graphic-media. | - |
| bluesky_threadgate | String | No | Who can reply. Alias: bluesky_reply_settings. | everyone |
| bluesky_postgate | String | No | disable_quotes (or true) to block quotes. Alias: bluesky_quote_settings. | quotes allowed |
| bluesky_quote_uri | String | No | Post URL or at:// URI to quote. Aliases: bluesky_quote_id, bluesky_quote_url. | - |
Note: To upload content to Bluesky, specify the platform as "bluesky" in the platform[] array. The maximum character limit is 300 characters per post.
How Bluesky Threads Are Created
If your text exceeds 300 characters, our API automatically creates a Bluesky thread. This works similarly to X (Twitter) and Threads.
How it works:
Our system creates natural-looking threads by intelligently splitting your text:
- It groups paragraphs: The system combines as many paragraphs (text separated by a blank line) as possible into a single post without exceeding 300 characters.
- It splits long paragraphs: If a single paragraph is too long for one post, it's split into smaller parts. The system first tries to split by line breaks and then by words.
Example of a thread creation
If you send this text in the title:
This is the first paragraph. It is short.
This second paragraph is a bit longer. Our API tries to keep paragraphs together.
This is a much longer third paragraph. It probably won't fit with the others. If so, the API will split it into multiple posts automatically.
The API will create a thread like this:
Post 1:
This is the first paragraph. It is short.
This second paragraph is a bit longer. Our API tries to keep paragraphs together.
Post 2:
This is a much longer third paragraph. It probably won't fit with the others. If so, the API will split it into multiple posts automatically.
Discord
Discord posts go to the channel behind the incoming webhook you connected (see Connecting Discord). A text post is sent as the webhook message content.
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| discord_title | String | No | Specific text for the Discord message. Fallbacks to title. | title |
Note: To upload content to Discord, specify the platform as "discord" in the platform[] array. The maximum message length is 2000 characters; longer text is truncated. Discord does not support analytics.
Telegram
Telegram posts are delivered by your connected bot to the chat/channel you set up (see Connecting Telegram). A text post is sent via sendMessage.
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| telegram_title | String | No | Specific text for the Telegram message. Fallbacks to title. | title |
Note: To upload content to Telegram, specify the platform as "telegram" in the platform[] array. The maximum message length is 4096 characters; longer text is truncated. Telegram does not support analytics.
Slack
Slack posts are delivered to the channel behind the Incoming Webhook you connected (see Connecting Slack). The text is sent as the webhook message.
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| slack_title | String | No | Specific text for the Slack message. Fallbacks to title. | title |
Note: To post to Slack, specify "slack" in the platform[] array. Slack is text-only (Incoming Webhooks cannot upload media). Max 40000 characters. Slack does not support analytics.
Mastodon
Mastodon publishes a status ("toot") to your connected instance (see Connecting Mastodon).
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| mastodon_title | String | No | Specific text for the Mastodon status. Fallbacks to title. | title |
Note: To post to Mastodon, specify "mastodon" in the platform[] array. The default limit is 500 characters (configurable per instance). Mastodon also supports photos and video. Mastodon does not support analytics.
Nostr
Nostr signs a note with your key and broadcasts it to your relays (see Connecting Nostr).
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| nostr_title | String | No | Specific text for the Nostr note. Fallbacks to title. | title |
Note: To post to Nostr, specify "nostr" in the platform[] array. Nostr is text-only (media is referenced by URL, not uploaded). Max 32768 characters. Nostr does not support analytics.
Lemmy
Lemmy creates a post in the community you connected (see Connecting Lemmy). Lemmy posts always require a title.
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| lemmy_title | String | No | Title of the Lemmy post. If omitted, it is derived from the first line of the content. Fallbacks to title. | title |
Note: To post to Lemmy, specify "lemmy" in the platform[] array. Title max 200 characters, body max 10000. Lemmy also supports photo posts. Lemmy does not support analytics.
Dev.to
Dev.to publishes a Markdown article (see Connecting Dev.to). The text content becomes the article body.
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| devto_title | String | No | Article title. If omitted, it is derived from the first line of the content. Fallbacks to title. | title |
Note: To post to Dev.to, specify "devto" in the platform[] array. Title max 250 characters, body (Markdown) max 250000. Dev.to does not support analytics.
Hashnode
Hashnode publishes a Markdown article to your publication (see Connecting Hashnode).
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| hashnode_title | String | No | Article title. If omitted, it is derived from the first line of the content. Fallbacks to title. | title |
Note: To post to Hashnode, specify "hashnode" in the platform[] array. Title max 250 characters, body (Markdown) max 100000. Hashnode does not support analytics.
WordPress
WordPress publishes a post to your site (see Connecting WordPress). The text content becomes the post body.
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| wordpress_title | String | No | Post title. If omitted, it is derived from the first line of the content. Fallbacks to title. | title |
Note: To post to WordPress, specify "wordpress" in the platform[] array. Title max 200 characters, body max 500000. WordPress also supports photos and video. WordPress does not support analytics.
Whop
Whop creates a post in the community forum ("experience") you connected (see Connecting Whop).
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| whop_title | String | No | Title of the Whop forum post. Fallbacks to title. | title |
Note: To post to Whop, specify "whop" in the platform[] array. Max 20000 characters. Posting requires an API key with the forum:post:create permission. Whop does not support analytics.
Listmonk
Listmonk creates and sends an email campaign to the list you connected (see Connecting Listmonk).
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| listmonk_title | String | No | Email subject line. If omitted, it is derived from the first line of the content. Fallbacks to title. | title |
Note: To post to Listmonk, specify "listmonk" in the platform[] array. The text content becomes the email body. Subject max 500 characters, body max 200000. Listmonk does not support analytics.
Google Business Profile
| Name | Type | Required | Description | Default |
|---|---|---|---|---|
| gbp_location_id | String | No* | The location to post to. Use Get Google Business Locations to list available locations. | Auto |
| 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. Alias: cta_type. Other spellings (cta_action_type, gbp_cta_action_type) are accepted too, but the canonical name is gbp_cta_type. An unknown value is rejected with 400 instead of publishing the post without its button. | - |
| gbp_cta_url | String | Conditional | URL the button opens. Required for every type except CALL, which dials the location's phone number and must not carry a URL. Missing URL → 400. Alias: cta_url. | - |
| gbp_post_type | String | No | Set to MEDIA (also accepts PHOTO or GALLERY) to publish an attached photo to the location's Media/Gallery tab instead of creating a Local Post. See Publishing to the Media/Gallery tab. | - |
| gbp_upload_to_gallery | Boolean | No | Alternative to gbp_post_type. Set to true for the same Media/Gallery behaviour. | false |
| gbp_media_category | String | No | Category assigned to the uploaded photo in the Media tab. Alias: media_category. Allowed: COVER, PROFILE, LOGO, EXTERIOR, INTERIOR, PRODUCT, AT_WORK, FOOD_AND_DRINK, MENU, COMMON_AREA, ROOMS, TEAMS, ADDITIONAL. | ADDITIONAL |
If gbp_location_id is omitted and the account has exactly one location, that location is used. If several are connected, the API asks you to pick one.
The Media/Gallery flow publishes a photo, so it is normally used from /api/upload_photos. The fields are accepted here too, but a text-only request that selects Media/Gallery with no media returns 400 (error_code: MEDIA_REQUIRED). Omitting gbp_post_type keeps Local Post behaviour.
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. Alias of gbp_offer_coupon (old name wins if both are sent). |
| gbp_redeem_url | String | No | URL where the offer can be redeemed. Alias of gbp_offer_redeem_url. |
| gbp_terms | String | No | Terms and conditions of the offer. Alias of gbp_offer_terms. |
| gbp_language_code | String | No | BCP-47 language. Default "en". |
Example Requests
Upload Text to X (Twitter)
- cURL
- Python
- JavaScript
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'user="test"' \
-F 'platform[]=x' \
-F 'title="This is my tweet content!"' \
-X POST https://api.upload-post.com/api/upload_text
import requests
response = requests.post(
"https://api.upload-post.com/api/upload_text",
headers={"Authorization": "Apikey your-api-key-here"},
data={
"user": "test",
"platform[]": "x",
"title": "This is my tweet content!",
},
)
print(response.json())
const form = new FormData();
form.append("user", "test");
form.append("platform[]", "x");
form.append("title", "This is my tweet content!");
const response = await fetch("https://api.upload-post.com/api/upload_text", {
method: "POST",
headers: { Authorization: "Apikey your-api-key-here" },
body: form,
});
console.log(await response.json());
Create a Twitter Thread
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'user="test"' \
-F 'platform[]=x' \
-F 'title="This is the first paragraph of a thread.\n\nThis is the second paragraph. Because this whole text is longer than 280 characters, the API will automatically create a thread. You can also add more paragraphs to create longer and more detailed threads easily."' \
-X POST https://api.upload-post.com/api/upload_text
Upload Text to LinkedIn with Link Preview
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'user="test"' \
-F 'platform[]=linkedin' \
-F 'title="Check out this great article about renewable energy!"' \
-F 'linkedin_link_url="https://example.com/article"' \
-X POST https://api.upload-post.com/api/upload_text
Upload Text to LinkedIn (Personal Profile)
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'user="test"' \
-F 'platform[]=linkedin' \
-F 'title="Exciting news to share on LinkedIn!"' \
-X POST https://api.upload-post.com/api/upload_text
Upload Text to LinkedIn (Organization Page)
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'user="test"' \
-F 'platform[]=linkedin' \
-F 'title="Our company is launching a new product!"' \
-F 'target_linkedin_page_id="your_linkedin_page_id_here"' \
-X POST https://api.upload-post.com/api/upload_text
Upload Text to Facebook Page
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'title="This is a test post to Facebook using the title field for content!"' \
-F 'user="test2"' \
-F 'platform[]=facebook' \
-F 'facebook_page_id="your_facebook_page_id_here"' \
-F 'facebook_link_url="https://example.com/article"' \
-X POST https://api.upload-post.com/api/upload_text
Upload Text to Threads and Twitter (X)
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'title="This is a cross-post to Threads and X!"' \
-F 'user="test"' \
-F 'platform[]=threads' \
-F 'platform[]=x' \
-X POST https://api.upload-post.com/api/upload_text
Upload Text to Reddit
Reddit posting is currently unavailable. A request with platform[]=reddit returns HTTP 503:
{
"success": false,
"error_code": "reddit_unavailable",
"error": "Reddit posting is currently unavailable."
}
Upload Text to Bluesky
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'user="test"' \
-F 'platform[]=bluesky' \
-F 'title="This is my Bluesky post!"' \
-X POST https://api.upload-post.com/api/upload_text
Upload Text to Bluesky with Link Preview
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'user="test"' \
-F 'platform[]=bluesky' \
-F 'title="Great read on climate policy!"' \
-F 'bluesky_link_url="https://example.com/article"' \
-X POST https://api.upload-post.com/api/upload_text
Create a Bluesky Thread
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'user="test"' \
-F 'platform[]=bluesky' \
-F 'title="This is the first paragraph of a thread.\n\nThis is the second paragraph. Because this whole text is longer than 300 characters, the API will automatically create a thread. You can add more paragraphs to create longer threads easily."' \
-X POST https://api.upload-post.com/api/upload_text
Upload Text to Discord
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'user="test"' \
-F 'platform[]=discord' \
-F 'title="Hello from Upload-Post!"' \
-X POST https://api.upload-post.com/api/upload_text
Upload Text to Telegram
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'user="test"' \
-F 'platform[]=telegram' \
-F 'title="Hello from Upload-Post!"' \
-X POST https://api.upload-post.com/api/upload_text
Upload Text to Multiple Platforms with Link Preview
Use the generic link_url parameter to add a link preview card across all supported platforms at once:
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'user="test"' \
-F 'platform[]=linkedin' \
-F 'platform[]=bluesky' \
-F 'platform[]=facebook' \
-F 'facebook_page_id="your_facebook_page_id_here"' \
-F 'title="Check out our latest article on renewable energy!"' \
-F 'link_url="https://example.com/article"' \
-X POST https://api.upload-post.com/api/upload_text
Reply to a Tweet on X (Twitter)
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'user="test"' \
-F 'platform[]=x' \
-F 'title="This is my reply to a tweet!"' \
-F 'reply_to_id="1346889436626259968"' \
-X POST https://api.upload-post.com/api/upload_text
Reply to a Tweet on X (Twitter) with Excluded Users
curl \
-H 'Authorization: Apikey your-api-key-here' \
-F 'user="test"' \
-F 'platform[]=x' \
-F 'title="This is my reply to a tweet with some users excluded!"' \
-F 'reply_to_id="1346889436626259968"' \
-F 'exclude_reply_user_ids[]=1234567890' \
-F 'exclude_reply_user_ids[]=0987654321' \
-X POST https://api.upload-post.com/api/upload_text
Responses
- 200 OK (synchronous, finished fast)
{
"success": true,
"results": {
"x": { "success": true, "url": "https://x.com/..." },
"facebook": { "success": false, "error": "Facebook Page ID is required for text posts to Facebook." }
},
"usage": { "count": 14, "limit": 100, "last_reset": "..." }
}
- 200 OK (asynchronous/background started or sync→background fallback)
{
"success": true,
"message": "Text post initiated successfully in background.",
"request_id": "1a2b3c4d5e...",
"total_platforms": 1
}
- 202 Accepted (scheduled)
{
"success": true,
"job_id": "scheduler_job_789",
"scheduled_date": "2025-09-22T10:00:00Z"
}
-
400 Bad Request
- Missing
title(content),user,platform[], invalid platforms, invalidscheduled_date. For Facebook withoutfacebook_page_id, the per-platform result will include an error entry forfacebook. Reddit-only requests return 503reddit_unavailable.
- 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 are returned under
results.{platform}and may include fields likeurl, platform-specific IDs, orerror. A result withskipped: truemeans the profile has no account for that platform (see Unconnected platforms).
Unconnected platforms
Each profile only has accounts for the platforms you connected to it. When a request lists several platforms and the profile has no account for some of them, the upload is not rejected: the connected platforms are published normally and each unconnected one comes back in results as skipped (nothing is posted there and it is not counted as a failure in your dashboard):
{
"success": true,
"results": {
"instagram": { "success": true, "url": "https://instagram.com/p/..." },
"linkedin": {
"success": false,
"skipped": true,
"skip_reason": "profile_platform_not_configured",
"error": "Profile creator has no Linkedin account configured",
"error_code": "profile_platform_mapping_invalid",
"failure_stage": "profile_platform_validation"
}
}
}
For asynchronous uploads the same per-platform entry is returned by GET /api/uploadposts/status, and skipped platforms count as finished, so completed reaches total as soon as the connected platforms are done.
If none of the requested platforms is connected to the profile, the request is rejected with 400:
{
"success": false,
"message": "None of the requested platforms are valid for profile \"creator\". Profile creator has no Linkedin account configured",
"invalid_platforms": { "linkedin": "Profile creator has no Linkedin account configured" }
}
Scheduled posts (scheduled_date) keep every requested platform, connected or not, and are validated again when the job runs. Connect the missing account before the scheduled time and the post will be published there too; otherwise that platform is reported as skipped at execution time.