HR-ATS-Portal/docs/integrations/buffer/Buffer-API.postman_collecti...

1977 lines
90 KiB
JSON
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

{
"info": {
"_postman_id": "b0ffe4a1-2c3d-4e5f-8a9b-0c1d2e3f4a5b",
"name": "Buffer API (GraphQL) — HRMS",
"description": "Buffer's public GraphQL API — every operation the HRMS job-posting integration needs, plus the full read surface.\n\n**One endpoint for everything:** `POST https://api.buffer.com`. There are no REST paths; the operation is decided by the GraphQL document in the body.\n\n---\n\n### Setup\n1. Import `Buffer-API.postman_environment.json` and paste your key from `backend/.env` (`BUFFER_API`) into `buffer_token`.\n2. Run **01 · Get Organizations** → fills `{{org_id}}`.\n3. Run **02 · Get Channels** → fills `{{channel_id}}`.\n4. Everything else now works. Create/list requests fill `{{post_id}}` for you, so **Delete Post** always targets the last post you touched.\n\n### The three answers you were after\n| Need | Request | Field |\n|---|---|---|\n| `org_id` | 01 · Get Organizations | `account.organizations[].id` |\n| `channel_id` | 02 · Get Channels | `channels[].id` |\n| create post | 04 · Create Post · … | `createPost` → `PostActionSuccess.post.id` |\n| delete post | 04 · Delete Post | `deletePost` → `DeletePostSuccess.id` |\n| list posts | 03 · Get Posts | `posts.edges[].node` |\n\n### Gotchas that cost real time\n* Errors come back as **HTTP 200**. Check `errors[]` and `__typename`, not the status code.\n* Do **not** request `totalCount` on `posts` — API keys get `FORBIDDEN`.\n* The edit mutation is `editPost`, not `updatePost`.\n* `deletePost` returns `DeletePostSuccess`, *not* `PostActionSuccess`.\n* `schedulingType` is `automatic` | `notification` only — it is **not** the queue mode. The queue mode is `mode` (`addToQueue` | `shareNext` | `shareNow` | `customScheduled`).\n* `mode: customScheduled` requires `dueAt`; `mode: shareNow` publishes instantly.\n* `metadata.<service>.linkAttachment` and a non-empty `assets` array are mutually exclusive.\n* Sorting is only by `dueAt` or `createdAt` — there is no `sentAt` sort key.\n\n### Rate limits\nFree plan: **100 requests / 15 min**, 250 / day, 3000 / 30 days. A full Collection Runner pass over this collection is ~38 calls, so back-to-back runs will trip the 15-minute window (HTTP 429, `RATE_LIMIT_EXCEEDED`, `extensions.window: \"15m\"`). Every response carries `ratelimit` / `ratelimit-policy` headers — see **08 · Rate limit headers**.\n\n### Plan-gated operations\nThese are valid GraphQL but rejected on a Free account: LinkedIn `firstComment`, `needsApproval: true` (needs a posting policy), and Insights windows older than 31 days.\n\nDocs: https://developers.buffer.com/guides · Explorer: https://developers.buffer.com/explorer.html",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{buffer_token}}",
"type": "string"
}
]
},
"item": [
{
"name": "00 · Auth & Account",
"description": "Verify the API key works and inspect the authenticated account. The key is account-scoped: it can reach every organization and channel on the account.",
"item": [
{
"name": "Ping / Whoami",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"pm.test('Token is valid', function () {",
" pm.expect(res.data.account.id).to.be.a('string');",
"});",
"console.log('Rate limit:', pm.response.headers.get('ratelimit'));"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query Whoami {\\n account {\\n id\\n email\\n name\\n }\\n}\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Cheapest possible call. 200 + an account id means the token is valid.\n401 / `UNAUTHORIZED` in `errors[]` means the token is wrong or revoked."
},
"response": []
},
{
"name": "Get Account (full)",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetAccount {\\n account {\\n id\\n email\\n backupEmail\\n name\\n avatar\\n timezone\\n createdAt\\n organizations {\\n id\\n name\\n ownerEmail\\n channelCount\\n }\\n connectedApps {\\n clientId\\n name\\n category\\n scopes\\n createdAt\\n }\\n }\\n}\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Everything readable about the logged-in account in one call.\n\n`connectedApps[].clientId` is the OAuth **client id** — do not confuse it with `organizations[].id`."
},
"response": []
}
]
},
{
"name": "01 · Organizations → org_id",
"description": "**Run this first.** Almost every other query needs `organizationId`. The test script writes the first org id into the `org_id` collection variable automatically.",
"item": [
{
"name": "Get Organizations (captures org_id)",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"const orgs = res.data.account.organizations;",
"pm.test('At least one organization', () => pm.expect(orgs).to.have.length.above(0));",
"pm.collectionVariables.set('org_id', orgs[0].id);",
"console.log('org_id =', orgs[0].id, '|', orgs[0].name);"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetOrganizations {\\n account {\\n organizations {\\n id\\n name\\n ownerEmail\\n channelCount\\n }\\n }\\n}\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "**This is the org_id endpoint.**\n\n`account.organizations[].id` is the `organizationId` every other call wants.\nThe test script stores `organizations[0].id` in `{{org_id}}`."
},
"response": []
},
{
"name": "Get Organization Limits",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetOrganizationLimits {\\n account {\\n organizations {\\n id\\n name\\n channelCount\\n limits {\\n channels\\n members\\n scheduledPosts\\n ideas\\n tags\\n postTemplates\\n }\\n }\\n }\\n}\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Plan ceilings for the org (each field is the max, an `Int`) — compare `limits.channels` against `channelCount` before connecting another channel."
},
"response": []
}
]
},
{
"name": "02 · Channels → channel_id",
"description": "**Run `Get Channels` second.** `channel_id` is what `createPost` publishes to. The test script captures the first channel into `{{channel_id}}`.",
"item": [
{
"name": "Get Channels (captures channel_id)",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"const chans = res.data.channels;",
"pm.test('At least one channel', () => pm.expect(chans).to.have.length.above(0));",
"pm.collectionVariables.set('channel_id', chans[0].id);",
"console.log('channel_id =', chans[0].id, '|', chans[0].service, '|', chans[0].name);",
"chans.forEach(c => console.log(` ${c.id} ${c.service.padEnd(14)} ${c.name}`));"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetChannels($input: ChannelsInput!) {\\n channels(input: $input) {\\n id\\n name\\n displayName\\n service\\n type\\n serviceId\\n organizationId\\n avatar\\n externalLink\\n timezone\\n isDisconnected\\n isLocked\\n isQueuePaused\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"organizationId\": \"{{org_id}}\"\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "**This is the channel_id endpoint.**\n\nReturns every connected social profile in the organization. `id` → use as `channelId` in `createPost`. `service` is the network (`linkedin`, `twitter`, `instagram`, `facebook`, `tiktok`, `threads`, `youtube`, `pinterest`, `mastodon`, `bluesky`, `googlebusiness`, `startPage`).\n\nStore the id you actually want in `{{channel_id}}` — the script picks the first one."
},
"response": []
},
{
"name": "Get Channels (filtered)",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetFilteredChannels($input: ChannelsInput!) {\\n channels(input: $input) {\\n id\\n name\\n service\\n isLocked\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"organizationId\": \"{{org_id}}\",\n \"filter\": {\n \"isLocked\": false,\n \"product\": \"publish\"\n }\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "`filter.isLocked` — true/false/omit. `filter.product` — `publish` | `analyze` | `engage` | `comments` | `startPage` | `buffer`."
},
"response": []
},
{
"name": "Get Channel by ID",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetChannel($input: ChannelInput!) {\\n channel(input: $input) {\\n id\\n name\\n displayName\\n service\\n type\\n serviceId\\n organizationId\\n timezone\\n isDisconnected\\n isQueuePaused\\n allowedActions\\n scopes\\n postingSchedule {\\n day\\n times\\n paused\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"id\": \"{{channel_id}}\"\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Single channel, including its weekly posting schedule (the slots `mode: addToQueue` will fill)."
},
"response": []
},
{
"name": "Get Daily Posting Limits",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetDailyPostingLimits($input: DailyPostingLimitsInput!) {\\n dailyPostingLimits(input: $input) {\\n channelId\\n limit\\n scheduled\\n sent\\n isAtLimit\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"channelIds\": [\n \"{{channel_id}}\"\n ]\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Check before bulk-scheduling. `isAtLimit: true` means `createPost` will come back as `LimitReachedError`.\n\nOptional `input.date` (ISO 8601) checks a specific day."
},
"response": []
}
]
},
{
"name": "03 · Posts — Read",
"description": "Cursor-paginated. `first` = page size (2050 recommended), `after` = `pageInfo.endCursor` from the previous page. Cursors are opaque — never parse them.\n\n⚠ Do **not** add `totalCount` to the `posts` query — it returns `FORBIDDEN` on this API key.",
"item": [
{
"name": "Get Posts (paginated, captures post_id + cursor)",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"const conn = res.data.posts;",
"if (conn.edges.length) {",
" pm.collectionVariables.set('post_id', conn.edges[0].node.id);",
" console.log('post_id =', conn.edges[0].node.id);",
"}",
"pm.collectionVariables.set('posts_cursor', conn.pageInfo.endCursor || '');",
"console.log('hasNextPage =', conn.pageInfo.hasNextPage);",
"conn.edges.forEach(e => console.log(` ${e.node.id} ${e.node.status.padEnd(14)} ${(e.node.text || '').slice(0, 60).replace(/\\n/g, ' ')}`));"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetPosts($first: Int, $after: String, $input: PostsInput!) {\\n posts(first: $first, after: $after, input: $input) {\\n edges {\\n cursor\\n node {\\n id\\n text\\n status\\n shareMode\\n schedulingType\\n dueAt\\n sentAt\\n createdAt\\n updatedAt\\n channelId\\n channelService\\n externalLink\\n isCustomScheduled\\n via\\n }\\n }\\n pageInfo {\\n hasNextPage\\n endCursor\\n startCursor\\n hasPreviousPage\\n }\\n }\\n}\",\n \"variables\": {\n \"first\": 20,\n \"input\": {\n \"organizationId\": \"{{org_id}}\"\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "**This is the list-posts endpoint.**\n\nStores `edges[0].node.id` in `{{post_id}}` and `pageInfo.endCursor` in `{{posts_cursor}}` so *Get Posts — Next Page* and *Delete Post* just work."
},
"response": []
},
{
"name": "Get Posts — Next Page",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"pm.collectionVariables.set('posts_cursor', res.data.posts.pageInfo.endCursor || '');"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetPostsPage($first: Int, $after: String, $input: PostsInput!) {\\n posts(first: $first, after: $after, input: $input) {\\n edges {\\n cursor\\n node {\\n id\\n text\\n status\\n dueAt\\n channelId\\n }\\n }\\n pageInfo {\\n hasNextPage\\n endCursor\\n }\\n }\\n}\",\n \"variables\": {\n \"first\": 20,\n \"after\": \"{{posts_cursor}}\",\n \"input\": {\n \"organizationId\": \"{{org_id}}\"\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Run *Get Posts* first to populate `{{posts_cursor}}`. Re-run this request repeatedly — it rolls the cursor forward each time."
},
"response": []
},
{
"name": "Get Scheduled Posts (the queue)",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"const edges = res.data.posts.edges;",
"const queued = edges.filter(e => !e.node.isCustomScheduled);",
"if (queued.length) {",
" pm.collectionVariables.set('queued_post_id', queued[0].node.id);",
" console.log('queued_post_id =', queued[0].node.id);",
"}"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetScheduledPosts($first: Int, $input: PostsInput!) {\\n posts(first: $first, input: $input) {\\n edges {\\n node {\\n id\\n text\\n status\\n shareMode\\n dueAt\\n isCustomScheduled\\n channelId\\n channelService\\n allowedActions\\n }\\n }\\n pageInfo {\\n hasNextPage\\n endCursor\\n }\\n }\\n}\",\n \"variables\": {\n \"first\": 50,\n \"input\": {\n \"organizationId\": \"{{org_id}}\",\n \"filter\": {\n \"status\": [\n \"scheduled\"\n ],\n \"channelIds\": [\n \"{{channel_id}}\"\n ]\n },\n \"sort\": [\n {\n \"field\": \"dueAt\",\n \"direction\": \"asc\"\n }\n ]\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Everything waiting to go out, soonest first. `allowedActions` tells you whether `deletePost` / `editPost` is permitted on each one.\n\n`sort.field` (`PostSortableKey`) is only `dueAt` or `createdAt`; `direction` is `asc` or `desc`.\n\nCaptures the first queued post into `{{queued_post_id}}` for **Move Post in Queue**."
},
"response": []
},
{
"name": "Get Sent Posts",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"const edges = res.data.posts.edges;",
"if (edges.length) {",
" pm.collectionVariables.set('sent_post_id', edges[0].node.id);",
" console.log('sent_post_id =', edges[0].node.id);",
"}"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetSentPosts($first: Int, $input: PostsInput!) {\\n posts(first: $first, input: $input) {\\n edges {\\n node {\\n id\\n text\\n sentAt\\n externalLink\\n channelService\\n metricsUpdatedAt\\n metrics {\\n name\\n type\\n unit\\n value\\n }\\n }\\n }\\n pageInfo {\\n hasNextPage\\n endCursor\\n }\\n }\\n}\",\n \"variables\": {\n \"first\": 25,\n \"input\": {\n \"organizationId\": \"{{org_id}}\",\n \"filter\": {\n \"status\": [\n \"sent\"\n ],\n \"channelIds\": [\n \"{{channel_id}}\"\n ]\n },\n \"sort\": [\n {\n \"field\": \"dueAt\",\n \"direction\": \"desc\"\n }\n ]\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Published posts with their live engagement metrics and the permalink (`externalLink`) on the network. `metrics` is null until the post is sent.\n\nCaptures the newest sent post into `{{sent_post_id}}` for the **06 · Analytics** folder."
},
"response": []
},
{
"name": "Get Drafts",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetDrafts($first: Int, $input: PostsInput!) {\\n posts(first: $first, input: $input) {\\n edges {\\n node {\\n id\\n text\\n status\\n createdAt\\n channelId\\n }\\n }\\n pageInfo {\\n hasNextPage\\n endCursor\\n }\\n }\\n}\",\n \"variables\": {\n \"first\": 25,\n \"input\": {\n \"organizationId\": \"{{org_id}}\",\n \"filter\": {\n \"status\": [\n \"draft\",\n \"needs_approval\"\n ]\n }\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "`PostStatus` values: `draft`, `needs_approval`, `scheduled`, `sending`, `sent`, `error`."
},
"response": []
},
{
"name": "Get Failed Posts",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetFailedPosts($first: Int, $input: PostsInput!) {\\n posts(first: $first, input: $input) {\\n edges {\\n node {\\n id\\n text\\n status\\n dueAt\\n channelId\\n error {\\n message\\n }\\n }\\n }\\n pageInfo {\\n hasNextPage\\n endCursor\\n }\\n }\\n}\",\n \"variables\": {\n \"first\": 25,\n \"input\": {\n \"organizationId\": \"{{org_id}}\",\n \"filter\": {\n \"status\": [\n \"error\"\n ]\n }\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Posts the network rejected. `error.message` carries the reason (expired token, media rejected, duplicate content …)."
},
"response": []
},
{
"name": "Get Posts by Date Range",
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
"pm.collectionVariables.set('range_end', new Date().toISOString());",
"pm.collectionVariables.set('range_start', new Date(Date.now() - 30 * 864e5).toISOString());"
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetPostsByDate($first: Int, $input: PostsInput!) {\\n posts(first: $first, input: $input) {\\n edges {\\n node {\\n id\\n text\\n status\\n dueAt\\n sentAt\\n createdAt\\n }\\n }\\n pageInfo {\\n hasNextPage\\n endCursor\\n }\\n }\\n}\",\n \"variables\": {\n \"first\": 50,\n \"input\": {\n \"organizationId\": \"{{org_id}}\",\n \"filter\": {\n \"startDate\": \"{{range_start}}\",\n \"endDate\": \"{{range_end}}\"\n }\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "`startDate`/`endDate` match on `createdAt` **or** `dueAt`. The pre-request script sets a rolling 30-day window.\n\nFiner control: `dueAt` / `createdAt` accept a `DateTimeComparator` (`{ start, end }`), and `dueAtPresence` (`present` | `absent`) filters on whether a schedule exists at all. `absent` cannot be combined with a `dueAt` comparator."
},
"response": []
},
{
"name": "Get Post by ID",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetPost($input: PostInput!) {\\n post(input: $input) {\\n id\\n text\\n status\\n shareMode\\n schedulingType\\n dueAt\\n sentAt\\n createdAt\\n updatedAt\\n channelId\\n channelService\\n externalLink\\n isCustomScheduled\\n sharedNow\\n via\\n allowedActions\\n assets {\\n id\\n type\\n mimeType\\n source\\n thumbnail\\n }\\n tags {\\n id\\n name\\n }\\n author {\\n id\\n name\\n }\\n error {\\n message\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"id\": \"{{post_id}}\"\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Full single post. `allowedActions` includes `deletePost` / `updatePost` when those mutations will be accepted."
},
"response": []
}
]
},
{
"name": "04 · Posts — Create / Edit / Delete",
"description": "Every create/edit response is a **union**. Always select `__typename` plus `... on PostActionSuccess` and `... on MutationError` — an HTTP 200 with `__typename: \"InvalidInputError\"` is still a failure.\n\n`ShareMode`: `addToQueue` · `shareNext` · `shareNow` · `customScheduled`.\n`SchedulingType`: `automatic` (Buffer publishes) · `notification` (Buffer reminds you).\n\nEach create request stores the new id in `{{post_id}}`, so **Delete Post** at the bottom of this folder cleans up whatever you just made.",
"item": [
{
"name": "Create Post · Add to Queue",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"const out = res.data.createPost;",
"pm.test('createPost succeeded', function () {",
" pm.expect(out.__typename, out.message || '').to.eql('PostActionSuccess');",
"});",
"if (out.__typename === 'PostActionSuccess') {",
" pm.collectionVariables.set('post_id', out.post.id);",
" console.log('post_id =', out.post.id, '| status =', out.post.status);",
"}",
"if (out.__typename === 'PostActionSuccess') {",
" pm.collectionVariables.set('queued_post_id', out.post.id);",
"}"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"mutation CreatePost($input: CreatePostInput!) {\\n createPost(input: $input) {\\n __typename\\n ... on PostActionSuccess {\\n post {\\n id\\n text\\n status\\n shareMode\\n dueAt\\n createdAt\\n channelId\\n channelService\\n }\\n }\\n ... on MutationError {\\n message\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"channelId\": \"{{channel_id}}\",\n \"text\": \"Posted from the Buffer API collection.\",\n \"schedulingType\": \"automatic\",\n \"mode\": \"addToQueue\",\n \"assets\": []\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Drops the post into the next free slot of the channel's posting schedule. Buffer picks `dueAt` for you.\n\nThis is the mode the HRMS job-post flow uses by default.\n\nAlso stores the new id in `{{queued_post_id}}` so **Move Post in Queue** has a genuinely queued post to act on."
},
"response": []
},
{
"name": "Create Post · Draft (safe to test with)",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"const out = res.data.createPost;",
"pm.test('createPost succeeded', function () {",
" pm.expect(out.__typename, out.message || '').to.eql('PostActionSuccess');",
"});",
"if (out.__typename === 'PostActionSuccess') {",
" pm.collectionVariables.set('post_id', out.post.id);",
" console.log('post_id =', out.post.id, '| status =', out.post.status);",
"}"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"mutation CreatePost($input: CreatePostInput!) {\\n createPost(input: $input) {\\n __typename\\n ... on PostActionSuccess {\\n post {\\n id\\n text\\n status\\n shareMode\\n dueAt\\n createdAt\\n channelId\\n channelService\\n }\\n }\\n ... on MutationError {\\n message\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"channelId\": \"{{channel_id}}\",\n \"text\": \"Draft from the Buffer API collection — not published.\",\n \"schedulingType\": \"automatic\",\n \"mode\": \"addToQueue\",\n \"saveToDraft\": true,\n \"assets\": []\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "`saveToDraft: true` creates the post with `status: draft`. Nothing is published and daily posting limits are not consumed.\n\n**Use this one when smoke-testing** — then run *Delete Post* to remove it."
},
"response": []
},
{
"name": "Create Post · Custom Scheduled",
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
"pm.collectionVariables.set('due_at', new Date(Date.now() + 864e5).toISOString());"
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"const out = res.data.createPost;",
"pm.test('createPost succeeded', function () {",
" pm.expect(out.__typename, out.message || '').to.eql('PostActionSuccess');",
"});",
"if (out.__typename === 'PostActionSuccess') {",
" pm.collectionVariables.set('post_id', out.post.id);",
" console.log('post_id =', out.post.id, '| status =', out.post.status);",
"}"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"mutation CreatePost($input: CreatePostInput!) {\\n createPost(input: $input) {\\n __typename\\n ... on PostActionSuccess {\\n post {\\n id\\n text\\n status\\n shareMode\\n dueAt\\n createdAt\\n channelId\\n channelService\\n }\\n }\\n ... on MutationError {\\n message\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"channelId\": \"{{channel_id}}\",\n \"text\": \"Scheduled from the Buffer API collection.\",\n \"schedulingType\": \"automatic\",\n \"mode\": \"customScheduled\",\n \"dueAt\": \"{{due_at}}\",\n \"assets\": []\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "`mode: customScheduled` **requires** `dueAt` as an ISO 8601 UTC timestamp (`2026-08-06T09:00:00.000Z`). The pre-request script sets `{{due_at}}` to 24 hours from now."
},
"response": []
},
{
"name": "Create Post · Share Next (top of queue)",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"const out = res.data.createPost;",
"pm.test('createPost succeeded', function () {",
" pm.expect(out.__typename, out.message || '').to.eql('PostActionSuccess');",
"});",
"if (out.__typename === 'PostActionSuccess') {",
" pm.collectionVariables.set('post_id', out.post.id);",
" console.log('post_id =', out.post.id, '| status =', out.post.status);",
"}"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"mutation CreatePost($input: CreatePostInput!) {\\n createPost(input: $input) {\\n __typename\\n ... on PostActionSuccess {\\n post {\\n id\\n text\\n status\\n shareMode\\n dueAt\\n createdAt\\n channelId\\n channelService\\n }\\n }\\n ... on MutationError {\\n message\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"channelId\": \"{{channel_id}}\",\n \"text\": \"Jumping the queue, via the Buffer API collection.\",\n \"schedulingType\": \"automatic\",\n \"mode\": \"shareNext\",\n \"assets\": []\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Takes the *next* available slot, pushing everything else down."
},
"response": []
},
{
"name": "⚠️ Create Post · Share Now (PUBLISHES IMMEDIATELY)",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"const out = res.data.createPost;",
"pm.test('createPost succeeded', function () {",
" pm.expect(out.__typename, out.message || '').to.eql('PostActionSuccess');",
"});",
"if (out.__typename === 'PostActionSuccess') {",
" pm.collectionVariables.set('post_id', out.post.id);",
" console.log('post_id =', out.post.id, '| status =', out.post.status);",
"}"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"mutation CreatePost($input: CreatePostInput!) {\\n createPost(input: $input) {\\n __typename\\n ... on PostActionSuccess {\\n post {\\n id\\n text\\n status\\n shareMode\\n dueAt\\n createdAt\\n channelId\\n channelService\\n }\\n }\\n ... on MutationError {\\n message\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"channelId\": \"{{channel_id}}\",\n \"text\": \"Published immediately from the Buffer API collection.\",\n \"schedulingType\": \"automatic\",\n \"mode\": \"shareNow\",\n \"assets\": []\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "**This goes live on the real social account the moment you hit Send.** There is no undo — `deletePost` removes it from Buffer but does not always retract it from the network.\n\nUse *Create Post · Draft* for testing instead."
},
"response": []
},
{
"name": "Create Post · Needs Approval",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"const out = res.data.createPost;",
"pm.test('createPost succeeded', function () {",
" pm.expect(out.__typename, out.message || '').to.eql('PostActionSuccess');",
"});",
"if (out.__typename === 'PostActionSuccess') {",
" pm.collectionVariables.set('post_id', out.post.id);",
" console.log('post_id =', out.post.id, '| status =', out.post.status);",
"}"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"mutation CreatePost($input: CreatePostInput!) {\\n createPost(input: $input) {\\n __typename\\n ... on PostActionSuccess {\\n post {\\n id\\n text\\n status\\n shareMode\\n dueAt\\n createdAt\\n channelId\\n channelService\\n }\\n }\\n ... on MutationError {\\n message\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"channelId\": \"{{channel_id}}\",\n \"text\": \"Submitted for approval from the Buffer API collection.\",\n \"schedulingType\": \"automatic\",\n \"mode\": \"addToQueue\",\n \"needsApproval\": true,\n \"assets\": []\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "`needsApproval: true` parks the post at `status: needs_approval` instead of scheduling it.\n\n⚠ Only accepted when the channel's posting policy actually requires approval (Buffer → Settings → posting policy, paid plans). Otherwise you get `InvalidInputError: needsApproval is only valid when your posting policy on this channel requires approval`."
},
"response": []
},
{
"name": "Create Post · With Image",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"const out = res.data.createPost;",
"pm.test('createPost succeeded', function () {",
" pm.expect(out.__typename, out.message || '').to.eql('PostActionSuccess');",
"});",
"if (out.__typename === 'PostActionSuccess') {",
" pm.collectionVariables.set('post_id', out.post.id);",
" console.log('post_id =', out.post.id, '| status =', out.post.status);",
"}"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"mutation CreatePost($input: CreatePostInput!) {\\n createPost(input: $input) {\\n __typename\\n ... on PostActionSuccess {\\n post {\\n id\\n text\\n status\\n shareMode\\n dueAt\\n createdAt\\n channelId\\n channelService\\n }\\n }\\n ... on MutationError {\\n message\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"channelId\": \"{{channel_id}}\",\n \"text\": \"Image post from the Buffer API collection.\",\n \"schedulingType\": \"automatic\",\n \"mode\": \"addToQueue\",\n \"saveToDraft\": true,\n \"assets\": [\n {\n \"image\": {\n \"url\": \"https://picsum.photos/1200/630.jpg\",\n \"thumbnailUrl\": \"https://picsum.photos/1200/630.jpg\"\n }\n }\n ]\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "`assets` is an **ordered** list. Each entry is exactly one of `image` / `video` / `document` / `link`.\n\n* `image` → `{ url!, thumbnailUrl, metadata }`\n* `video` → `{ url!, thumbnailUrl, metadata }`\n* `document` → `{ url!, title!, thumbnailUrl! }`\n* `link` → `{ url!, title, description, thumbnailUrl }`\n\nURLs must be publicly reachable **and return the raw bytes** — Buffer fetches them server-side, so a page that redirects to a login or a CDN that blocks server-side fetches fails with `InvalidInputError: Image could not be read from its URL`. See the *Hosting Media* guide for Buffer's own upload endpoint.\n\nSet to `saveToDraft: true` here so you can run it safely."
},
"response": []
},
{
"name": "Create Post · LinkedIn (first comment + link attachment)",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"const out = res.data.createPost;",
"pm.test('createPost succeeded', function () {",
" pm.expect(out.__typename, out.message || '').to.eql('PostActionSuccess');",
"});",
"if (out.__typename === 'PostActionSuccess') {",
" pm.collectionVariables.set('post_id', out.post.id);",
" console.log('post_id =', out.post.id, '| status =', out.post.status);",
"}"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"mutation CreatePost($input: CreatePostInput!) {\\n createPost(input: $input) {\\n __typename\\n ... on PostActionSuccess {\\n post {\\n id\\n text\\n status\\n shareMode\\n dueAt\\n createdAt\\n channelId\\n channelService\\n }\\n }\\n ... on MutationError {\\n message\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"channelId\": \"{{channel_id}}\",\n \"text\": \"LinkedIn post from the Buffer API collection.\",\n \"schedulingType\": \"automatic\",\n \"mode\": \"addToQueue\",\n \"saveToDraft\": true,\n \"assets\": [],\n \"metadata\": {\n \"linkedin\": {\n \"linkAttachment\": {\n \"url\": \"https://example.com/careers\"\n }\n }\n }\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "`metadata` is keyed by network: `linkedin`, `twitter`, `instagram`, `facebook`, `tiktok`, `threads`, `youtube`, `pinterest`, `mastodon`, `bluesky`, `google`.\n\nLinkedIn accepts `firstComment`, `linkAttachment` (`{ url }` only — no title/description override), and `annotations` (@-mentions).\n\n⚠ `firstComment` is a **paid-plan feature** — on Free it comes back as `InvalidInputError: LinkedIn first comment requires a paid plan`. It is left out of the body below; add it back once the account is upgraded:\n```json\n\"linkedin\": { \"firstComment\": \"Full JD in the comments 👇\" }\n```\n\n⚠ `metadata.<service>.linkAttachment` and a non-empty `assets` array are **mutually exclusive** — sending both is an `InvalidInputError`."
},
"response": []
},
{
"name": "Edit Post",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"pm.test('editPost succeeded', function () {",
" pm.expect(res.data.editPost.__typename, res.data.editPost.message || '')",
" .to.eql('PostActionSuccess');",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"mutation EditPost($input: EditPostInput!) {\\n editPost(input: $input) {\\n __typename\\n ... on PostActionSuccess {\\n post {\\n id\\n text\\n status\\n dueAt\\n updatedAt\\n }\\n }\\n ... on MutationError {\\n message\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"id\": \"{{post_id}}\",\n \"text\": \"Edited via the Buffer API collection.\",\n \"schedulingType\": \"automatic\"\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "The mutation is `editPost` (not `updatePost`). `id` and `schedulingType` are required; every other field is optional and **omitting a field preserves its current value**.\n\nChange the schedule by sending `mode: \"customScheduled\"` together with a new `dueAt`."
},
"response": []
},
{
"name": "Move Post in Queue",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"pm.test('movePostInQueue succeeded', function () {",
" pm.expect(res.data.movePostInQueue.__typename,",
" res.data.movePostInQueue.message || '').to.eql('PostActionSuccess');",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"mutation MovePostInQueue($input: MovePostInQueueInput!) {\\n movePostInQueue(input: $input) {\\n __typename\\n ... on PostActionSuccess {\\n post {\\n id\\n dueAt\\n shareMode\\n }\\n }\\n ... on MutationError {\\n message\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"id\": \"{{queued_post_id}}\",\n \"position\": \"top\"\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "`position` is `top` or `bottom`.\n\n⚠ Only works on posts whose `shareMode` is `addToQueue`/`shareNext`. A draft or a `customScheduled` post gives `VoidMutationError: Only queued posts can be moved within the queue` — hence the separate `{{queued_post_id}}` variable, filled by *Get Scheduled Posts* or *Create Post · Add to Queue*."
},
"response": []
},
{
"name": "Delete Post",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"const out = res.data.deletePost;",
"pm.test('deletePost succeeded', function () {",
" pm.expect(out.__typename, out.message || '').to.eql('DeletePostSuccess');",
"});",
"if (out.__typename === 'DeletePostSuccess') {",
" console.log('deleted', out.id);",
" pm.collectionVariables.set('post_id', '');",
"}"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"mutation DeletePost($input: DeletePostInput!) {\\n deletePost(input: $input) {\\n __typename\\n ... on DeletePostSuccess {\\n id\\n }\\n ... on MutationError {\\n message\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"id\": \"{{post_id}}\"\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "**This is the delete endpoint.**\n\nTakes only the post id. The payload union is `DeletePostSuccess { id }` | `VoidMutationError { message }` — note it is *not* `PostActionSuccess`.\n\nDeleting a `sent` post removes it from Buffer; it does not necessarily retract it from the social network. Check `allowedActions` on the post for `deletePost` first."
},
"response": []
}
]
},
{
"name": "05 · Ideas",
"description": "Ideas live on the **organization**, not a channel — drafts that are not yet committed to a network.",
"item": [
{
"name": "Get Ideas",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"const edges = res.data.ideas.edges;",
"if (edges.length) pm.collectionVariables.set('idea_id', edges[0].node.id);"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetIdeas($first: Int, $after: String, $input: IdeasInput!) {\\n ideas(first: $first, after: $after, input: $input) {\\n edges {\\n cursor\\n node {\\n id\\n createdAt\\n content {\\n title\\n text\\n services\\n }\\n }\\n }\\n pageInfo {\\n hasNextPage\\n endCursor\\n }\\n }\\n}\",\n \"variables\": {\n \"first\": 20,\n \"input\": {\n \"organizationId\": \"{{org_id}}\"\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Cursor-paginated like posts. Optional `groupFilter` and `tagsFilter`."
},
"response": []
},
{
"name": "Create Idea",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"mutation CreateIdea($input: CreateIdeaInput!) {\\n createIdea(input: $input) {\\n __typename\\n ... on IdeaResponse {\\n refreshIdeas\\n idea {\\n id\\n organizationId\\n createdAt\\n content {\\n title\\n text\\n services\\n }\\n }\\n }\\n ... on Idea {\\n id\\n content {\\n title\\n text\\n }\\n }\\n ... on MutationError {\\n message\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"organizationId\": \"{{org_id}}\",\n \"content\": {\n \"title\": \"Idea from the Buffer API collection\",\n \"text\": \"Draft copy that is not tied to a channel yet.\",\n \"services\": [\n \"linkedin\"\n ]\n }\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "`content` accepts `title`, `text`, `services`, `media`, `tags`, `date`, `aiAssisted`.\n\nThe payload union is `IdeaResponse` | `Idea` | `InvalidInputError` | `UnauthorizedError` | `LimitReachedError` | `UnexpectedError` — this API returns `IdeaResponse`.\n\n⚠ There is no `deleteIdea` mutation, so anything you create here has to be removed from the Buffer UI."
},
"response": []
}
]
},
{
"name": "06 · Analytics",
"description": "Metrics only exist for `sent` posts. On the Free plan, Insights history is capped at the **last 31 days** — a wider window returns `BAD_USER_INPUT`.",
"item": [
{
"name": "Get Post Metrics",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetPostMetrics($input: PostInput!) {\\n post(input: $input) {\\n id\\n sentAt\\n externalLink\\n metricsUpdatedAt\\n metrics {\\n name\\n description\\n type\\n unit\\n value\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"id\": \"{{sent_post_id}}\"\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Run **03 · Get Sent Posts** first — it fills `{{sent_post_id}}`. (Pointing this at `{{post_id}}` right after a delete gives `BAD_USER_INPUT: Invalid PostId format`, because the variable is empty.)\n\n`metrics` is `null` until the post is sent. `type` is one of `impressions`, `reach`, `reactions`, `likes`, `comments`, `shares`, `reposts`, `quotes`, `clicks`, `saves`, `follows`, `views`, `viewers`, `totalTimeWatched`, `engagementRate`, `postCount`. `unit` is `count` or `percentage`."
},
"response": []
},
{
"name": "Get Aggregated Post Metrics (last 30 days)",
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
"pm.collectionVariables.set('metrics_end', new Date().toISOString());",
"pm.collectionVariables.set('metrics_start', new Date(Date.now() - 30 * 864e5).toISOString());"
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetAggregatedPostMetrics($input: AggregatedPostMetricsInput!) {\\n aggregatedPostMetrics(input: $input) {\\n metricsUpdatedAt\\n metrics {\\n name\\n type\\n unit\\n value\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"organizationId\": \"{{org_id}}\",\n \"channelIds\": [\n \"{{channel_id}}\"\n ],\n \"startDateTime\": \"{{metrics_start}}\",\n \"endDateTime\": \"{{metrics_end}}\"\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Totals across every sent post in the window. The pre-request script sets a 30-day range to stay inside the Free-plan 31-day cap."
},
"response": []
}
]
},
{
"name": "07 · HRMS job-post flow",
"description": "The exact calls `backend/job/job_post/plugins.py` makes, so you can reproduce a backend failure directly against Buffer.\n\n`.env` mapping: `BUFFER_API` → `{{buffer_token}}`, `BUFFER_API_URL` → `{{buffer_api_url}}`, `BUFFER_CHANNEL_ID` → `{{channel_id}}`.",
"item": [
{
"name": "1. list_buffer_channels — organizations",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"pm.collectionVariables.set('org_id', res.data.account.organizations[0].id);"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query { account { organizations { id name } } }\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "First half of `list_buffer_channels()` — mirrors the literal query string in `plugins.py`."
},
"response": []
},
{
"name": "2. list_buffer_channels — channels per org",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query GetChannels {\\n channels(input: { organizationId: \\\"{{org_id}}\\\" }) {\\n id\\n name\\n displayName\\n service\\n isQueuePaused\\n }\\n}\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Second half of `list_buffer_channels()`, exposed by the backend at `GET /job/buffer/channels`. Note this one inlines the org id rather than using GraphQL variables — same as the Python."
},
"response": []
},
{
"name": "3. create_buffer_post — rendered job ad",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"const out = res.data.createPost;",
"pm.test('createPost succeeded', function () {",
" pm.expect(out.__typename, out.message || '').to.eql('PostActionSuccess');",
"});",
"if (out.__typename === 'PostActionSuccess') {",
" pm.collectionVariables.set('post_id', out.post.id);",
" console.log('post_id =', out.post.id, '| status =', out.post.status);",
"}"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"mutation CreatePost($input: CreatePostInput!) {\\n createPost(input: $input) {\\n __typename\\n ... on PostActionSuccess {\\n post {\\n id\\n text\\n status\\n shareMode\\n dueAt\\n createdAt\\n channelId\\n channelService\\n }\\n }\\n ... on MutationError {\\n message\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"channelId\": \"{{channel_id}}\",\n \"text\": \"We're hiring: AI Engineer\\n\\nKarachi · Full-time\\n\\nExperience: 23 years\\n\\nRequirements:\\n• AWS\\n• FastAPI\\n• LangChain\\n\\nNice to have:\\n• Azure\\n\\nSalary: Anonymous\\n\\nInterested? Apply via our careers page or reply to this post.\\n\\n#AWS #FastAPI #LangChain\",\n \"schedulingType\": \"automatic\",\n \"mode\": \"addToQueue\",\n \"saveToDraft\": true,\n \"assets\": []\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "What `POST /job/post-job` ends up sending, using the output of `render_job_post()`. The backend supports `mode` of `addToQueue`, `shareNow`, or `customScheduled` (which then requires `due_at`).\n\nLinkedIn caps post text at 3000 characters — `render_job_post()` truncates to that.\n\n`saveToDraft: true` is added here so running it does not queue a real job ad; the backend does not send it."
},
"response": []
},
{
"name": "4. clean up — delete the post created above",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"mutation DeletePost($input: DeletePostInput!) {\\n deletePost(input: $input) {\\n __typename\\n ... on DeletePostSuccess {\\n id\\n }\\n ... on MutationError {\\n message\\n }\\n }\\n}\",\n \"variables\": {\n \"input\": {\n \"id\": \"{{post_id}}\"\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Removes whatever step 3 created."
},
"response": []
}
]
},
{
"name": "08 · Error shapes (reference)",
"description": "Run these to see each failure mode. Buffer returns **HTTP 200** for almost everything — you must inspect the body.\n\n* Non-recoverable → top-level `errors[]` with `extensions.code`: `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BAD_USER_INPUT`, `GRAPHQL_VALIDATION_FAILED`, `UNEXPECTED`, `RATE_LIMIT_EXCEEDED`.\n* Recoverable → `data.<mutation>.__typename` is a member of the error union (`InvalidInputError`, `LimitReachedError`, `NotFoundError`, `UnauthorizedError`, `RestProxyError`, `UnexpectedError`).",
"item": [
{
"name": "FORBIDDEN — totalCount on posts",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"console.log(JSON.stringify(res.errors, null, 2));",
"pm.test('Returns a GraphQL error (expected)', function () {",
" pm.expect(res.errors).to.be.an('array');",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query {\\n posts(first: 1, input: { organizationId: \\\"{{org_id}}\\\" }) {\\n totalCount\\n }\\n}\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "`totalCount` is in the schema but rejected for API-key auth. This is the most common cause of a `posts` query failing after copy-pasting from the schema reference — leave it out."
},
"response": []
},
{
"name": "NOT_FOUND — bad post id",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"console.log(JSON.stringify(res.errors, null, 2));",
"pm.test('Returns a GraphQL error (expected)', function () {",
" pm.expect(res.errors).to.be.an('array');",
"});"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query {\\n post(input: { id: \\\"000000000000000000000000\\\" }) {\\n id\\n }\\n}\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Expect `errors[0].extensions.code === 'NOT_FOUND'`."
},
"response": []
},
{
"name": "Rate limit headers",
"event": [
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
"const res = pm.response.json();",
"pm.test('HTTP 200', () => pm.response.to.have.status(200));",
"pm.test('No GraphQL errors', function () {",
" pm.expect(res.errors, JSON.stringify(res.errors)).to.be.undefined;",
"});",
"console.log('ratelimit :', pm.response.headers.get('ratelimit'));",
"console.log('ratelimit-policy:', pm.response.headers.get('ratelimit-policy'));"
]
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"query { account { id } }\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{buffer_api_url}}",
"host": [
"{{buffer_api_url}}"
]
},
"description": "Every response carries three rolling windows. Free plan: 100 / 15 min, 250 / day, 3000 / 30 days. `r` = remaining, `t` = seconds to reset. Exceeding one gives HTTP 429 + `Retry-After`."
},
"response": []
}
]
}
],
"variable": [
{
"key": "buffer_api_url",
"value": "https://api.buffer.com",
"type": "string"
},
{
"key": "buffer_token",
"value": "",
"type": "string"
},
{
"key": "org_id",
"value": "",
"type": "string"
},
{
"key": "channel_id",
"value": "",
"type": "string"
},
{
"key": "post_id",
"value": "",
"type": "string"
},
{
"key": "sent_post_id",
"value": "",
"type": "string"
},
{
"key": "queued_post_id",
"value": "",
"type": "string"
},
{
"key": "idea_id",
"value": "",
"type": "string"
},
{
"key": "posts_cursor",
"value": "",
"type": "string"
},
{
"key": "due_at",
"value": "",
"type": "string"
},
{
"key": "range_start",
"value": "",
"type": "string"
},
{
"key": "range_end",
"value": "",
"type": "string"
},
{
"key": "metrics_start",
"value": "",
"type": "string"
},
{
"key": "metrics_end",
"value": "",
"type": "string"
}
]
}