Buffer API — Content Operations
Purpose
Create, schedule, edit, and analyze social media content through Buffer's GraphQL API at https://api.buffer.com.
Inputs to request
- What to do: create, schedule, draft, edit, delete, list, or analyze a post — or save/list ideas.
- Channel ID(s) to target (or ask the user to run the "get channels" query first).
- Post content: text, and optionally image/video URLs or thread structure.
- Scheduling intent: add to queue, schedule at a specific time, publish now, or save as draft.
- For analytics: which post ID(s) and which metrics matter (impressions, reactions, comments, etc.).
Operations map
| Goal | API call |
|---|---|
| Create / schedule a post | createPost mutation |
| Save a post as draft | createPost with saveToDraft: true |
| Edit an existing post | editPost mutation |
| Delete a post | deletePost mutation |
| List scheduled / sent posts | posts query with filter: { status: [scheduled] } |
| Get a single post | post query by ID |
| Read post metrics | post { metrics } or aggregatedPostMetrics query |
| Save a content idea | createIdea mutation |
| Find channel IDs | channels query by organization ID |
| Find organization ID | account { organizations { id } } query |
Auth setup (one-time)
All requests need Authorization: Bearer $BUFFER_API_KEY and Content-Type: application/json.
For personal scripts and automations: use an API key from https://publish.buffer.com/settings/api.
For multi-user apps: use OAuth 2.0 with PKCE — authorize at https://auth.buffer.com/auth, exchange at https://auth.buffer.com/token. Refresh tokens are single-use; always save the new one immediately after refresh.
Workflow
-
Get your organization ID (first time only):
graphqlquery { account { organizations { id name } } } -
Get channel IDs for your target platforms:
graphqlquery GetChannels($orgId: String!) { channels(input: { organizationId: $orgId }) { id name service } } -
Create or schedule the post using the relevant example below.
-
Check the response — the mutation returns a union type.
PostActionSuccessmeans it worked;MutationErrorcarries the reason it failed. GraphQL always responds with HTTP 200, so always inspect the response body. -
Edit or delete if needed using the post
idreturned in step 3. -
Pull analytics after the post publishes (metrics are refreshed daily; allow up to 24 hours after publish).
Examples
Create a text post (add to queue)
graphqlmutation CreatePost($input: CreatePostInput!) { createPost(input: $input) { ... on PostActionSuccess { post { id text status dueAt } } ... on MutationError { message } } }
json{ "input": { "text": "Your post content here", "channelId": "$CHANNEL_ID", "schedulingType": "automatic", "mode": "addToQueue" } }
Schedule at a specific time
json{ "input": { "text": "Your post content here", "channelId": "$CHANNEL_ID", "schedulingType": "automatic", "mode": "customScheduled", "dueAt": "2026-07-01T14:00:00.000Z" } }
Save as draft
json{ "input": { "text": "Draft content here", "channelId": "$CHANNEL_ID", "schedulingType": "automatic", "mode": "addToQueue", "saveToDraft": true } }
Post with image
json{ "input": { "text": "Your caption here", "channelId": "$CHANNEL_ID", "schedulingType": "automatic", "mode": "addToQueue", "assets": [{ "image": { "url": "https://your-public-image-url.jpg" } }] } }
Image URL must be publicly accessible. Each asset entry specifies exactly one type: image, video, document, or link.
Edit an existing post
graphqlmutation EditPost($input: EditPostInput!) { editPost(input: $input) { ... on PostActionSuccess { post { id text status dueAt } } ... on MutationError { message } } }
json{ "input": { "id": "$POST_ID", "text": "Updated content here" } }
Delete a post
graphqlmutation DeletePost { deletePost(input: { id: "$POST_ID" }) { ... on PostActionSuccess { post { id } } ... on MutationError { message } } }
List scheduled posts
graphqlquery GetScheduledPosts($orgId: String!) { posts(input: { organizationId: $orgId, filter: { status: [scheduled] }, sort: [{ field: dueAt, direction: asc }] }) { edges { node { id text dueAt channelId } } pageInfo { hasNextPage endCursor } } }
For more pages, add after: "$endCursor" to input. Page size: 20–50 items.
Get post analytics
graphqlquery GetPostMetrics { post(input: { id: "$POST_ID" }) { id text metricsUpdatedAt metrics { type name value unit } } }
Available metric types (varies by network): reactions, reposts, comments, shares, impressions, reach, views, saves, follows, likes. Metrics appear up to ~24 hours after publish.
Save an idea
graphqlmutation CreateIdea($input: CreateIdeaInput!) { createIdea(input: $input) { ... on MutationError { message } } }
json{ "input": { "organizationId": "$ORG_ID", "content": { "title": "Optional title", "text": "Idea content here" } } }
Ideas are org-level (not tied to a channel). Promote to a post by using the idea's text in createPost.
Rate limits
Buffer enforces three time windows. On HTTP 429, read retryAfter (seconds) from the response body.
| Plan | 15-min | 24-hr | 30-day |
|---|---|---|---|
| Free | 100 | 100 | 3,000 |
| Essentials | 100 | 250 | 7,500 |
| Team | 100 | 500 | 15,000 |
Troubleshooting
- No
postfield in mutation response →MutationErrorfired; logdata.<mutationName>.message. UNAUTHORIZED→ checkAuthorization: Bearer $BUFFER_API_KEYheader is present and token is valid.FORBIDDEN→ token lacks the right scope (e.g.,posts:writeneeded for create/edit/delete).- Metrics missing → post published less than 24 hours ago; check
metricsUpdatedAt. - Image not attaching → URL must be publicly accessible; see the Hosting Media guide.
Quality bar
- Always use GraphQL variables — never interpolate user content into query strings.
- Include
... on MutationError { message }in every mutation. - Never expose real tokens or channel IDs in examples.
- Metrics API is preview-only and available for personal API keys only (not OAuth apps).

