Как опубликовать историю Instagram через API
История публикуется через Instagram API так же, как обычный пост: POST /<IG_ID>/media создаёт контейнер с параметром media_type=STORIES, а POST /<IG_ID>/media_publish его публикует. Отдельного endpoint для историй нет, параметра caption тоже нет, а контейнер истекает через 24 часа. Изображение принимается только в JPEG и до 8 МБ, видео — от 3 до 60 секунд.
Обновлено:
Как опубликовать историю через Instagram API?
Два вызова. Первый — POST /<IG_ID>/media с image_url или video_url и media_type=STORIES. Второй — POST /<IG_ID>/media_publish с creation_id. Для видео перед вторым вызовом дождитесь статуса FINISHED. Meta скачивает файл сама, поэтому ссылка должна быть публичной HTTPS.
Другого значения media_type для историй не нужно: фото это или видео, определяет то, какой параметр вы передали — image_url или video_url. В одном контейнере они вместе не работают.
История живёт 24 часа. Контейнер — тоже: неопубликованный контейнер переходит в EXPIRED, и повторно его использовать нельзя.
Требования к медиа для истории
Эти требования не проверит ни SDK, ни кабинет — файл загружает Meta на своей стороне. При несоответствии контейнер уходит в ERROR.
| Что | Фото | Видео |
|---|---|---|
| Формат | JPEG (только) | MOV или MP4 |
| Размер | до 8 МБ | до 100 МБ |
| Длительность | нет | 3–60 секунд |
| Соотношение | рекомендуется 9:16 | от 0.1:1 до 10:1, рекомендуется 9:16 |
| Кодек | sRGB | H264 или HEVC, звук AAC 48 кГц |
| Кадры | нет | 23–60 FPS |
естьнет
PNG и WebP не принимаются — конвертируйте в JPEG заранее. Если ссылка требует авторизации или уже истекла, Meta не сможет забрать файл.
Сырые запросы: контейнер и публикация
Без SDK поток выглядит ровно так же. В первом ответе приходит id контейнера, он же становится creation_id во втором запросе.
# 1) Контейнер истории
curl -X POST "https://graph.facebook.com/v26.0/$IG_ID/media" \
-d "image_url=https://cdn.example.com/story.jpg" \
-d "media_type=STORIES" \
-d "access_token=$TOKEN"
# -> {"id":"17998...."}
# 2) Для видео — дождитесь готовности контейнера
curl -G "https://graph.facebook.com/v26.0/17998...." \
-d "fields=status_code" -d "access_token=$TOKEN"
# -> {"status_code":"FINISHED"}
# 3) Публикация
curl -X POST "https://graph.facebook.com/v26.0/$IG_ID/media_publish" \
-d "creation_id=17998...." -d "access_token=$TOKEN"В схеме Instagram Login базовый адрес — graph.instagram.com, в схеме Facebook Login — graph.facebook.com; имена endpoint одинаковые.
Для фото ждать не нужно: контейнер обычно готов сразу. Для видео не вызывайте media_publish, пока status_code не станет FINISHED.
Почему у истории нет параметра caption
В Content Publishing API caption — это текст поста в ленте. История в ленту не попадает, поля описания у неё нет, поэтому caption, отправленный вместе с media_type=STORIES, нигде не отображается.
Стикеров в API тоже нет: ссылку, опрос, вопрос, геометку, музыку, @упоминание и хештег Graph API не добавляет. Если в истории нужен текст или ссылка, их придётся вшить в само изображение (JPEG) или в кадры видео.
По этой причине в Socialift SDK метод publish_story() намеренно не принимает caption — места под несуществующий параметр не оставлено.
Разрешения для двух схем входа
У Instagram две схемы авторизации, и наборы scope у них разные. Страница Facebook нужна только в одной из них.
| Действие | Instagram Login | Facebook Login |
|---|---|---|
| Публикация истории | instagram_business_basic + instagram_business_content_publish | instagram_basic + instagram_content_publish + pages_read_engagement |
| Список активных историй | нет — edge отсутствует | instagram_basic + pages_read_engagement |
| Токен | токен пользователя (graph.instagram.com) | токен страницы (graph.facebook.com) |
| Страница Facebook | не нужна | нужна связанная страница |
В схеме Facebook Login, если роль на странице выдана через Business Manager, дополнительно потребуется ads_management или ads_read.
Кто может читать список активных историй
Истории не попадают в edge media — для них Meta отдаёт отдельный GET /<IG_ID>/stories. В ответе обычные объекты IG Media, а признак истории — media_product_type == "STORY": поле media_type и для истории возвращает IMAGE или VIDEO, то есть по нему отличить нельзя.
Главное: в документации Meta этот edge помечен как «Instagram API with Facebook Login only». С токеном Instagram Login его просто нет, и официального способа получить список активных историй в этой схеме тоже нет. Поэтому Socialift SDK в таком случае поднимает ConfigurationError, не отправляя запрос — это честнее, чем ждать от Meta путающую ошибку «nonexisting field».
В списке только последние 24 часа: ни архива, ни «актуального» (highlights) в API нет. Истории прямых эфиров и истории, созданные при репосте пользователем, тоже не возвращаются.
Дневной лимит: в самой документации две разные цифры
История выходит через тот же media_publish, то есть попадает в лимит публикаций — в документации нет ни одной фразы, которая выводила бы истории из-под него.
Но само число на разных страницах Meta отличается: на общей странице Content Publishing указано 100 постов за скользящие 24 часа, а на страницах media_publish и content_publishing_limit — 50. Поэтому не зашивайте цифру в код.
Правильный путь — спросить значение: GET /<IG_ID>/content_publishing_limit возвращает quota_usage (сколько постов вышло за окно) и объект config с quota_total и quota_duration. Необязательный параметр since принимает Unix-время не старше 24 часов. Читайте лимит из ответа и притормаживайте очередь по нему.
У контейнеров есть свой отдельный предел: до 400 контейнеров за скользящие 24 часа. Неопубликованный контейнер истекает через сутки, так что создавать их заранее пачками бессмысленно.
Публикация истории через Socialift SDK
SDK собирает создание контейнера, ожидание и публикацию в один вызов и возвращает container_id и media_id.
import asyncio, os
from socialift import ContainerNotReadyError, Instagram
CDN = "https://cdn.example.com"
async def main():
token = os.environ["IG_USER_TOKEN"]
async with Instagram.from_env() as ig:
photo = await ig.publish_story(token, image_url=f"{CDN}/story.jpg")
try:
clip = await ig.publish_story(token, video_url=f"{CDN}/story.mp4", wait_timeout=120.0)
except ContainerNotReadyError as exc:
print("контейнер ещё не готов:", exc.container_id)
return
active = await ig.stories(token, limit=25)
print(photo["media_id"], clip["media_id"], len(active))
asyncio.run(main())publish_story принимает ровно один из image_url и video_url — если передать оба, будет ConfigurationError.
Для видео контейнер ожидается до wait_timeout секунд (по умолчанию 300). По истечении времени приходит ContainerNotReadyError, и по его container_id можно продолжить без повторной загрузки видео: контейнер живёт 24 часа.
Для схемы Facebook Login создайте клиент через Instagram.via_facebook(app_secret=...), передавайте токен страницы и ig_user_id=.
stories() работает только в схеме Facebook Login; с токеном Instagram Login SDK вернёт ConfigurationError, не отправляя запрос.
История страницы Facebook: фото и видео
Истории страницы вообще не используют контейнерный поток Instagram — здесь два собственных протокола.
Фото: сначала неопубликованное фото
Edge
photo_storiesпринимает толькоphoto_idи не принимает ссылку. Поэтому сначала вызывается POST /<PAGE_ID>/photos?published=false&url=<ссылка> — он возвращает толькоid, и фото не появляется в ленте.Фото: публикация как истории
С полученным
idвызывается POST /<PAGE_ID>/photo_stories?photo_id=<id>. Неопубликованное фото хранится на серверах Facebook около 24 часов, после чегоphoto_idперестаёт работать.Видео: открыть сессию
POST /<PAGE_ID>/video_stories?upload_phase=start — в ответе приходят
video_idиupload_url. Адресupload_urlведёт на rupload.facebook.com и берётся из ответа, а не собирается вручную.Видео: загрузка файла
POST на тот самый
upload_url. Токен здесь передаётся не в query, а в заголовкеAuthorization: OAuth <token>. При ссылке — заголовокfile_urlи пустое тело; при файле — заголовкиoffsetиfile_size, а в теле байты.Видео: завершение
POST /<PAGE_ID>/video_stories?upload_phase=finish&video_id=<id>. Перед этим дождитесь
uploading_phase.status == "complete"через GET /<VIDEO_ID>?fields=status.
История страницы: пример SDK и обрыв загрузки
SDK выполняет оба потока внутри: два шага для фото, три для видео и докачку при обрыве.
import asyncio, os
from socialift import Facebook
async def main():
page_id = os.environ["FB_PAGE_ID"]
page_token = os.environ["FB_PAGE_TOKEN"]
async with Facebook.from_env() as fb:
photo = await fb.publish_photo_story(
page_id, page_token, photo_url="https://cdn.example.com/story.jpg"
)
clip = await fb.publish_video_story(
page_id, page_token, video="/srv/media/story.mp4", upload_attempts=3
)
state = await fb.video_story_status(clip["video_id"], page_token)
print(photo["post_id"], state.get("publishing_phase"))
for item in await fb.stories(page_id, page_token, limit=25):
print(item["post_id"], item["status"])
asyncio.run(main())publish_photo_story принимает ровно один из photo_url и photo_id. Второй нужен, если первая попытка оборвалась на шаге публикации: перезаливать фото не придётся.
publish_video_story при обрыве запрашивает uploading_phase.bytes_transfered и продолжает с этого байта (до upload_attempts попыток). Файл не отправляется заново с начала.
Разрешения: pages_manage_posts, pages_read_engagement, pages_show_list и задача CREATE_CONTENT на странице.
Требования к видео: .mp4, H.264 или H.265, звук AAC 48 кГц; соотношение 9:16; разрешение от 540x960 до 1080x1920; 24–60 FPS. По длительности документация Meta противоречит себе: в одном месте 3–90 секунд, в другом — не более 60; безопасный диапазон — 3–60 секунд.
Частые ошибки и как их избежать
- Фото в PNG или WebP — для истории Instagram принимается только JPEG.
- Подписанная ссылка истекла или требует входа — Meta забирает файл именно в момент вызова.
- media_publish вызван до того, как контейнер видео стал FINISHED.
- В один контейнер переданы и image_url, и video_url.
- Ожидается caption или стикер — API их не добавляет.
- GET /<IG_ID>/stories вызван с токеном Instagram Login — там этого edge нет.
- Дневной лимит зашит числом в коде — читайте его из content_publishing_limit.
- Видео истории страницы ниже 540p — ошибка приходит не на третьем шаге, а в статусе после загрузки.
- Фото страницы пролежало больше суток: неопубликованный photo_id уже истёк.
Частые вопросы
Какие разрешения нужны для публикации истории через Instagram API?
В схеме Instagram Login — instagram_business_basic и instagram_business_content_publish. В схеме Facebook Login — instagram_basic, instagram_content_publish и pages_read_engagement, с токеном страницы. Если роль на странице выдана через Business Manager, дополнительно нужны ads_management или ads_read.
Почему нельзя добавить подпись к истории?
История не пост в ленте, поля описания у неё нет. В Content Publishing API caption относится только к постам ленты; переданный вместе с media_type=STORIES он нигде не появится. Если нужен текст, вшивайте его в изображение или видео.
Можно ли добавить стикер или ссылку через API?
Нет. Graph API не поддерживает стикеры историй — ссылку, опрос, вопрос, геометку, музыку, @упоминание и хештег. Они добавляются только из приложения Instagram.
Как получить список активных историй?
Через GET /<IG_ID>/stories, но этот edge существует только в схеме Facebook Login. С токеном Instagram Login его нет. В списке только последние 24 часа: архива и «актуального» в API нет.
Сколько историй в сутки можно опубликовать?
История попадает в тот же лимит media_publish, что и обычный пост, но сама документация Meta даёт две цифры: 100 на общей странице и 50 на страницах media_publish и content_publishing_limit. Не зашивайте число в код — читайте quota_total и quota_usage из GET /<IG_ID>/content_publishing_limit.
История страницы Facebook отличается от истории Instagram?
Да, полностью. У страницы нет контейнера: фото сначала загружается с published=false и публикуется по photo_id через photo_stories, а видео проходит через upload_phase=start, загрузку на rupload.facebook.com и upload_phase=finish.