Skip to content

postStory

Posts a story on behalf of a managed business account. Requires the can_manage_stories business bot right. Returns Story on success.

Parameters

business_connection_idStringRequired
Unique identifier of the business connection
contentInputStoryContentRequired
Content of the story
active_periodIntegerRequired
Values:216004320086400172800
Period after which the story is moved to the archive, in seconds; must be one of 6 3600, 12 3600, 86400, or 2 * 86400
captionStringOptional✏️ FormattableminLen 0maxLen 2048
Caption of the story, 0-2048 characters after entities parsing
parse_modeStringOptional
Mode for parsing entities in the story caption. See formatting options for more details.
caption_entitiesMessageEntity[]Optional
A JSON-serialized list of special entities that appear in the caption, which can be specified instead of parse\_mode
areasStoryArea[]Optional
A JSON-serialized list of clickable areas to be shown on the story
post_to_chat_pageBooleanOptional
Pass True to keep the story accessible after it expires
protect_contentBooleanOptional
Pass True if the content of the story must be protected from forwarding and screenshotting

Returns

On success, the Story object is returned.

GramIO Usage

Post a photo story from a URL on behalf of a business account:

ts
bot.on("business_message", async (ctx) => {
  const story = await bot.api.postStory({
    business_connection_id: ctx.businessConnectionId,
    content: {
      type: "photo",
      photo: await MediaUpload.url("https://example.com/photo.jpg"),
    },
    active_period: 86400, // 24 hours
    caption: "Check out our latest offer!",
  });

  console.log("Story posted:", story.id);
});

Post a video story with a caption using rich text formatting:

ts
async function 
postVideoStory
(
businessConnectionId
: string,
videoPath
: string) {
const
story
= await
bot
.
api
.
postStory
({
business_connection_id
:
businessConnectionId
,
content
: {
type
: "video",
video
: await
MediaUpload
.
path
(
videoPath
),
},
active_period
: 21600, // 6 hours
caption
:
format
`${
bold
("Flash Sale")} — 20% off everything today!`,
post_to_chat_page
: true, // Keep accessible after expiry
}); return
story
;
}

Post a photo story that is permanent and protected from forwarding:

ts
async function 
postProtectedStory
(
businessConnectionId
: string) {
return
bot
.
api
.
postStory
({
business_connection_id
:
businessConnectionId
,
content
: {
type
: "photo",
photo
: await
MediaUpload
.
url
("https://example.com/banner.jpg"),
},
active_period
: 172800, // 48 hours
protect_content
: true,
post_to_chat_page
: true,
}); }

Errors

CodeErrorCause
400Bad Request: business connection not foundThe business_connection_id is invalid or the connection no longer exists.
400Bad Request: not enough rightsThe business bot connection does not have the can_manage_stories right. Grant this right in BotFather's business bot settings.
400Bad Request: STORY_PERIOD_INVALIDThe active_period value is not one of the four allowed values: 21600, 43200, 86400, or 172800.
400Bad Request: story content invalidThe content object is malformed or the file could not be uploaded/processed.
400Bad Request: caption too longThe caption exceeds 2048 characters.
403Forbidden: not enough rightsThe bot is not authorized to manage stories for this business account.
429Too Many Requests: retry after NFlood control triggered. Wait N seconds before retrying.

Tips & Gotchas

  • active_period has only four allowed values. You must pass exactly 21600 (6 h), 43200 (12 h), 86400 (24 h), or 172800 (48 h). Any other integer will result in a 400 error. Use the literal numbers or named constants to avoid mistakes.
  • can_manage_stories right is mandatory. This business bot right must be enabled in the business connection settings; it is separate from general message rights. Check with getBusinessConnection first if you are unsure.
  • File uploads require await. Both MediaUpload.url() and MediaUpload.path() are async — always await them before embedding in the content object. MediaUpload.buffer() is synchronous if you already have the binary data.
  • post_to_chat_page vs. protect_content. post_to_chat_page: true makes the story persist on the account's chat page even after the active_period expires. protect_content: true prevents viewers from forwarding or saving the media — they are independent flags and can be combined.
  • Do not use parse_mode together with caption_entities. These are mutually exclusive. Pass one or the other: format tagged template (which returns entities) or a parse_mode string — never both.
  • Stories are tied to a business connection. Unlike regular bot messages, stories posted via postStory appear on the business account's profile, not the bot's own profile.

See Also

  • editStory — Edit the caption or content of a posted story
  • deleteStory — Remove a story from the business account
  • repostStory — Repost a story to another managed business account
  • getBusinessConnection — Inspect business connection rights before posting
  • Story — The Story object returned on success
  • InputStoryContent — Describes the photo or video content of a story
  • StoryArea — Clickable interactive areas overlaid on the story
  • formatting — Rich text formatting with the format tag