Skip to main content

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

NameValueDescription
AuthorizationApikey your-api-key-hereYour API key for authentication
Idempotency-Keyunique-stringOptional. 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

NameTypeRequiredDescription
userStringYesUser identifier
platform[]ArrayYesPlatform(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
titleStringYesDefault text content for the post.
descriptionStringNoOptional extended body used only on Reddit (becomes the post text). Ignored elsewhere.
request_idStringNoClient-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_dateString (ISO-8601)NoOptional 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.
timezoneString (IANA)NoOptional 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_idStringNoYour 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_uploadBooleanNoIf true, the request returns immediately with a request_id and processes in the background. See Upload Status.
add_to_queueBooleanNoIf true, automatically schedules the post to your next available queue slot. Cannot be used with scheduled_date. See Queue System.
max_posts_per_slotIntegerNoOverride the profile's max posts per slot setting for this request. Only used when add_to_queue=true. See Queue System.
first_commentStringNoAutomatically 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)NoImage 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_urlStringNoURL 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

NameTypeRequiredDescriptionDefault
linkedin_titleStringNoSpecific text for the LinkedIn post. Fallbacks to title.title
target_linkedin_page_idStringNoLinkedIn page ID to upload text to an organization's page. If not provided, posts to the user's personal profile.
linkedin_link_urlStringNoURL 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_visibilityStringNoPUBLIC, CONNECTIONS, LOGGED_IN, CONTAINER. Aliases: visibility, linkedinVisibility.PUBLIC
linkedin_disable_reshareBooleanNoWhen true, disable reshare.false
linkedin_link_titleStringNoLink-share title (max 400 characters).-
linkedin_link_descriptionStringNoLink-share description (max 4086 characters).-
linkedin_thumbnail_alt_textStringNoAlt text for the link thumbnail.-
linkedin_poll_questionStringNoQuestion of a LinkedIn poll. Max 140 characters. Requires linkedin_poll_options[].-
linkedin_poll_options[]ArrayNoPoll choices: 2 to 4 options, max 30 characters each. Requires linkedin_poll_question.-
linkedin_poll_durationStringNoHow 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'
A poll cannot be edited after it is published

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 with linkedin_link_url / link_url is 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)

URLs are stripped from every X post

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.

NameTypeRequiredDescriptionDefault
x_titleStringNoSpecific text for the tweet. Fallbacks to title. If the text is long, it will be split into a thread.title
x_long_text_as_postBooleanNoFor 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_settingsStringNoControls who can reply to the tweet ("following", "mentionedUsers", "subscribers", "verified")-
quote_tweet_idStringNoID 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_idStringNoPlace ID for adding geographic location to the tweet-
nullcastBooleanNoWhether to publish without broadcasting (promotional/promoted-only posts)false
for_super_followers_onlyBooleanNoTweet exclusive for super followersfalse
community_idStringNoCommunity ID for posting to specific communities-
share_with_followersBooleanNoShare community post with followersfalse
direct_message_deep_linkStringNoLink to take the conversation from public timeline to private Direct Message. Mutually exclusive with card_uri, quote_tweet_id, and poll_*.-
card_uriStringNoCard URI for Twitter Cards/ads/promoted content. Mutually exclusive with quote_tweet_id, direct_message_deep_link, and poll_*.-
poll_optionsArrayNoArray of poll options (2-4 options, max 25 characters each). Mutually exclusive with card_uri, quote_tweet_id, and direct_message_deep_link.[]
poll_durationIntegerNoPoll duration in minutes (5-10080, i.e., 5 minutes to 7 days)1440
poll_reply_settingsStringNoWho can reply to poll ("following", "mentionedUsers", "subscribers", "verified"). Requires poll_options.-
reply_to_idStringNoID of the tweet to reply to. Creates a reply to the specified tweet.-
exclude_reply_user_idsArrayNoArray of user IDs to exclude from replying to this tweet. Requires reply_to_id.[]
x_paid_partnershipBooleanNoPaid partnership flag (applied to the first tweet).false
x_article_titleStringNoPublishes 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_bodyStringNoArticle 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_stateStringNoRaw X content_state JSON ({"blocks": [...], "entities": [...]}) for callers that build DraftJS themselves. Overrides x_article_body.-
x_article_draftBooleanNoWhen true, the Article is saved as a draft on X instead of being published.false
x_article_cover_mediaFile, URL, or media_idNoCover 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:

MarkdownX block type
# Headingheader-one
## Headingheader-two
### Headingheader-three
- * + unordered-list-item
1. 2) ordered-list-item
> quoteblockquote
anything elseunstyled (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.

X Articles need X Premium and are published on their own
  • 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_title cannot be combined with poll_options[], quote_tweet_id, reply_to_id, card_uri or direct_message_deep_link — an Article is published as a standalone post. Sending them together returns a 400.
  • 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:

  1. 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.
  2. 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

NameTypeRequiredDescriptionDefault
facebook_titleStringNoSpecific text for the Facebook post. Fallbacks to title.title
facebook_page_idStringYesFacebook Page ID where the text will be posted.-
facebook_link_urlStringNoOptional 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_actionJSONNoCall-to-action object. Requires facebook_link_url or link_url.-
facebook_child_attachmentsJSON arrayNoLink carousel: 2–5 objects with link.-
facebook_multi_share_end_cardBooleanNoEnd card on a link carousel.-
facebook_place_idStringNoFacebook 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

NameTypeRequiredDescriptionDefault
threads_titleStringNoSpecific text for the Threads post. Fallbacks to title.title
threads_long_text_as_postBooleanNoIf 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_tagStringNoA topic tag for the post (1-50 characters). Cannot contain periods (.) or ampersands (&). One tag per post. Helps increase reach.-
threads_reply_controlStringNoWho can reply: everyone, accounts_you_follow, mentioned_only, parent_post_author_only, followers_only (alias followers).-
threads_reply_to_idStringNoNumeric post ID to reply to.-
threads_quote_post_idStringNoNumeric post ID to quote.-
threads_link_attachmentStringNohttp(s) URL attached to a text post. Max 5 unique links per post (THREADS_API__LINK_LIMIT_EXCEEDED).-
threads_poll_optionsString or threads_poll_options[]No2–4 options, 1–25 characters each. Text posts only.-
threads_auto_publish_textBooleanNoSkip 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:

  1. It groups paragraphs: The system combines as many paragraphs as possible into a single post without exceeding the character limit.
  2. 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

Reddit posting is currently unavailable

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."
}
NameTypeRequiredDescriptionDefault
subredditStringYesDestination subreddit, without r/ (e.g., python). Unused while Reddit is unavailable.-
flair_idStringNoID of the flair template to apply to the post. Unused while Reddit is unavailable.-
reddit_link_urlStringNoURL for a Reddit link post. Unused while Reddit is unavailable.link_url

Bluesky

NameTypeRequiredDescriptionDefault
bluesky_titleStringNoSpecific text for the Bluesky post. Fallbacks to title.title
bluesky_link_urlStringNoURL 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_idStringNoURL or AT-URI of the post to reply to. Creates a reply to the specified post.-
bluesky_langsStringNoUp to 3 BCP-47 codes, comma-separated.-
bluesky_labelsStringNoComma list of porn, sexual, nudity, graphic-media.-
bluesky_threadgateStringNoWho can reply. Alias: bluesky_reply_settings.everyone
bluesky_postgateStringNodisable_quotes (or true) to block quotes. Alias: bluesky_quote_settings.quotes allowed
bluesky_quote_uriStringNoPost 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:

  1. 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.
  2. 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.

NameTypeRequiredDescriptionDefault
discord_titleStringNoSpecific 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.

NameTypeRequiredDescriptionDefault
telegram_titleStringNoSpecific 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.

NameTypeRequiredDescriptionDefault
slack_titleStringNoSpecific 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).

NameTypeRequiredDescriptionDefault
mastodon_titleStringNoSpecific 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).

NameTypeRequiredDescriptionDefault
nostr_titleStringNoSpecific 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.

NameTypeRequiredDescriptionDefault
lemmy_titleStringNoTitle 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.

NameTypeRequiredDescriptionDefault
devto_titleStringNoArticle 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).

NameTypeRequiredDescriptionDefault
hashnode_titleStringNoArticle 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.

NameTypeRequiredDescriptionDefault
wordpress_titleStringNoPost 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).

NameTypeRequiredDescriptionDefault
whop_titleStringNoTitle 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).

NameTypeRequiredDescriptionDefault
listmonk_titleStringNoEmail 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

NameTypeRequiredDescriptionDefault
gbp_location_idStringNo*The location to post to. Use Get Google Business Locations to list available locations.Auto
gbp_topic_typeStringNoPost type: STANDARD (default), EVENT, or OFFER.STANDARD
gbp_cta_typeStringNoCall-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_urlStringConditionalURL 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_typeStringNoSet 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_galleryBooleanNoAlternative to gbp_post_type. Set to true for the same Media/Gallery behaviour.false
gbp_media_categoryStringNoCategory 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):

NameTypeRequiredDescription
gbp_event_titleStringYesTitle of the event.
gbp_event_start_dateStringYesStart date in YYYY-MM-DD format.
gbp_event_start_timeStringNoStart time in HH:MM format (24h).
gbp_event_end_dateStringYesEnd date in YYYY-MM-DD format.
gbp_event_end_timeStringNoEnd time in HH:MM format (24h).

Offer parameters (when gbp_topic_type is OFFER):

NameTypeRequiredDescription
gbp_coupon_codeStringNoCoupon or promo code. Alias of gbp_offer_coupon (old name wins if both are sent).
gbp_redeem_urlStringNoURL where the offer can be redeemed. Alias of gbp_offer_redeem_url.
gbp_termsStringNoTerms and conditions of the offer. Alias of gbp_offer_terms.
gbp_language_codeStringNoBCP-47 language. Default "en".

Example Requests

Upload Text to X (Twitter)

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

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
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
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

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, invalid scheduled_date. For Facebook without facebook_page_id, the per-platform result will include an error entry for facebook. Reddit-only requests return 503 reddit_unavailable.
  • 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 like url, platform-specific IDs, or error. A result with skipped: true means 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.