Last Updated: 2026-03-28 Status: Active
This guide covers everything you need to master the Facebook Graph API — from foundational concepts to advanced publishing workflows for Facebook Pages and Instagram. It is written for developers building social media automation tools (like the LogicSpike Content Engine).
1. What is the Graph API?
The Facebook Graph API is the primary interface for reading and writing data to Meta's platforms (Facebook, Instagram, Messenger, WhatsApp). It models the entire social platform as a graph — a collection of objects (nodes) connected by relationships (edges).
1.1 Base URL
Every Graph API request hits this base:
https://graph.facebook.com/v25.0/The v25.0 is the version identifier. Meta releases new versions roughly every 4 months. Each version is supported for approximately 2 years before deprecation.
IMPORTANT
Always pin your API calls to a specific version. Unversioned calls default to the oldest supported version — which may be deprecated soon.
1.2 The Three Building Blocks
The entire Graph API is built on three primitives:
| Concept | What It Is | Example |
|---|---|---|
| Node | An individual object in the graph | A User (/me), a Page (/123456), a Photo (/789) |
| Edge | A connection between two nodes | A Page's feed (/123456/feed), a User's accounts (/me/accounts) |
| Field | A property of a node | name, id, created_time, message |
1.3 Anatomy of a Request
Every Graph API call follows this pattern:
GET https://graph.facebook.com/v25.0/{node-id}?fields={field1},{field2}&access_token={token}Breaking it down:
| Part | Purpose | Example |
|---|---|---|
v25.0 |
API version | Always use a specific version |
{node-id} |
The object you are querying | me, 123456789, a Page ID |
fields |
Explicitly request only the data you need | name,id,fan_count |
access_token |
Proves who you are and what you can do | A User, Page, or App token |
Example — Get your own profile name and ID:
curl -X GET "https://graph.facebook.com/v25.0/me?fields=id,name&access_token=EAABs..."Response:
{
"id": "10229876543210",
"name": "Dipanshu Vishwakarma"
}1.4 HTTP Methods
| Method | Purpose | Example |
|---|---|---|
GET |
Read data | Fetch a Page's info, list posts |
POST |
Create or update data | Publish a post, upload a photo |
DELETE |
Remove data | Delete a post or comment |
2. Access Tokens — The Key to Everything
Access tokens are the most critical concept in the Graph API. Every request requires one. They determine who is making the request and what they can access.
2.1 Types of Access Tokens
| Token Type | Who Uses It | Lifespan | How to Get It |
|---|---|---|---|
| User Access Token | Acts on behalf of a user | Short-lived: ~1 hour; Long-lived: ~60 days | Facebook Login (OAuth 2.0) |
| Page Access Token | Acts on behalf of a Facebook Page | Inherits from user token; can be made "never-expiring" | Exchange from User Token via /me/accounts |
| App Access Token | Acts on behalf of your app itself | Never expires (until secret is reset) | GET /oauth/access_token?client_id={id}&client_secret={secret}&grant_type=client_credentials |
| System User Token | Automated server-to-server actions | Long-lived | Created in Meta Business Manager |
2.2 Getting a User Access Token (OAuth 2.0 Flow)
This is the standard flow when a user clicks "Connect Facebook" in your app.
Step 1 — Build the Authorization URL:
https://www.facebook.com/v25.0/dialog/oauth?
client_id={YOUR_APP_ID}
&redirect_uri={YOUR_CALLBACK_URL}
&scope=pages_show_list,pages_manage_posts,instagram_basic,instagram_content_publish
&state={CSRF_TOKEN}| Parameter | Purpose |
|---|---|
client_id |
Your Facebook App ID |
redirect_uri |
Where Meta sends the user back (must match your app settings exactly) |
scope |
Comma-separated list of permissions you are requesting |
state |
A random string you generate to prevent CSRF attacks — verify it on callback |
Step 2 — Exchange the Code for a Token:
curl -X GET "https://graph.facebook.com/v25.0/oauth/access_token?\
client_id={APP_ID}&\
redirect_uri={CALLBACK_URL}&\
client_secret={APP_SECRET}&\
code={CODE_FROM_STEP_1}"Response:
{
"access_token": "EAABs...short_lived_token",
"token_type": "bearer",
"expires_in": 3600
}Step 3 — Exchange for a Long-Lived Token:
curl -X GET "https://graph.facebook.com/v25.0/oauth/access_token?\
grant_type=fb_exchange_token&\
client_id={APP_ID}&\
client_secret={APP_SECRET}&\
fb_exchange_token={SHORT_LIVED_TOKEN}"Response:
{
"access_token": "EAABs...long_lived_token",
"token_type": "bearer",
"expires_in": 5184000
}TIP
The long-lived token lasts ~60 days. Store it securely in your database (encrypted). Set up a cron job to refresh it before expiration.
2.3 Getting a Page Access Token
Once you have a User Access Token, you can fetch Page tokens for all Pages the user manages:
curl -X GET "https://graph.facebook.com/v25.0/me/accounts?\
fields=id,name,access_token&\
access_token={USER_ACCESS_TOKEN}"Response:
{
"data": [
{
"id": "108234567890123",
"name": "My Business Page",
"access_token": "EAABs...page_access_token"
}
]
}IMPORTANT
If you obtained the User Access Token as a long-lived token, the Page Access Token returned here is never-expiring. This is the token you save for automated posting.
2.4 Debugging Tokens
Use the Debug Token endpoint to inspect any token's metadata:
curl -X GET "https://graph.facebook.com/debug_token?\
input_token={TOKEN_TO_INSPECT}&\
access_token={APP_ID}|{APP_SECRET}"Response (key fields):
{
"data": {
"app_id": "123456789",
"type": "PAGE",
"is_valid": true,
"expires_at": 0,
"scopes": ["pages_show_list", "pages_manage_posts"]
}
}| Field | Meaning |
|---|---|
is_valid |
Whether the token is still active |
expires_at |
0 means never-expiring |
scopes |
The permissions granted to this token |
type |
USER, PAGE, or APP |
3. Permissions (Scopes) — What You Can Access
Permissions control what data your app can read or write. They are requested during the OAuth flow via the scope parameter.
3.1 Permission Access Levels
| Level | Who Can Use It | Requires App Review? |
|---|---|---|
| Standard Access | Only users with a role on your app (Admin, Developer, Tester) | No |
| Advanced Access | Any Facebook user (general public) | Yes |
WARNING
In Development Mode, your app only has Standard Access. Only role-holders can use it. You must pass App Review and switch to Live Mode for public use.
3.2 Key Permissions for Content Publishing
| Permission | What It Grants | Required For |
|---|---|---|
pages_show_list |
List Pages the user manages | Getting Page IDs |
pages_read_engagement |
Read Page engagement metrics | Reading Page info |
pages_manage_posts |
Create, edit, delete posts on a Page | Publishing to Facebook Pages |
pages_manage_metadata |
Manage Page metadata | Updating Page settings |
instagram_basic |
Read Instagram profile info | Getting IG User ID |
instagram_content_publish |
Publish content to Instagram | Posting images, videos, reels, carousels |
instagram_manage_comments |
Read and manage IG comments | Comment moderation |
instagram_manage_insights |
Read IG analytics | Performance tracking |
business_management |
Manage Business Manager assets | Advanced business operations |
publish_video |
Publish videos to Pages | Uploading videos and reels to Facebook |
3.3 How the scope Parameter Works
When building your OAuth URL, you list the permissions you need:
scope=pages_show_list,pages_manage_posts,instagram_basic,instagram_content_publishThe user sees a consent screen listing each permission. They can grant or deny individual scopes. Always check which scopes were actually granted:
curl -X GET "https://graph.facebook.com/v25.0/me/permissions?\
access_token={USER_TOKEN}"Response:
{
"data": [
{ "permission": "pages_show_list", "status": "granted" },
{ "permission": "pages_manage_posts", "status": "granted" },
{ "permission": "instagram_basic", "status": "declined" }
]
}CAUTION
Never assume all scopes were granted. Always verify. If a required permission is declined, prompt the user to re-authorize with that specific scope.
4. Publishing to Facebook Pages
This section covers every type of content you can publish to a Facebook Page.
4.1 Publish a Text Post
curl -X POST "https://graph.facebook.com/v25.0/{page-id}/feed" \
-F "message=Hello from the Graph API! 🚀" \
-F "access_token={PAGE_ACCESS_TOKEN}"Response:
{
"id": "108234567890123_456789012345678"
}The returned id is the Post ID — format is {page-id}_{post-id}.
4.2 Publish a Link Post
curl -X POST "https://graph.facebook.com/v25.0/{page-id}/feed" \
-F "message=Check out our latest blog post!" \
-F "link=https://logicspike.com/blog/my-article" \
-F "access_token={PAGE_ACCESS_TOKEN}"Facebook automatically scrapes the link for an Open Graph preview (title, description, image).
4.3 Publish a Photo Post
curl -X POST "https://graph.facebook.com/v25.0/{page-id}/photos" \
-F "caption=Our new product launch! 📸" \
-F "url=https://example.com/photo.jpg" \
-F "access_token={PAGE_ACCESS_TOKEN}"| Parameter | Description |
|---|---|
caption |
The text accompanying the photo |
url |
A publicly accessible URL to the image |
source |
Alternative: upload the file directly (multipart/form-data) |
published |
true (default) to post immediately, false for draft |
scheduled_publish_time |
Unix timestamp for scheduled posting (requires published=false) |
4.4 Publish a Video
Video publishing uses the Resumable Upload API — a multi-step process:
Step 1 — Initialize the upload session:
curl -X POST "https://graph.facebook.com/v25.0/{app-id}/uploads" \
-F "file_type=video/mp4" \
-F "file_length=15728640" \
-F "access_token={PAGE_ACCESS_TOKEN}"Response:
{
"id": "upload:MTphdHRhY2htZW50..."
}Step 2 — Upload the video file:
curl -X POST "https://graph.facebook.com/v25.0/upload:{session_id}" \
--header "Authorization: OAuth {PAGE_ACCESS_TOKEN}" \
--header "file_offset: 0" \
--data-binary @video.mp4Step 3 — Publish to the Page:
curl -X POST "https://graph.facebook.com/v25.0/{page-id}/videos" \
-F "title=My Video Title" \
-F "description=A great video description" \
-F "fbuploader_video_file_chunk=upload:{session_id}" \
-F "access_token={PAGE_ACCESS_TOKEN}"4.5 Publish a Reel (Facebook)
Reels use a dedicated endpoint with a 3-phase upload:
Phase 1 — Initialize:
curl -X POST "https://graph.facebook.com/v25.0/{page-id}/video_reels" \
-F "upload_phase=start" \
-F "access_token={PAGE_ACCESS_TOKEN}"Response:
{
"video_id": "123456789",
"upload_url": "https://rupload.facebook.com/video-upload/..."
}Phase 2 — Upload the video file:
curl --location "$UPLOAD_URL" \
--header "Authorization: OAuth {PAGE_ACCESS_TOKEN}" \
--header "offset: 0" \
--header "file_size: {FILE_SIZE_BYTES}" \
--data-binary @reel_video.mp4Phase 3 — Publish:
curl -X POST "https://graph.facebook.com/v25.0/{page-id}/video_reels" \
-F "upload_phase=finish" \
-F "video_id=123456789" \
-F "title=My First Reel" \
-F "description=Check this out! #trending" \
-F "access_token={PAGE_ACCESS_TOKEN}"Reel Specifications:
| Spec | Requirement |
|---|---|
| Aspect Ratio | 9:16 (recommended) |
| Resolution | 1080x1920 |
| Duration | 3–90 seconds |
| Format | .mp4 or .mov (H.264 codec) |
| Rate Limit | 30 reels per 24-hour window |
4.6 Scheduled Posts
Any post type supports scheduling by adding these parameters:
curl -X POST "https://graph.facebook.com/v25.0/{page-id}/feed" \
-F "message=This post goes live tomorrow at 9 AM!" \
-F "published=false" \
-F "scheduled_publish_time=1711612800" \
-F "access_token={PAGE_ACCESS_TOKEN}"| Parameter | Description |
|---|---|
published |
Must be false for scheduled posts |
scheduled_publish_time |
Unix timestamp (must be 10 min to 75 days in the future) |
5. Publishing to Instagram
Instagram publishing uses a container-based workflow: you first create a media container, then publish it.
IMPORTANT
Instagram API publishing only works with Business or Creator accounts connected to a Facebook Page. Personal accounts cannot use the publishing API.
5.1 Getting the Instagram User ID
First, find the Instagram account linked to a Facebook Page:
curl -X GET "https://graph.facebook.com/v25.0/{page-id}?\
fields=instagram_business_account&\
access_token={PAGE_ACCESS_TOKEN}"Response:
{
"instagram_business_account": {
"id": "17841400123456789"
},
"id": "108234567890123"
}The instagram_business_account.id is your IG User ID — used in all Instagram API calls.
5.2 Publish a Single Image
Step 1 — Create the container:
curl -X POST "https://graph.facebook.com/v25.0/{ig-user-id}/media" \
-F "image_url=https://example.com/photo.jpg" \
-F "caption=Beautiful sunset! 🌅 #photography" \
-F "access_token={PAGE_ACCESS_TOKEN}"Response:
{
"id": "17889455123456789"
}Step 2 — Publish the container:
curl -X POST "https://graph.facebook.com/v25.0/{ig-user-id}/media_publish" \
-F "creation_id=17889455123456789" \
-F "access_token={PAGE_ACCESS_TOKEN}"| Parameter | Description |
|---|---|
image_url |
Must be a publicly accessible URL (Instagram's servers download it) |
caption |
Post text (supports hashtags and mentions) |
alt_text |
Accessibility description for the image |
location_id |
Optional Facebook Place ID for location tagging |
5.3 Publish a Video (Feed Post)
curl -X POST "https://graph.facebook.com/v25.0/{ig-user-id}/media" \
-F "video_url=https://example.com/video.mp4" \
-F "media_type=VIDEO" \
-F "caption=Check out this video! 🎬" \
-F "access_token={PAGE_ACCESS_TOKEN}"Then publish with /media_publish as in Step 2 above.
NOTE
Video processing takes time. Check the container status before publishing:
GET /{container-id}?fields=status_codeWait until status_code is FINISHED before calling /media_publish.
5.4 Publish a Reel (Instagram)
curl -X POST "https://graph.facebook.com/v25.0/{ig-user-id}/media" \
-F "video_url=https://example.com/reel.mp4" \
-F "media_type=REELS" \
-F "caption=My first API reel! 🎥 #reels" \
-F "cover_url=https://example.com/cover.jpg" \
-F "share_to_feed=true" \
-F "access_token={PAGE_ACCESS_TOKEN}"| Parameter | Description |
|---|---|
media_type |
Must be REELS |
video_url |
Publicly accessible video URL |
cover_url |
Optional custom cover image |
share_to_feed |
true to also show on the feed grid |
Reel Specs:
| Spec | Requirement |
|---|---|
| Aspect Ratio | 9:16 |
| Duration | 3–90 seconds |
| Format | .mp4 (H.264 codec, AAC audio) |
| Max File Size | 1 GB |
5.5 Publish a Carousel
Carousels require creating individual child containers first, then a parent container:
Step 1 — Create child containers (up to 10):
# Image child
curl -X POST "https://graph.facebook.com/v25.0/{ig-user-id}/media" \
-F "image_url=https://example.com/slide1.jpg" \
-F "is_carousel_item=true" \
-F "access_token={PAGE_ACCESS_TOKEN}"
# Response: { "id": "CHILD_1_ID" }
# Video child
curl -X POST "https://graph.facebook.com/v25.0/{ig-user-id}/media" \
-F "video_url=https://example.com/slide2.mp4" \
-F "media_type=VIDEO" \
-F "is_carousel_item=true" \
-F "access_token={PAGE_ACCESS_TOKEN}"
# Response: { "id": "CHILD_2_ID" }Step 2 — Create the parent carousel container:
curl -X POST "https://graph.facebook.com/v25.0/{ig-user-id}/media" \
-F "media_type=CAROUSEL" \
-F "caption=Swipe through our gallery! ➡️" \
-F "children=CHILD_1_ID,CHILD_2_ID" \
-F "access_token={PAGE_ACCESS_TOKEN}"
# Response: { "id": "CAROUSEL_CONTAINER_ID" }Step 3 — Publish:
curl -X POST "https://graph.facebook.com/v25.0/{ig-user-id}/media_publish" \
-F "creation_id=CAROUSEL_CONTAINER_ID" \
-F "access_token={PAGE_ACCESS_TOKEN}"TIP
Carousels count as a single post toward the 100-post daily rate limit.
5.6 Publish a Story
curl -X POST "https://graph.facebook.com/v25.0/{ig-user-id}/media" \
-F "image_url=https://example.com/story.jpg" \
-F "media_type=STORIES" \
-F "access_token={PAGE_ACCESS_TOKEN}"Then publish with /media_publish. Stories expire after 24 hours. Interactive stickers (polls, links) are not supported via API.
5.7 Container Status Codes
Always check container status before publishing, especially for videos:
curl -X GET "https://graph.facebook.com/v25.0/{container-id}?\
fields=status_code,status&\
access_token={PAGE_ACCESS_TOKEN}"| Status Code | Meaning |
|---|---|
FINISHED |
Ready to publish |
IN_PROGRESS |
Still processing (wait and retry) |
ERROR |
Something went wrong — check status for details |
EXPIRED |
Container expired (24-hour TTL) — recreate it |
6. Reading Data — Common Query Patterns
6.1 Field Expansion (Nested Queries)
Request nested data in a single call:
curl -X GET "https://graph.facebook.com/v25.0/{page-id}?\
fields=name,fan_count,posts{message,created_time,likes.summary(true)}&\
access_token={PAGE_ACCESS_TOKEN}"This returns the Page info AND its recent posts with like counts — all in one request.
6.2 Pagination
Large result sets are paginated. The response includes a paging object:
{
"data": [...],
"paging": {
"cursors": {
"before": "QVFIu...",
"after": "QVFIu..."
},
"next": "https://graph.facebook.com/v25.0/.../feed?after=QVFIu..."
}
}| Pagination Type | Parameters | Best For |
|---|---|---|
| Cursor-based | after, before |
Most endpoints (recommended) |
| Time-based | since, until (Unix timestamps) |
Chronological data (posts, comments) |
| Offset-based | offset, limit |
Legacy — avoid when possible |
TIP
Always use the next URL from the response rather than manually constructing pagination URLs. When next is absent, you have reached the end.
6.3 The /me Endpoint
/me is an alias for the currently authenticated user or Page:
# With a User Token → returns user info
GET /v25.0/me?fields=id,name
# With a Page Token → returns Page info
GET /v25.0/me?fields=id,name,fan_count7. Batch Requests
Send up to 50 requests in a single HTTP call to reduce latency:
curl -X POST "https://graph.facebook.com/v25.0/" \
-F "access_token={TOKEN}" \
-F 'batch=[
{"method":"GET","relative_url":"me?fields=id,name"},
{"method":"GET","relative_url":"{page-id}?fields=fan_count"},
{"method":"POST","relative_url":"{page-id}/feed","body":"message=Hello!"}
]'Response: An array of individual responses:
[
{ "code": 200, "body": "{\"id\":\"123\",\"name\":\"Dipanshu\"}" },
{ "code": 200, "body": "{\"fan_count\":1500}" },
{ "code": 200, "body": "{\"id\":\"123_456\"}" }
]WARNING
Each operation in a batch counts toward your rate limits individually. Batching saves network round-trips, not quota.
8. Webhooks — Real-Time Updates
Instead of polling the API, subscribe to real-time notifications:
8.1 How Webhooks Work
8.2 Verification Endpoint
Meta verifies your webhook with a GET request:
// Example: Hono/Express handler
app.get('/webhook', (c) => {
const mode = c.req.query('hub.mode');
const token = c.req.query('hub.verify_token');
const challenge = c.req.query('hub.challenge');
if (mode === 'subscribe' && token === 'MY_VERIFY_TOKEN') {
return c.text(challenge, 200);
}
return c.text('Forbidden', 403);
});8.3 Receiving Events
app.post('/webhook', async (c) => {
const body = await c.req.json();
// Respond immediately — process async
c.executionCtx.waitUntil(processWebhookEvent(body));
return c.text('OK', 200);
});CAUTION
You must respond with 200 OK within 5 seconds. If Meta receives failures, your subscription is deactivated. Always offload heavy processing to a background queue.
9. Error Handling
9.1 Error Response Format
Every error returns this structure:
{
"error": {
"message": "Invalid OAuth access token.",
"type": "OAuthException",
"code": 190,
"error_subcode": 463,
"fbtrace_id": "AbC123dEf"
}
}9.2 Common Error Codes Reference
| Code | Subcode | Meaning | Action |
|---|---|---|---|
| 1 | — | Unknown error | Retry with exponential backoff |
| 2 | — | Temporary service error | Retry after 30 seconds |
| 4 | — | Application-level rate limit | Wait, check x-app-usage header |
| 10 | — | Permission denied | Check your app's approved permissions |
| 17 | — | User-level rate limit | Wait, check x-business-use-case-usage header |
| 100 | — | Invalid parameter | Validate request params |
| 190 | 458 | App not installed | User removed your app — re-authorize |
| 190 | 460 | Password changed | User changed password — re-authorize |
| 190 | 463 | Token expired | Exchange refresh token or re-authorize |
| 190 | 467 | Invalid token | Token is corrupted — re-authorize |
| 200–299 | — | Permissions error | Request the missing scope |
| 368 | — | Temporarily blocked for policy violations | Review content for policy compliance |
9.3 Rate Limit Headers
Monitor these headers in every response:
// x-app-usage header (parsed)
{
"call_count": 28,
"total_cputime": 15,
"total_time": 12
}| Field | Meaning | Throttle At |
|---|---|---|
call_count |
% of calls used in current window | 100% |
total_cputime |
% of CPU time consumed | 100% |
total_time |
% of total time consumed | 100% |
Strategy: Implement exponential backoff when any value exceeds 80%.
10. App Review & Going Live
10.1 Development Mode vs Live Mode
| Aspect | Development Mode | Live Mode |
|---|---|---|
| Who can use it | Only app role-holders (Admin, Dev, Tester) | Any Facebook user |
| Permissions | All permissions work for role-holders | Only approved permissions work |
| Content visibility | Only visible to role-holders | Public |
| App Review required | No | Yes (for Advanced Access) |
10.2 The App Review Process
What you must provide for each permission:
- A written explanation of how your app uses the data
- A screen recording (screencast) demonstrating the feature
- At least one successful API call made within 30 days of submission
- A functional prototype (not just mockups)
10.3 Business Verification
Required before you can get Advanced Access:
- Go to Meta Business Manager → Security Center
- Upload business documents (registration, tax ID)
- Verify your contact info (phone or email)
- Meta reviews and approves (usually 2–5 business days)
IMPORTANT
Document names and addresses must exactly match your official registration. Mismatches are the #1 rejection reason.
11. Developer Tools & Debugging
11.1 Graph API Explorer
The most important tool for learning and debugging:
URL: https://developers.facebook.com/tools/explorer
What you can do:
- Generate test access tokens with specific permissions
- Execute any Graph API request interactively
- Inspect response structure before writing code
- Test edge cases and error scenarios
11.2 Access Token Debugger
URL: https://developers.facebook.com/tools/debug/accesstoken
Paste any token to see: type, expiration, scopes, associated app, and user.
11.3 Useful curl Debugging Tips
Add &debug=all to any request for extra debug info:
GET /v25.0/me?fields=id&debug=all&access_token={TOKEN}12. Quick Reference — Complete Endpoint Map
12.1 Facebook Page Endpoints
| Action | Method | Endpoint |
|---|---|---|
| Get Page info | GET | /{page-id}?fields=name,fan_count |
| List managed Pages | GET | /me/accounts |
| Publish text post | POST | /{page-id}/feed |
| Publish photo | POST | /{page-id}/photos |
| Publish video | POST | /{page-id}/videos |
| Publish reel | POST | /{page-id}/video_reels |
| Get Page feed | GET | /{page-id}/feed |
| Delete a post | DELETE | /{post-id} |
| Get post insights | GET | /{post-id}/insights |
12.2 Instagram Endpoints
| Action | Method | Endpoint |
|---|---|---|
| Get IG User ID | GET | /{page-id}?fields=instagram_business_account |
| Create media container | POST | /{ig-user-id}/media |
| Publish container | POST | /{ig-user-id}/media_publish |
| Check container status | GET | /{container-id}?fields=status_code |
| Get user media | GET | /{ig-user-id}/media |
| Get media insights | GET | /{media-id}/insights |
| Get user insights | GET | /{ig-user-id}/insights |
| Delete media | DELETE | /{media-id} |
12.3 Token Endpoints
| Action | Method | Endpoint |
|---|---|---|
| OAuth dialog | GET | facebook.com/v25.0/dialog/oauth |
| Exchange code for token | GET | /oauth/access_token |
| Extend token | GET | /oauth/access_token?grant_type=fb_exchange_token |
| Debug token | GET | /debug_token?input_token={token} |
| Check permissions | GET | /me/permissions |
13. Best Practices Checklist
- Always specify API version in URLs (
v25.0) - Request only the fields you need (reduces response size and improves speed)
- Use long-lived tokens for server-side integrations
- Store Page Access Tokens (never-expiring) securely in your database
- Implement exponential backoff for rate-limited requests
- Monitor
x-app-usageheaders on every response - Check container status before publishing Instagram videos
- Verify granted permissions after OAuth — never assume all scopes were approved
- Use the Graph API Explorer to prototype before coding
- Subscribe to the Meta changelog for deprecation notices
- Always respond to webhooks within 5 seconds
- Host media on public URLs with proper SSL for Instagram publishing
14. Captions, Hashtags & Interactive Features
14.1 Hashtags in Captions
Hashtags work on both platforms — just include them directly in the caption or message text:
# Facebook
POST /{page-id}/feed
message=Launching today! 🚀 #startup #saas
# Instagram
POST /{ig-user-id}/media
caption=Beautiful vibes ☀️ #photography #nature| Rule | ||
|---|---|---|
| Hashtags in caption | ✅ Just type #tag |
✅ Just type #tag |
| Max hashtags | No hard limit (avoid spam) | 5 per post (new 2026 rule) |
| Clickable/searchable | ✅ | ✅ |
Mentions (@username) |
✅ | ✅ |
| Emojis | ✅ | ✅ |
| Line breaks | Use \n in the string |
Use \n in the string |
| Max caption length | ~63,206 characters | 2,200 characters |
WARNING
Instagram recently reduced the max hashtags to 5 per post. For best engagement, Instagram recommends 3–5 relevant hashtags. Exceeding the limit causes the post to fail.
14.2 Caption with Line Breaks Example
const caption = [
"Launching LogicSpike v2.0 today! 🚀",
"",
"New features:",
"✅ AI content scheduling",
"✅ Multi-platform publishing",
"",
"#startup #saas #ai"
].join("\n");14.3 Polls, Prompts & Interactive Features
Interactive elements (polls, quizzes, question stickers, AI caption prompts) are in-app-only features and are not available through the Graph API:
| Feature | In Instagram App | Via Graph API |
|---|---|---|
| Polls (Stories/Feed/Reels) | ✅ | ❌ |
| AI caption prompts/suggestions | ✅ | ❌ |
| Question stickers | ✅ | ❌ |
| Quiz stickers | ✅ | ❌ |
| Countdown stickers | ✅ | ❌ |
| Music sticker | ✅ | ❌ |
| Link sticker | ✅ | ❌ |
| Location tag | ✅ | ✅ (location_id param) |
| User tags | ✅ | ✅ (user_tags param) |
| Hashtags in caption | ✅ | ✅ (just text) |
| Mentions in caption | ✅ | ✅ (just text) |
Workaround: Use caption-based engagement instead — add questions like "What do you think? Comment below! 👇"
15. FAQ — Common Doubts & Edge Cases
Q1: Can I post an image with music?
❌ Not directly. The Graph API has no music_id or audio_url parameter. Meta's music library is in-app-only (licensed content).
Workaround: Pre-render the image + audio into a video file using FFmpeg, then post as a Reel:
ffmpeg -loop 1 -i photo.jpg -i music.mp3 \
-c:v libx264 -tune stillimage \
-c:a aac -b:a 128k \
-vf "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:-1:-1:color=black" \
-shortest -pix_fmt yuv420p \
-t 15 output_reel.mp4Then upload output_reel.mp4 as a Reel via the standard /media → /media_publish flow.
Q2: Can I add the "Made with AI" label via API?
Not directly. There is no ai_generated=true parameter. Meta detects AI content through file metadata:
| Detection Method | How It Works |
|---|---|
| C2PA metadata | Embedded by Adobe Firefly, DALL-E 3, Microsoft tools |
| IPTC DigitalSourceType | Standard EXIF/XMP metadata field |
| Manual toggle | In-app only — not available via API |
Workaround: Embed the metadata yourself before uploading:
exiftool -IPTC:DigitalSourceType="trainedAlgorithmicMedia" image.jpgInstagram reads this metadata and auto-applies the "AI generated" label.
Q3: What happens with text-only posts?
| Platform | Text-Only Post Supported? | What Users See |
|---|---|---|
| ✅ Works natively | Standard text post (short text may get colored gradient background) | |
| ❌ Not supported | Every post requires media — API returns an error without image_url or video_url |
Workaround for Instagram: Render text as a styled image (1080x1080) using server-side SVG/image generation, then post as an image:
const svg = `
<svg width="1080" height="1080" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="#1a1a2e"/>
<text x="540" y="540" text-anchor="middle"
font-family="Inter" font-size="48" fill="#fff"
dominant-baseline="middle">${userText}</text>
</svg>`;
const image = await sharp(Buffer.from(svg)).png().toBuffer();Q4: Can I create collaborative posts via API?
✅ Partially. Meta added Collaboration API endpoints in 2025:
# Create a collab post (invite a collaborator)
POST /{ig-user-id}/media
image_url=https://cdn.example.com/photo.jpg
caption=Amazing collab! 🤝
collaborators=['other_ig_username']
access_token={PAGE_TOKEN}
# View incoming collab requests
GET /{ig-user-id}/collab_requests?fields=id,status,media
# Accept/Decline
POST /{collab-request-id}/accept
POST /{collab-request-id}/decline| Limitation | Detail |
|---|---|
| Max collaborators via API | 1 (app supports up to 3) |
| Other user must accept | Post shows on their profile only after acceptance |
| Supported content | Feed posts, Reels, Carousels — not Stories |
| Permissions needed | instagram_content_publish + instagram_manage_comments |
Q5: Can I edit or update a post after publishing?
| Platform | Edit Caption | Replace Media | Delete |
|---|---|---|---|
✅ POST /{post-id} with new message |
❌ Must delete & repost | ✅ DELETE /{post-id} |
|
| ❌ Not supported via API | ❌ Not supported | ✅ DELETE /{media-id} |
NOTE
Instagram captions cannot be edited via API. The user must edit in the Instagram app, or you must delete and repost.
Q6: Can I get analytics/insights for published posts?
✅ Yes, both platforms support it:
# Facebook Post Insights
GET /{post-id}/insights?metric=post_impressions,post_engaged_users
# Instagram Media Insights
GET /{media-id}/insights?metric=impressions,reach,likes,comments,shares,saved| Metric | ||
|---|---|---|
| Impressions | ✅ | ✅ |
| Reach | ✅ | ✅ |
| Likes | ✅ | ✅ |
| Comments count | ✅ | ✅ |
| Shares | ✅ | ✅ |
| Saves | ❌ | ✅ |
| Clicks | ✅ | ❌ |
| Video views | ✅ | ✅ (Reels) |
Q7: What happens when a token expires mid-schedule?
| Token Type | Behavior |
|---|---|
| Page Access Token (from long-lived user token) | Never expires — use this for all scheduled posts |
| User token expires | Doesn't matter if you already saved the Page token |
| User changes password / revokes app | Page token becomes invalid → error code 190 |
Strategy for LogicSpike:
Save never-expiring Page Token → Use for all scheduled posts
→ If error 190 occurs → Mark connection as "disconnected"
→ Notify user to reconnectQ8: Can I comment on posts or reply to comments via API?
✅ Yes:
# Comment on a Facebook post
POST /{post-id}/comments?message=Thanks!&access_token={TOKEN}
# Reply to an Instagram comment
POST /{comment-id}/replies?message=Thank you! 🙏&access_token={TOKEN}
# Read comments
GET /{post-id}/comments?fields=from,message,created_timeQ9: Can I add Instagram Stories to Highlights?
❌ No. The API can publish Stories but cannot add them to Highlights. Stories auto-expire after 24 hours. Highlights are an in-app-only feature.
Q10: What are the media URL requirements for Instagram?
Instagram servers download the media from your URL during container creation:
| Requirement | Detail |
|---|---|
| Publicly accessible | No auth headers, no quickly-expiring signed URLs |
| HTTPS | Must be https:// |
| Correct content-type | Server must return proper Content-Type header |
| Available for ~5 minutes | Instagram needs time to fetch and process |
For LogicSpike: Use R2/CDN public URLs. If using signed URLs, set expiry to at least 30 minutes.
Q11: Can I schedule posts natively through the API?
| Platform | Native Scheduling? | How |
|---|---|---|
| ✅ Yes | published=false + scheduled_publish_time={unix_timestamp} |
|
| ❌ No | Build your own scheduler (cron job that calls publish API at scheduled time) |
Q12: What are the daily publishing rate limits?
| Platform | Limit |
|---|---|
| Facebook Page posts | 25 posts per Page per hour |
| Facebook Reels | 30 per 24 hours |
| Instagram (all types) | 100 posts per account per 24 hours (carousels count as 1) |
| API calls total | ~200 calls per user per hour (varies by app tier) |
Q13: Can I post to a Facebook Group?
❌ Effectively no. Meta removed Group publishing API access in 2024. You can only post to Groups if your app is the group's own installed app — heavily restricted. Focus on Pages only.
Q14: Can I cross-post to both Facebook and Instagram in one API call?
❌ No. They are separate APIs with separate endpoints:
Facebook: POST /{page-id}/feed
Instagram: POST /{ig-user-id}/media → POST /{ig-user-id}/media_publishYour server must make separate API calls for each platform — this is what every scheduling tool does.
Q15: Can I tag people or locations in posts?
| Feature | ||
|---|---|---|
| Tag users in photo | ✅ tags param |
✅ user_tags param (x,y coordinates required) |
| Tag location | ✅ place param (Facebook Place ID) |
✅ location_id param (Facebook Place ID) |
| @mention in caption | ✅ Just type @pagename |
✅ Just type @username |
Instagram user tags require coordinates:
POST /{ig-user-id}/media
image_url=https://...
user_tags=[{"username":"friend","x":0.5,"y":0.5}]Q16: What media formats and sizes are supported?
| Spec | FB Photo | FB Video | IG Image | IG Video/Reel |
|---|---|---|---|---|
| Formats | JPG, PNG, GIF, BMP, TIFF | MP4, MOV | JPG, PNG | MP4, MOV |
| Max file size | 10 MB | 10 GB | 8 MB | 1 GB |
| Aspect ratio | Any | Any | 4:5 to 1.91:1 | 9:16 (Reels), 4:5 to 1.91:1 (Feed) |
| Resolution | Up to 2048px | Up to 4K | 1080px wide recommended | 1080x1920 (Reels) |
| Duration | — | Up to 240 min | — | 3–90 sec (Reels), up to 60 min (Feed) |
16. Related LogicSpike Docs
- Facebook App Setup Guide — Step-by-step developer portal setup
- Social Media OAuth Explained — How OAuth 2.0 works conceptually
- Content Engine Vision — LogicSpike's social automation architecture
- Content Engine API Spec — Internal API endpoints for scheduling