REST API

Platforms

Every channel and its rules, read from the same source we validate against.

This endpoint is the honest answer to 'what are the limits'. It is the same table the validator uses, so it cannot drift from behaviour.

GET/v1/platforms

Every channel, its limits, and whether it is live.

Limits today

NameTypeDescription
Threads500images 20 · video 1 · text alone ok · live
Instagram2,200images 10 · video 1 · media required · live
YouTube5,000 + title 100video 1 · media required · live
Facebook63,206images 10 · video 1 · text alone ok · live
Bluesky300 + 3,000 bytesimages 4 · video 1 · text alone ok · live
Telegram4,096 · 1,024 with mediaimages 10 · video 10 · text alone ok · live
Naver Blog30,000 + title 100images 40 · video 10 · text alone ok · beta, through the browser extension
TikTok2,200 + title 90images 35 · video 1 · media required · in platform review, testers only

How a character is counted

Every character counts as 1, except code points above U+FFFF (mostly emoji) which count as 4. Do not use string length: "a😀".length is 3 but it counts as 5. GET /v1/platforms returns the same rule per channel as charCount, so you can check before publishing instead of after it fails.

Your own limits come from GET /v1/me

This table is what each channel allows. What your account allows is a different number, and GET /v1/me returns it as limits: how many workspaces you can create and how many requests a minute you get. There is no separate cap on connections. A workspace holds one channel per platform, and connecting a second account on the same platform is refused with platform_already_connected. **These are beta values and they will change.** Read them at runtime instead of writing them into your code.

What options.<channel> takes

Every channel entry carries options: a JSON Schema of the per-channel settings publish accepts, generated from the same adapter the server validates with. Read it instead of guessing field names. Fields marked agentOnly are for API callers; the dashboard does not draw them. Every channel also takes content, a different text for that channel alone. The tables below are that schema, rendered.

options.threads

NameTypeDescription
topicTagstringOne topic to tag the post with, like a category. Threads takes exactly one and rejects periods and ampersands in it. On a thread it goes on the first piece only. Leave it out unless the person asked for a topic.
contentstringText for this channel only. When set it replaces the shared content for this channel, and the length and media checks use it. Leave it out to use the shared content.

options.instagram

NameTypeDescription
contentTypefeed | story | reelWhat kind of post. Defaults to reel for a single video and feed otherwise. Stories show no caption and take one image or video.
shareToFeedbooleanReels only. false keeps the reel out of the main feed.
coverMediaIdstringMedia id of a JPEG up to 8MB to use as the reel cover. Reels only.
thumbOffsetMsnumberReel cover frame in milliseconds. Ignored when coverMediaId is set.
collaboratorsstring[]Up to 3 public professional accounts to invite as collaborators. They appear only after accepting. Not on stories.
userTagsobject[]Tag accounts on the media. Photos need x and y between 0 and 1; videos take the username alone. mediaIndex picks the carousel slide. (API only; the dashboard does not draw it.)
isAiGeneratedbooleanSet true to label the media as AI-generated on Instagram. An attached media marked aiGenerated turns this on automatically unless you set it to false.
firstCommentstringA comment we post right after publishing, often used for hashtags. Not on stories. If it fails the post still goes up, with a warning.
contentstringText for this channel only. When set it replaces the shared content for this channel, and the length and media checks use it. Leave it out to use the shared content.

options.youtube

NameTypeDescription
titleRequiredstringVideo title, up to 100 characters. Required. Cannot contain < or >.
defaultLanguagestringThe language the title and description are written in, as one BCP-47 code (for example en-US or ko). Leave it out and none is sent. The spoken language (YouTube Studio's Video language) cannot be set through the API.
tagsstring[]Search tags. 500 characters across all of them; a tag with a space costs two extra for the quotes YouTube adds.
categoryIdstringYouTube category id. Defaults to 22 (People & Blogs). Leave it out unless the person named a category. (API only; the dashboard does not draw it.)
privacyStatuspublic | unlisted | privateDefaults to private. We pass this through as you set it. If YouTube applies something else, the result carries a warning saying what it actually applied.
publishAtstringISO 8601 time to make the video public. Only works with privacyStatus private, and only on a video that was never public. This is YouTube's own scheduling; for scheduling across channels use scheduledAt on the post. (API only; the dashboard does not draw it.)
madeForKidsbooleanWhether this video is directed at children. This is a legal declaration about someone else's channel. Leave it out unless the person tells you, and the channel's own default applies. If the channel has not set its audience either, YouTube Studio will not save other edits to this video until someone picks one, so ask the person.
containsSyntheticMediabooleanSet true when the video contains realistic altered or synthetic content, including AI generated footage of real-looking people, places or events. An attached media marked aiGenerated turns this on automatically unless you set it to false.
thumbnailMediaIdstringMedia id of a JPEG or PNG up to 2MB to use as the thumbnail. Long-form only: YouTube does not take custom thumbnails on Shorts, and a vertical video of three minutes or less becomes a Short. The channel also has to be verified before YouTube accepts one.
contentstringText for this channel only. When set it replaces the shared content for this channel, and the length and media checks use it. Leave it out to use the shared content.

options.facebook

NameTypeDescription
contentTypefeed | story | reelWhat kind of post. Defaults to reel for a single video and feed otherwise. A Facebook story is rejected if you send any text: publish the story with no content, and put the words in a feed post instead. A story also takes exactly one image or video and no first comment.
linkstringMakes a link post with a preview card. Cannot be combined with media.
titlestringReel title, separate from the caption.
scheduledPublishTimestringISO 8601 time to publish a feed post later, 10 minutes to 28 days from now. Feed posts only, not reels or stories. Cannot be combined with firstComment (a scheduled post is not live yet, so nothing to comment on). This is Facebook's own scheduling; for scheduling across channels use scheduledAt on the post. (API only; the dashboard does not draw it.)
firstCommentstringA comment we post right after publishing. Not on stories, and not on scheduled posts. If it fails the post still goes up, with a warning.
feedTargetingobjectPreferred audience for a Facebook text or link post. A hint, not a limit: Facebook may show the post more to these people, others can still see it, and the effect is not guaranteed. Facebook does not keep it on photo posts or reels (we checked), so those are refused. It cannot be changed after the post is published. (API only; the dashboard does not draw it.)
contentstringText for this channel only. When set it replaces the shared content for this channel, and the length and media checks use it. Leave it out to use the shared content.

options.bluesky

NameTypeDescription
langsstring[]The language of the text, 1-3 BCP-47 codes like ["ko"] or ["en", "ko"]. Feed generators filter on this, so without it the post never appears in language-scoped feeds. Bluesky has no post editing, so this cannot be fixed after publishing. Set it to the language the text is actually written in.
contentstringText for this channel only. When set it replaces the shared content for this channel, and the length and media checks use it. Leave it out to use the shared content.

options.telegram

NameTypeDescription
contentstringText for this channel only. When set it replaces the shared content for this channel, and the length and media checks use it. Leave it out to use the shared content.

options.naver_blog

NameTypeDescription
titleRequiredstringRequired. The post title, up to 100 characters.
categoryIdstringThe id of a category on the connected blog. list_accounts returns them under naverBlog.categories. Give this or category (the name); there is no default, and without one Naver files the post under the wrong board. Not needed and ignored when draftOnly is true (a draft has no board until it is published).
categorystringThe category name instead of its id, exactly as it appears in naverBlog.categories from list_accounts. If the blog does not have it you get naver_category_unknown with the list to pick from; if the person just created it, call refresh_account first. (API only; the dashboard does not draw it.)
tagsstring[]Up to 30 tags without the # sign and without spaces. Ignored when draftOnly is true.
openTypepublic | neighbor | mutual | privateWho can see it. public (default), neighbor, mutual (mutual neighbors only), or private. Ignored when draftOnly is true.
draftOnlybooleanSave as a draft in Naver instead of publishing. The user finishes it by hand, or you publish it later with publish_naver_draft. categoryId, tags and openType are not stored on a draft; pass them when you publish it. Different from draft on the post, which keeps the whole post on uplika.
layouttemplate | as-istemplate (default) reshapes the body into the blog's house form before it goes out: #/## titles become underlined quote headings, a thin rule sits between sections, and photos you did not place with media: references go one per section (leftovers pair up at the end). Text is never changed. Call naver_layout first to see the result. Use as-is when the person dictated the structure or you placed everything yourself. (API only; the dashboard does not draw it.)
formbubble-info | divider-tutorial | photo-review | place-list | news-event | table-explain | mood-review | photo-storyForm preset for the template layout (default photo-story). GET /v1/naver/forms lists the presets; naver_layout with forms: "all" shows them side by side.
scheduledAtstringSame as scheduledAt on the post: ISO 8601 with a timezone offset, 10 minutes to a year out. Uplika holds the post and sends it to Naver at that time; Naver's own reservation is not used. Prefer scheduledAt on the post. Not allowed together with draftOnly or on a post that also targets other channels. (API only; the dashboard does not draw it.)
tempLogNostringSet by publish_naver_draft: the Naver temp-saved draft to publish as it is. The body is ignored; the extension loads that draft in the editor and presses publish. (API only; the dashboard does not draw it.)
fontFamilynanumgothic | nanummyeongjo | nanumbarungothic | nanumsquare | maruburiBody font, one of nanumgothic, nanummyeongjo, nanumbarungothic, nanumsquare, maruburi. Omit for the editor default.
fontSize11 | 13 | 15 | 16 | 19 | 24 | 28 | 30 | 34 | 38Body font size in px, one of the editor's sizes (11-38). Omit for the editor default.
alignCenterbooleanCenter every paragraph and image that has no alignment of its own. A photo line's own {.left} / {.center} / {.right} wins.
imageSizefit | smallSize of every single photo that has none of its own: fit is the document width, small is the editor's Small (about three quarters). A photo line's own {.fit} / {.small} wins. Leave it out for the default: document width for photos wider than 693px, original width for narrower ones. @group photos are always document width. Needs uplika extension 0.7.13 or later; with an older one the post still goes out and, if fit was asked for, a warning says so.
seoKeywordsobjectThe search keywords this post targets, from keyword research (POST /v1/naver/keywords/judge): main is the one keyword the title leads with, subs (2-5) become the section titles one each. Not used when publishing; stored with the post and read by the SEO check.
contentstringText for this channel only. When set it replaces the shared content for this channel, and the length and media checks use it. Leave it out to use the shared content. On Naver Blog this is where the Markdown body goes when the other channels get plain text.

options.tiktok

NameTypeDescription
privacyLevelstringRequired unless postMode is draft. One of the values that get_publish_options returns for this account, e.g. PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR or SELF_ONLY. The list differs per account, so do not hard-code it.
titlestringRequired for a photo post, up to 90 characters. A video post has no title field at all and passing one is rejected; a video's text comes from content.
postModedirect | draftdirect (default) posts to the profile. draft sends it to the creator's TikTok inbox; they open the notification and finish it in the app, choosing who can see it themselves, so we send no visibility on this path. Until Uplika completes TikTok's review, a direct post is visible only to the creator (SELF_ONLY). A draft is not on the profile until the person finishes it, so there is no link and no metrics in the meantime.
disableCommentbooleanTurn comments off for this post. Cannot be set to false if the creator disabled comments account-wide.
disableDuetbooleanTurn duets off for this post. Video posts only.
disableStitchbooleanTurn stitches off for this post. Video posts only.
brandContentTogglebooleanDeclare paid partnership content ("Branded Content"). TikTok does not allow branded content to be private, so this cannot be combined with privacyLevel SELF_ONLY.
brandOrganicTogglebooleanDeclare that the post promotes the creator's own brand ("Your Brand"). Leave it out rather than sending false: not declaring and declaring 'no' are different statements.
isAigcbooleanDeclare that the content was generated by AI. Worth setting when you made the video or images. An attached media marked aiGenerated turns this on automatically unless you set it to false.
videoCoverTimestampMsnumberWhich frame to use as the cover, in milliseconds into the video. TikTok does not accept a cover image file, only a timestamp.
contentstringText for this channel only. When set it replaces the shared content for this channel, and the length and media checks use it. Leave it out to use the shared content.

Common questions

What is the character limit on each channel?

The table above has it, and it is generated from the same table the validator reads. If a limit changes in the product it changes here in the same commit, so this page cannot drift from behaviour.

What happens if I go over a limit?

The call is refused with content_too_long before anything is published, and the response carries the channel, the limit and your actual length so the caller can shorten and retry.

Do I have to pick the strictest limit myself?

Only if you want to. Publishing to several channels at once checks each one against its own limit, so you find out which channel refused rather than guessing at a single number.