RelayAPI

Facebook API

Schedule and automate Facebook Page posts with RelayAPI — feed posts, multi-image, stories, reels, and first comments.

Quick Reference

PropertyValue
Platform keyfacebook
Auth methodOAuth 2.0
Character limit63,206 (truncated at ~480 with "See more")
Images per post10
Videos per post1
Image formatsJPEG, PNG, GIF (WebP auto-converted to JPEG)
Image max size10 MB
Video formatsMP4, MOV, AVI
Video max size1 GB
Video max duration240 min (feed), 120 sec (stories)
Post typesFeed (text/image/video/multi-image), Story, Reel
SchedulingYes
AnalyticsYes (impressions, likes, comments, shares, clicks, views)

SDK sourceTypeScript · Python · Go · Java · REST API

Before You Start

Facebook API posting only works with Pages — you cannot post to personal profiles. The user must be a Page Admin or Editor. After connecting via OAuth, you must select which Facebook Page to post to. Facebook tokens expire frequently, so subscribe to the account.disconnected webhook to catch token expirations proactively. Images over 4 MB are frequently rejected in practice even though the stated limit is higher.

Quick Start

Post to a Facebook Page:

import Relay from '@relayapi/sdk';

const client = new Relay();

const post = await client.posts.create({
  content: 'Exciting news from our Page!',
  targets: ['facebook'],
  scheduled_at: 'now'
});

console.log(post.id); // post_abc123
from relay import Relay

client = Relay()

post = client.posts.create(
    content='Exciting news from our Page!',
    targets=['facebook'],
    scheduled_at='now'
)

print(post.id)  # post_abc123
client := relaygo.NewClient()

post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Exciting news from our Page!"),
    Targets:     relaygo.F([]string{"facebook"}),
    ScheduledAt: relaygo.F("now"),
})

fmt.Println(post.ID) // post_abc123
RelayClient client = RelayOkHttpClient.fromEnv();

PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Exciting news from our Page!")
    .addTarget("facebook")
    .scheduledAt("now")
    .build());

System.out.println(post.id()); // post_abc123
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Exciting news from our Page!",
    "targets": ["facebook"],
    "scheduled_at": "now"
  }'

Content Types

Text-Only Post

Facebook is one of few platforms that supports text-only posts. First ~480 characters are visible before the "See more" fold.

const post = await client.posts.create({
  content: 'Just a text update for our followers.',
  targets: ['facebook'],
  scheduled_at: 'now'
});
post = client.posts.create(
    content='Just a text update for our followers.',
    targets=['facebook'],
    scheduled_at='now'
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Just a text update for our followers."),
    Targets:     relaygo.F([]string{"facebook"}),
    ScheduledAt: relaygo.F("now"),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Just a text update for our followers.")
    .addTarget("facebook")
    .scheduledAt("now")
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Just a text update for our followers.",
    "targets": ["facebook"],
    "scheduled_at": "now"
  }'

Single Image Post

const post = await client.posts.create({
  content: 'Check out this photo!',
  targets: ['facebook'],
  media: [
    { url: 'https://cdn.example.com/photo.jpg', type: 'image' }
  ],
  scheduled_at: 'now'
});
post = client.posts.create(
    content='Check out this photo!',
    targets=['facebook'],
    media=[
        {'url': 'https://cdn.example.com/photo.jpg', 'type': 'image'}
    ],
    scheduled_at='now'
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content: relaygo.F("Check out this photo!"),
    Targets: relaygo.F([]string{"facebook"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{
        {URL: relaygo.F("https://cdn.example.com/photo.jpg"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage)},
    }),
    ScheduledAt: relaygo.F("now"),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Check out this photo!")
    .addTarget("facebook")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/photo.jpg")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .scheduledAt("now")
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Check out this photo!",
    "targets": ["facebook"],
    "media": [
      {"url": "https://cdn.example.com/photo.jpg", "type": "image"}
    ],
    "scheduled_at": "now"
  }'

Multi-Image Post (Up to 10)

Up to 10 images per post. Cannot mix images and videos in the same post.

const post = await client.posts.create({
  content: 'Photo dump from our event!',
  targets: ['facebook'],
  media: [
    { url: 'https://cdn.example.com/photo1.jpg', type: 'image' },
    { url: 'https://cdn.example.com/photo2.jpg', type: 'image' },
    { url: 'https://cdn.example.com/photo3.jpg', type: 'image' }
  ],
  scheduled_at: 'now'
});
post = client.posts.create(
    content='Photo dump from our event!',
    targets=['facebook'],
    media=[
        {'url': 'https://cdn.example.com/photo1.jpg', 'type': 'image'},
        {'url': 'https://cdn.example.com/photo2.jpg', 'type': 'image'},
        {'url': 'https://cdn.example.com/photo3.jpg', 'type': 'image'}
    ],
    scheduled_at='now'
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content: relaygo.F("Photo dump from our event!"),
    Targets: relaygo.F([]string{"facebook"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{
        {URL: relaygo.F("https://cdn.example.com/photo1.jpg"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage)},
        {URL: relaygo.F("https://cdn.example.com/photo2.jpg"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage)},
        {URL: relaygo.F("https://cdn.example.com/photo3.jpg"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage)},
    }),
    ScheduledAt: relaygo.F("now"),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Photo dump from our event!")
    .addTarget("facebook")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/photo1.jpg")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/photo2.jpg")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/photo3.jpg")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .scheduledAt("now")
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Photo dump from our event!",
    "targets": ["facebook"],
    "media": [
      {"url": "https://cdn.example.com/photo1.jpg", "type": "image"},
      {"url": "https://cdn.example.com/photo2.jpg", "type": "image"},
      {"url": "https://cdn.example.com/photo3.jpg", "type": "image"}
    ],
    "scheduled_at": "now"
  }'

Video Post

Single video per post. Up to 4 GB, max 240 minutes for feed videos.

const post = await client.posts.create({
  content: 'Watch our latest video!',
  targets: ['facebook'],
  media: [
    { url: 'https://cdn.example.com/video.mp4', type: 'video' }
  ],
  scheduled_at: 'now'
});
post = client.posts.create(
    content='Watch our latest video!',
    targets=['facebook'],
    media=[
        {'url': 'https://cdn.example.com/video.mp4', 'type': 'video'}
    ],
    scheduled_at='now'
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content: relaygo.F("Watch our latest video!"),
    Targets: relaygo.F([]string{"facebook"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{
        {URL: relaygo.F("https://cdn.example.com/video.mp4"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeVideo)},
    }),
    ScheduledAt: relaygo.F("now"),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Watch our latest video!")
    .addTarget("facebook")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/video.mp4")
        .type(PostCreateParams.Media.Type.VIDEO)
        .build())
    .scheduledAt("now")
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Watch our latest video!",
    "targets": ["facebook"],
    "media": [
      {"url": "https://cdn.example.com/video.mp4", "type": "video"}
    ],
    "scheduled_at": "now"
  }'

Story

Media is required for stories. Text captions are not displayed on Facebook Stories. Interactive stickers are not available via the API.

const post = await client.posts.create({
  targets: ['facebook'],
  media: [
    { url: 'https://cdn.example.com/story.jpg', type: 'image' }
  ],
  scheduled_at: 'now',
  target_options: {
    facebook: {
      content_type: 'story'
    }
  }
});
post = client.posts.create(
    targets=['facebook'],
    media=[
        {'url': 'https://cdn.example.com/story.jpg', 'type': 'image'}
    ],
    scheduled_at='now',
    target_options={
        'facebook': {
            'content_type': 'story'
        }
    }
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Targets: relaygo.F([]string{"facebook"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{
        {URL: relaygo.F("https://cdn.example.com/story.jpg"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage)},
    }),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "facebook": map[string]interface{}{
            "content_type": "story",
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .addTarget("facebook")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/story.jpg")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("facebook", JsonValue.from(Map.of(
            "content_type", "story"
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "targets": ["facebook"],
    "media": [
      {"url": "https://cdn.example.com/story.jpg", "type": "image"}
    ],
    "scheduled_at": "now",
    "target_options": {
      "facebook": {
        "content_type": "story"
      }
    }
  }'

Reel

Single vertical video, 3-60 seconds. Reels support a separate title field.

const post = await client.posts.create({
  content: 'Behind the scenes of our latest shoot',
  targets: ['facebook'],
  media: [
    { url: 'https://cdn.example.com/reel.mp4', type: 'video' }
  ],
  scheduled_at: 'now',
  target_options: {
    facebook: {
      content_type: 'reel',
      title: 'Studio day'
    }
  }
});
post = client.posts.create(
    content='Behind the scenes of our latest shoot',
    targets=['facebook'],
    media=[
        {'url': 'https://cdn.example.com/reel.mp4', 'type': 'video'}
    ],
    scheduled_at='now',
    target_options={
        'facebook': {
            'content_type': 'reel',
            'title': 'Studio day'
        }
    }
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content: relaygo.F("Behind the scenes of our latest shoot"),
    Targets: relaygo.F([]string{"facebook"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{
        {URL: relaygo.F("https://cdn.example.com/reel.mp4"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeVideo)},
    }),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "facebook": map[string]interface{}{
            "content_type": "reel",
            "title":        "Studio day",
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Behind the scenes of our latest shoot")
    .addTarget("facebook")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/reel.mp4")
        .type(PostCreateParams.Media.Type.VIDEO)
        .build())
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("facebook", JsonValue.from(Map.of(
            "content_type", "reel",
            "title", "Studio day"
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Behind the scenes of our latest shoot",
    "targets": ["facebook"],
    "media": [
      {"url": "https://cdn.example.com/reel.mp4", "type": "video"}
    ],
    "scheduled_at": "now",
    "target_options": {
      "facebook": {
        "content_type": "reel",
        "title": "Studio day"
      }
    }
  }'

Post with First Comment

Automatically post a first comment after publishing. Useful for adding links without suppressing reach.

const post = await client.posts.create({
  content: 'New product launch!',
  targets: ['facebook'],
  media: [
    { url: 'https://cdn.example.com/product.jpg', type: 'image' }
  ],
  scheduled_at: 'now',
  target_options: {
    facebook: {
      first_comment: 'Link to purchase: https://shop.example.com'
    }
  }
});
post = client.posts.create(
    content='New product launch!',
    targets=['facebook'],
    media=[
        {'url': 'https://cdn.example.com/product.jpg', 'type': 'image'}
    ],
    scheduled_at='now',
    target_options={
        'facebook': {
            'first_comment': 'Link to purchase: https://shop.example.com'
        }
    }
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content: relaygo.F("New product launch!"),
    Targets: relaygo.F([]string{"facebook"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{
        {URL: relaygo.F("https://cdn.example.com/product.jpg"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage)},
    }),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "facebook": map[string]interface{}{
            "first_comment": "Link to purchase: https://shop.example.com",
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("New product launch!")
    .addTarget("facebook")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/product.jpg")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("facebook", JsonValue.from(Map.of(
            "first_comment", "Link to purchase: https://shop.example.com"
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "New product launch!",
    "targets": ["facebook"],
    "media": [
      {"url": "https://cdn.example.com/product.jpg", "type": "image"}
    ],
    "scheduled_at": "now",
    "target_options": {
      "facebook": {
        "first_comment": "Link to purchase: https://shop.example.com"
      }
    }
  }'

First comments only work with feed posts. They are not supported on Stories or Reels.

Multi-Page Posting

Post to multiple Facebook Pages in a single request by targeting different account IDs.

const post = await client.posts.create({
  content: 'News from all our brands!',
  targets: ['acc_fb_brand1', 'acc_fb_brand2'],
  scheduled_at: 'now'
});
post = client.posts.create(
    content='News from all our brands!',
    targets=['acc_fb_brand1', 'acc_fb_brand2'],
    scheduled_at='now'
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("News from all our brands!"),
    Targets:     relaygo.F([]string{"acc_fb_brand1", "acc_fb_brand2"}),
    ScheduledAt: relaygo.F("now"),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("News from all our brands!")
    .addTarget("acc_fb_brand1")
    .addTarget("acc_fb_brand2")
    .scheduledAt("now")
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "News from all our brands!",
    "targets": ["acc_fb_brand1", "acc_fb_brand2"],
    "scheduled_at": "now"
  }'

Media Requirements

Images

PropertyFeed PostStory
Max images101
FormatsJPEG, PNG, GIF (WebP auto-converted)JPEG, PNG
Max file size4 MB4 MB
Recommended1200 x 630 px1080 x 1920 px

Videos

PropertyFeed VideoStoryReel
Max videos111
FormatsMP4, MOVMP4, MOVMP4, MOV
Max file size4 GB4 GB
Max duration240 min120 sec60 sec
Min duration1 sec1 sec3 sec
Recommended1280 x 720 min, H.264, 30fps1080 x 19209:16 vertical

target_options Fields

All fields go inside target_options.facebook on your post request.

FieldTypeDescription
contentstringOverride content for Facebook specifically
mediaobject[]Override media for Facebook specifically
content_typestring"story" or "reel" (default: feed post)
titlestringReel title (only for reels, separate from content/caption)
first_commentstringAuto-posted first comment (feed posts only, not stories or reels)
page_idstringTarget a specific Page for multi-page accounts

Common Errors

ErrorCauseFix
Photos should be smaller than 4 MBImage exceeds practical size limitReduce to under 4 MB. Use JPEG or PNG.
Missing or invalid image fileCorrupt file, wrong format, or inaccessible URLVerify the URL returns actual media bytes. Use JPEG or PNG under 4 MB.
Unable to fetch video from URLVideo URL is inaccessible to Facebook serversUse a direct, publicly accessible CDN URL.
Tokens expiredOAuth token has expiredReconnect the account. Facebook tokens have shorter lifespans than most platforms.
Confirm your identityFacebook security check triggeredThe user must complete identity verification directly on Facebook.

Known Quirks

  • Pages only — you cannot post to personal Facebook profiles via the API.
  • Cannot mix images and videos in the same post.
  • WebP images are auto-converted to JPEG by Facebook.
  • Story text captions are not displayed — only the media is shown.
  • Story interactive stickers are not available via the API.
  • Facebook often rejects images over 4 MB in practice, even though the documented limit is higher.
  • Tokens expire frequently — subscribe to the account.disconnected webhook to detect token expirations.
  • GIFs should use type: "video" — they auto-play and loop when sent as video type.
  • First ~480 characters visible before the "See more" fold on feed posts.

Automations

Facebook Messenger is a Tier 1 automation platform — full inbound conversational event coverage and 9 outbound send node types.

Triggers

TypeFires on
facebook_dmInbound Messenger DM
facebook_commentNew comment on a Page post
facebook_mentionPage @-mentioned
facebook_postbackGet Started / persistent menu button
facebook_reactionMessage reaction added/removed
facebook_optinSend-to-Messenger widget / Ref URL opt-in
facebook_feed_postNew post on the Page feed

Send nodes

All messaging nodes hit POST {GRAPH_BASE.facebook}/{page-id}/messages. Comment operations use the Graph API comment endpoints.

NodeRequired fields
facebook_send_texttext
facebook_send_mediaurl, media_type ∈ image|video|audio|file
facebook_send_templatepayload (raw Messenger template object)
facebook_send_quick_repliestext, quick_replies[] (≤13)
facebook_send_button_templatetext, buttons[] (≤3)
facebook_reply_to_commentmessage (+ optional comment_id)
facebook_private_replytext (+ optional comment_id)
facebook_hide_commentcomment_id (or falls back to trigger payload)
facebook_sender_actionaction ∈ typing_on|typing_off|mark_seen

The HUMAN_AGENT message tag is for human-handled surfaces only, not an automation escape hatch. Automated DMs must land inside the 24-hour customer-service window.

Found something wrong? Help us improve this page.

On this page

Submit an Issue
Requires a GitHub account.View repo