用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/JPeetz/buffer-api-skill --skill buffer-api命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
| name | buffer-api |
| description | Schedule, manage and analyze social media posts via the Buffer GraphQL API from any AI agent. |
| version | 1.0.0 |
| author | Joerg Peetz (@JPeetz) + Hermes Agent |
| license | MIT |
| metadata | {"hermes":{"tags":["buffer","social","scheduler","graphql","posting","scheduling","instagram","tiktok","linkedin","publishing"]},"agentskills":{"categories":["social-media","automation","marketing"]}} |
Use Buffer's GraphQL API to schedule, manage, and analyze social posts across Instagram, Threads, LinkedIn, X/Twitter, Facebook, Google Business, Mastodon, YouTube, Pinterest, and Bluesky — from any AI agent (Claude, Cursor, Codex, OpenClaw, Hermes, n8n).
references/media-and-metadata.md.../openclaw-imports/openclaw-seo-geo-optimization/... if needed; else fetch
https://developers.buffer.com/reference.html.https://mcp.buffer.com/mcp with header
Authorization: Bearer <API_KEY>. Prefer the raw GraphQL below when a script
exists or when you need the bytes (binary media) that MCP tends to not expose.Authorization: Bearer <API_KEY> on every request to
https://api.buffer.com (GraphQL, POST, Content-Type: application/json).references/media-and-metadata.md for hosting + verification.POST https://api.buffer.com
Authorization: Bearer <API_KEY>
Content-Type: application/json
Body: {"query": "<graphql>", "variables": {...}}
The API is GraphQL only. Use the Buffer API Explorer (https://developers.buffer.com/explorer.html) to exercise schema. Scalar DateTime is ISO 8601 UTC.
Find org + channel IDs — the two IDs you'll need for nearly everything.
query GetAccount {
account { id email organizations { id name } }
}
query GetChannels {
channels(input: { organizationId: "ORG_ID" }) { id name service type }
}
service = the platform (instagram, tiktok, linkedin, twitter/mastodon/youtube/pinterest/bluesky/facebook/gbp).
type = Page/Profile/Business/Group/Account.
mutation CreatePost {
createPost(input: {
text: "Hello from the Buffer API!"
channelId: "CHANNEL_ID"
schedulingType: automatic
mode: addToQueue # addToQueue or customScheduled
# dueAt: "2026-03-10T15:00:00.000Z" # required when mode=customScheduled
}) {
... on PostActionSuccess { post { id text dueAt } }
... on MutationError { message }
}
}
text, channelId, schedulingType: automatic. At least one of
mode (addToQueue / customScheduled) or dueAt.mode: addToQueue → next available slot from the channel's posting schedule.mode: customScheduled + dueAt → exact date/time (ISO 8601, UTC).PostActionSuccess AND MutationError (or your ... on { })
so errors surface, not silently vanish.assets is an ordered list; each entry carries exactly one of image, video,
document, or link. For an image post:
createPost(input: {
text: "Hello there, this is another one!"
channelId: "CHANNEL_ID" schedulingType: automatic mode: addToQueue
assets: [ { image: { url: "https://your-cdn.example.com/image.jpg" } } ]
}) { ... on PostCreatedSuccess { post { id assets { id mimeType } } } ... on CreateError { message } }
The url MUST be publicly reachable — Buffer fetches media from that URL. See
references/media-and-metadata.md (hosting + a curl verification you should run
before creating the post).
post(input: { id }), NOT a getPost field):query PostById { post(input: { id: "POST_ID" }) { id text status dueAt channelId } }
status values: scheduled → sent (successfully published) → error. Confirm
status == "sent" (the string is sent, not published).
query GetPosts {
posts(first: 20, after: "CURSOR", input: { organizationId: "ORG_ID"
filter: { status: [scheduled, sent] channelIds: ["CHANNEL_ID"] } }) {
edges { node { id text status dueAt } }
pageInfo { endCursor hasNextPage }
}
}
first + after are cursor-based; loop after until hasNextPage is false.
editPost mutation (same union shape).deletePost(input: { postId }) (returns DeletePostPayload union).movePostInQueue (experimental) — reorder queued posts.createIdea(input: { organizationId ideaGroupId text media }).ideas(input: { organizationId } ). Media attaches via url too
(image/video/gif document/link; video not exposed on public API).ideaGroups(input:).Once a post is sent, per-network performance is normalized on Post.metrics.
aggregatedPostMetrics (aggregate across channels/posts)dailyPostingLimits (per-channel daily caps)post(input: { id }) { metrics { ... } } for a single post.
Metrics are read-only and available for personal/workflow use.Supported services: instagram, threads, linkedin, twitter, facebook, google-business-profiles, mastodon, youtube, pinterest, bluesky.
Beyond the shared fields, metadata on createPost per network enables:
metadata.{network}.thread)You only provide the metadata for the network the channel belongs to. Read
references/media-and-metadata.md for the exact per-network shape.
... on <Error> fragment so mutation errors surface.ok=false on a retry as already-queued,
do NOT keep re-submitting.createPost returns PostActionPayload (union). Spread:
{ ... on PostCreatedSuccess { post {...} } ... on CreateError { message } }.post QUERY (not getPost): Buffer has NO getPost field; the arg is input: { id }.urllib.request throws
UnicodeEncodeError: 'latin-1' on a non-ASCII char (e.g. an em dash) in a JSON body —
use requests.post(..., json=...) for non-ASCII payloads. This is a real, repeated gotcha.env -u PYTHONPATH python3 on the Hermes box).api.bufferapp.com/1/) is RETIRED
(2027-02-01) and 401s "Public API tokens are not accepted for REST API access."
Do not use it.post(input:{id}) until status -> sent.metadata (post type) for owned-account scheduling, not the platform's
Direct Post API (Triggered-Use) — one shared API key across channels, no per-platform
token juggling.https://api.buffer.com. Legacy
REST is retired. api.bufferapp.com/1/ is dead.post(input:{ id }), not getPost. A query field named getPost
does NOT exist. Use post(input: { id: "..." }) { id status }. Poll confirms both
creation and sending->sent.status == "sent" — the string is sent, not published, in Buffer GraphQL.assets:[{image:{url}}]. No
raw blob upload mutation. Verify with a HEAD/GET (200) before creating.requests or any JSON-safe HTTP client, not
urllib (latin-1 crash).ok=false as already-queued; don't retry.CreateError / LimitReachedError — read the
message, adjust cadence, never blind-retry-loop.🧪 endpoints
(post templates, updatePostTemplate) are preview-only.ID (verified live). Buffer params like
organizationId/channelId/postId use DISTINCT custom scalars
(OrganizationId!, ChannelId!, PostId!). Declaring a query variable as
ID! where the field expects OrganizationId! fails GraphQL validation. Either
inline the ID as a string literal or type the variable with the exact scalar.references/api-reference.md — full GraphQL schema: all queries/mutations, type
fields, unions (account, channels, posts, ideas, metrics, post templates).references/media-and-metadata.md — public media hosting + verification, per-network
metadata shapes (threads/bluesky/mastodon threads; LI/FB/IG first comment; IG post
type/user tags; Pinterest board), scheduling, ideas.references/graphql-helpers.md — ready-to-run queries and mutation templates.scripts/buffer_graphql.py — a small, dependency-light Python helper to POST JSON
GraphQL safely (handles non-ASCII, unions, pagination), so you don't re-render a
fragment stack.scripts/buffer_graphql.py --query "{ account { id organizations { id } } }"
→ expect {"account":{...}} (proves auth + endpoint).post(input:{ id }) and confirm status:
scheduled -> sent.Last verified: 2026-08-21 against https://developers.buffer.com (guide + reference).