{"openapi":"3.0.3","info":{"title":"BrightBean API","version":"1","description":"Post to your BrightBean channels from scripts, Zapier, n8n or your own agent.\n\n**Using an AI agent?** Point it at [the agent guide](https://github.com/brightbeanxyz/brightbean-platform-agent): its `SKILL.md` teaches this API and the MCP server, and `npx skills@^1 add brightbeanxyz/brightbean-platform-agent` installs it.\n\n## Getting started\nEvery path below is under `https://api-platform.brightbean.xyz`. Check your key with:\n```\ncurl https://api-platform.brightbean.xyz/api/v1/me/ \\\n  -H \"Authorization: Bearer <key>\" \\\n  -H \"User-Agent: my-agent/1.0\"\n```\nBodies are JSON. Times are ISO 8601 with an offset (`2026-10-06T09:00:00+02:00`). Ids are UUIDs. A list path works with or without its trailing slash (`/api/v1/posts/` or `/api/v1/posts`).\n\n**Send a User-Agent naming your client.** The API sits behind Cloudflare, which blocks Python's built-in default (`Python-urllib/3.x`) with a plain-text `403 error code: 1010` before the request reaches BrightBean. Any value of your own gets through. `requests`, `httpx`, curl, Node, Go and Java clients already send one that does.\n\n## Keys\nCreate a key in BrightBean under Settings → Workspace → API & MCP. Send it on every request as `Authorization: Bearer <key>`. A key belongs to one workspace, may use only the channels picked for it, and can do only what its permissions allow AND what the person who made it can still do. Revoking a key stops it within 60 seconds.\n\nPermissions: `read` (Read channels and posts), `create_posts` (Create drafts and send for approval), `upload_media` (Upload media), `view_analytics` (Read analytics), `publish_directly` (Publish and schedule without approval). Without `publish_directly`, or when the workspace requires approval for everyone, a post you ask to schedule or publish goes to Review in BrightBean, labelled with your key, and is carried out once someone approves it. The `routed` field says which happened.\n\n## Errors\nEvery error from the API is `{ \"error\": \"<code>\", \"detail\": \"<what went wrong>\" }`:\n- 401: missing, invalid, expired or revoked key.\n- 403: `missing permission: <permission>`, or a channel this key may not post to.\n- 404: not found, including any post on a channel this key can't see.\n- 409: the post is in the wrong state (editing one waiting for approval: withdraw it first), or a request with the same Idempotency-Key is still running.\n- 422: the content won't work: over a channel's limit, a first comment where a channel can't take one, a time without an offset or in the past, a channel's daily cap for that time (`available_at` says when it fits).\n- 405: wrong method for that path (`Allow` says which).\n- 413: the body is too large.\n- 429: too many requests (`tier`, `retry_after`, and a Retry-After header), or a channel's daily cap when publishing now.\n- 503: a temporary failure while handling an upload. Retry the same request.\n\nA 403 with a plain-text body `error code: 1010` comes from Cloudflare, not the API: send your own User-Agent (see Getting started).\n\nA create saves the draft first, then sends it on when it has a `delivery`. An error after the save carries the post's id as `post_id`. After a 4xx (media a channel requires, its daily cap) it is still a draft: fix it with `PATCH` and send it with `/submit`. After a 5xx, `GET` it first: it may have gone ahead. A 4xx without `post_id` saved nothing.\n\n## Limits\nPer key, per minute: 120 writes and 300 reads. Per workspace: 1000 writes a minute. Windows are 60 seconds and counted per Cloudflare location.\n\n## Retries\nSend an `Idempotency-Key` header (at most 128 characters; an empty one counts as none) or `idempotencyKey` in the body on the writes that take one: `POST /api/v1/posts/`, `POST /api/v1/posts/{id}/duplicate`, `POST /api/v1/posts/{id}/submit`, `POST /api/v1/posts/{id}/schedule`, `POST /api/v1/posts/{id}/publish`, `POST /api/v1/posts/{id}/cancel`, `POST /api/v1/media/uploads`. Repeating a request with the same key within 24 hours never repeats the work; the same key with a different body is a 422. The other writes ignore it: finishing an upload is safe to repeat anyway.\n\n## Uploading media\n1. `POST /api/v1/media/uploads` with the file's type and size. The answer holds URLs for the first parts (up to 8) and `partCount`.\n2. `PUT` each part's bytes (`partSize` each, the last one smaller) to its URL and keep the ETag header each answer returns.\n3. For a file with more parts, `POST /api/v1/media/uploads/{id}/parts` for up to 16 more URLs at a time. A URL expires, so ask for them as you go.\n4. `POST /api/v1/media/uploads/{id}/complete` with every part's number and ETag. Use the returned id in `mediaAssetIds`.\n\n## Polling for changes\n`GET /api/v1/posts/`, `GET /api/v1/posts/{id}` return an ETag. Send it back as `If-None-Match`: when nothing changed the answer is usually `304` and costs almost nothing (sometimes it is a `200` with a new ETag; treat that as a normal answer). No other route sends one. Or pass the previous response's `serverTime` as `updatedSince`. Changes can take up to a minute to show. Deleted posts are not reported by `updatedSince`; they answer 404.\n\n## Activity\nEvery call is logged under the key's Activity in Settings, without request bodies. Activity and \"last used\" can take up to 20 minutes to appear.\n\n## MCP\nThe MCP server is `https://api-platform.brightbean.xyz/api/v1/mcp` (Streamable HTTP, `POST`). It has the same permissions, limits and Activity log as REST.\n- **With an API key:** send it as `Authorization: Bearer <key>`, e.g. `claude mcp add --transport http brightbean https://api-platform.brightbean.xyz/api/v1/mcp --header \"Authorization: Bearer <key>\"`.\n- **With OAuth** (claude.ai, Claude Desktop): add the URL as a custom connector. The client finds everything from the `401`: discovery is at `/.well-known/oauth-protected-resource/api/v1/mcp`, and the endpoints are `/oauth/authorize/`, `/oauth/token/`, `/oauth/register` (dynamic client registration) and `/oauth/revoke_token/`. PKCE (`S256`) is required. The person approving picks the workspace, channels and permissions."},"servers":[{"url":"https://api-platform.brightbean.xyz"}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"An API key (`bb_live_…`) from Settings → Workspace → API & MCP."}}},"security":[{"apiKey":[]}],"paths":{"/api/v1/me/":{"get":{"operationId":"me","summary":"Who this key is: workspace, permissions and channels","x-permission":"read","parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"workspace":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","nullable":true}},"required":["id","name"],"additionalProperties":false},"credential":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["key","oauth"]},"name":{"type":"string"},"expiresAt":{"type":"string","nullable":true}},"required":["id","kind","name","expiresAt"],"additionalProperties":false},"permissions":{"type":"array","items":{"type":"string","enum":["read","create_posts","upload_media","view_analytics","publish_directly"]}},"channels":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"platform":{"type":"string","enum":["facebook","instagram","threads","linkedin","tiktok","youtube","pinterest","bluesky","mastodon","google_business","devto"]},"handle":{"type":"string"}},"required":["id","platform","handle"],"additionalProperties":false}}},"required":["workspace","credential","permissions","channels"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}},"/api/v1/accounts/":{"get":{"operationId":"account_list","summary":"The channels this key may post to, with what each can take","x-permission":"read","parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"platform":{"type":"string","enum":["facebook","instagram","threads","linkedin","tiktok","youtube","pinterest","bluesky","mastodon","google_business","devto"]},"handle":{"type":"string"},"avatarUrl":{"type":"string","nullable":true},"status":{"type":"string"},"needsReconnect":{"type":"boolean"},"capabilities":{"type":"object","properties":{"charLimit":{"type":"number"},"title":{"type":"object","properties":{"supported":{"type":"boolean"},"required":{"type":"boolean"},"maxChars":{"type":"number","nullable":true}},"required":["supported","required","maxChars"],"additionalProperties":false},"firstComment":{"type":"boolean"},"link":{"type":"boolean"},"boardRequired":{"type":"boolean"},"mediaRequired":{"type":"string","enum":["video","visual"],"nullable":true,"description":"What a post to this channel must carry: `video` (YouTube), `visual` an image or a video (Instagram, Pinterest, TikTok), or null when text alone will do. A post without it is refused before it is scheduled."},"optionsKey":{"type":"string","nullable":true,"description":"The `perAccount[id].options` entry this channel takes."}},"required":["charLimit","title","firstComment","link","boardRequired","mediaRequired","optionsKey"],"additionalProperties":false},"optionsSchema":{"nullable":true,"description":"JSON Schema of this channel's options, or null."},"queues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"categoryId":{"type":"string","nullable":true,"description":"Posts of this content category go to this queue unless `queueIds` says otherwise."}},"required":["id","name","categoryId"],"additionalProperties":false},"description":"The channel's named queues, each with its own posting times. Its Main queue is not listed: pass `null` in `queueIds` for it."}},"required":["id","platform","handle","avatarUrl","status","needsReconnect","capabilities","queues"],"additionalProperties":false}}},"required":["data"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}},"/api/v1/accounts/{id}/boards":{"get":{"operationId":"account_boards","summary":"A Pinterest channel's boards (for `perAccount[id].boardId`)","x-permission":"read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Pass as `perAccount[id].boardId`."},"name":{"type":"string"},"privacy":{"type":"string","description":"As Pinterest reports it."},"pinCount":{"type":"integer"}},"required":["id","name"],"additionalProperties":false},"description":"Boards with the most pins first."}},"required":["data"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}},"/api/v1/accounts/{id}/tiktok-creator-info":{"get":{"operationId":"account_tiktok_creator_info","summary":"What a TikTok channel allows now: privacy levels, interactions, longest video","x-permission":"read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"username":{"type":"string","description":"The @handle."},"nickname":{"type":"string"},"avatarUrl":{"type":"string"},"privacyLevelOptions":{"type":"array","items":{"type":"string","enum":["PUBLIC_TO_EVERYONE","MUTUAL_FOLLOW_FRIENDS","FOLLOWER_OF_CREATOR","SELF_ONLY"]},"description":"Set `perAccount[id].options.tiktok.privacyLevel` to one of these."},"commentDisabled":{"type":"boolean"},"duetDisabled":{"type":"boolean","description":"Duet applies to video posts only."},"stitchDisabled":{"type":"boolean","description":"Stitch applies to video posts only."},"maxVideoPostDurationSec":{"type":"number","nullable":true,"description":"The longest video this creator may post; null when TikTok didn't say."},"canPost":{"type":"boolean","description":"False when TikTok refuses posts from this creator right now."},"cannotPostReason":{"type":"string"}},"required":["username","nickname","privacyLevelOptions","commentDisabled","duetDisabled","stitchDisabled","maxVideoPostDurationSec","canPost"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}},"/api/v1/accounts/{id}/youtube-categories":{"get":{"operationId":"account_youtube_categories","summary":"A YouTube channel's video categories","x-permission":"read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"regionCode":{"type":"string","description":"The ISO 3166-1 alpha-2 region the list is for."},"categories":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Pass as `options.youtube.categoryId`."},"title":{"type":"string"}},"required":["id","title"],"additionalProperties":false},"description":"Only categories an upload may be filed under."}},"required":["regionCode","categories"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}},"/api/v1/analytics/accounts/{id}":{"get":{"operationId":"account_analytics","summary":"A channel's analytics snapshots (refreshed hourly)","x-permission":"view_analytics","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"windowDays","in":"query","required":false,"schema":{"type":"integer","minimum":30,"maximum":30,"description":"Snapshots are collected for a 30-day window only, so this takes 30 (the default) and nothing else."},"description":"Snapshots are collected for a 30-day window only, so this takes 30 (the default) and nothing else."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"capturedAt":{"type":"string"},"windowDays":{"type":"integer"},"metrics":{"type":"object","additionalProperties":{"type":"number"},"description":"Metric name → value. A metric the network doesn't report is absent, never 0."}},"required":["capturedAt","windowDays","metrics"],"additionalProperties":false},"description":"Snapshots, newest first."}},"required":["data"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}},"/api/v1/posts/":{"get":{"operationId":"post_list","summary":"Posts on this key's channels, newest first","description":"Poll cheaply: send the previous response's ETag as If-None-Match (304 when nothing changed), or pass `updatedSince` = the previous `serverTime`. Changes can take up to a minute to show.","x-permission":"read","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","description":"Comma-separated statuses: draft, pending_approval, approved, changes_requested, rejected, queued, scheduled, publishing, published, partially_published, failed, on_hold."},"description":"Comma-separated statuses: draft, pending_approval, approved, changes_requested, rejected, queued, scheduled, publishing, published, partially_published, failed, on_hold."},{"name":"accountId","in":"query","required":false,"schema":{"type":"string","format":"uuid","description":"Only posts on this channel."},"description":"Only posts on this channel."},{"name":"updatedSince","in":"query","required":false,"schema":{"type":"string","format":"date-time","description":"Only posts changed after this time: pass the previous response's `serverTime`. Changes can take up to a minute to show."},"description":"Only posts changed after this time: pass the previous response's `serverTime`. Changes can take up to a minute to show."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","format":"uuid","description":"The previous page's `nextCursor`."},"description":"The previous page's `nextCursor`."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"If-None-Match","in":"header","required":false,"description":"The ETag of an earlier answer: 304 when nothing changed since.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Send it back as If-None-Match to ask whether anything changed.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["draft","pending_approval","approved","changes_requested","rejected","queued","scheduled","publishing","published","partially_published","failed","on_hold"]},"caption":{"type":"string"},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"linkUrl":{"type":"string","nullable":true},"mediaAssetIds":{"type":"array","items":{"type":"string"}},"scheduledAt":{"type":"string","nullable":true},"proposedPublishAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"attentionResolvedAt":{"type":"string","nullable":true,"description":"When a `partially_published` post was marked as dealt with (its failed channels posted another way, or a duplicate of it went out); null otherwise."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"createdVia":{"type":"object","properties":{"credentialId":{"type":"string"},"name":{"type":"string","nullable":true}},"required":["credentialId","name"],"additionalProperties":false,"nullable":true},"channels":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"platform":{"type":"string","enum":["facebook","instagram","threads","linkedin","tiktok","youtube","pinterest","bluesky","mastodon","google_business","devto"]},"handle":{"type":"string","nullable":true},"status":{"type":"string"},"scheduledAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"url":{"type":"string","nullable":true,"description":"The live post, once published."},"error":{"type":"string","nullable":true},"warnings":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"queuePosition":{"type":"number","nullable":true,"description":"Order within its queue, while queued."},"queueId":{"type":"string","nullable":true,"description":"The named queue it waits in, while queued; null is the channel's Main queue."},"caption":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"boardId":{"type":"string","nullable":true},"options":{"nullable":true}},"required":["accountId","platform","handle","status","scheduledAt","publishedAt","url","error","warnings","queuePosition","queueId","caption","title","firstComment","boardId"],"additionalProperties":false}}},"required":["id","status","caption","title","firstComment","linkUrl","mediaAssetIds","scheduledAt","proposedPublishAt","publishedAt","attentionResolvedAt","createdAt","updatedAt","createdVia","channels"],"additionalProperties":false}},"nextCursor":{"type":"string","nullable":true},"serverTime":{"type":"string","description":"Pass as `updatedSince` next time."}},"required":["data","nextCursor","serverTime"],"additionalProperties":false}}}},"304":{"description":"Nothing changed since the ETag sent as If-None-Match. No body.","headers":{"ETag":{"description":"Send it back as If-None-Match to ask whether anything changed.","schema":{"type":"string"}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}},"post":{"operationId":"post_create","summary":"Create a post, and optionally schedule, queue or publish it","description":"The draft is saved first, then sent on when there is a `delivery`. An error after the save carries the post's id as `post_id`: after a 4xx (media a channel requires, its daily cap, TikTok without an image or video) it is still a draft to fix and `/submit`. The Idempotency-Key covers the draft: a retry under the same key reuses it.","x-permission":"create_posts","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Retrying with the same key within 24 hours never repeats the work. Same as `idempotencyKey` in the body. An empty value counts as none.","schema":{"type":"string","maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"accounts":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":1,"maxItems":20,"description":"The channels to post to. Every one must be allowed for this key."},"caption":{"type":"string","description":"The post text."},"title":{"type":"string","description":"A title, for channels that take one."},"firstComment":{"type":"string","description":"Posted as the first comment. Refused (422) for a channel that can't take one."},"linkUrl":{"type":"string","maxLength":2048,"description":"Where the post points (a Pinterest pin's link)."},"perAccount":{"type":"object","additionalProperties":{"type":"object","properties":{"caption":{"type":"string"},"title":{"type":"string"},"firstComment":{"type":"string"},"boardId":{"type":"string","maxLength":128},"options":{"type":"object","properties":{"tiktok":{"type":"object","properties":{"privacyLevel":{"type":"string","enum":["PUBLIC_TO_EVERYONE","MUTUAL_FOLLOW_FRIENDS","FOLLOWER_OF_CREATOR","SELF_ONLY"]},"allowComment":{"type":"boolean"},"allowDuet":{"type":"boolean"},"allowStitch":{"type":"boolean"},"disclose":{"type":"boolean"},"brandOrganic":{"type":"boolean"},"brandContent":{"type":"boolean"},"aiGenerated":{"type":"boolean"},"coverMs":{"type":"integer","minimum":0},"autoAddMusic":{"type":"boolean"}},"additionalProperties":false},"devto":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string","maxLength":100},"maxItems":20},"canonicalUrl":{"type":"string","maxLength":2048}},"additionalProperties":false},"youtube":{"type":"object","properties":{"privacyStatus":{"type":"string","enum":["public","unlisted","private"]},"madeForKids":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string","minLength":1,"maxLength":500},"maxItems":500},"categoryId":{"type":"string","pattern":"^\\d{1,4}$"},"thumbnail":{"anyOf":[{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"}},"required":["assetId"],"additionalProperties":false},{"type":"object","properties":{"coverFrame":{"type":"boolean","enum":[true]}},"required":["coverFrame"],"additionalProperties":false}]}},"additionalProperties":false},"facebook":{"type":"object","properties":{"publishAs":{"type":"string","enum":["video","reel"]}},"additionalProperties":false}},"additionalProperties":false,"nullable":true}},"additionalProperties":false},"description":"Per-channel overrides, keyed by channel id."},"mediaAssetIds":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":35,"description":"Uploaded media, in display order."},"proposedPublishAt":{"type":"string","format":"date-time","nullable":true,"description":"A proposed publish time for the draft (ISO 8601 with an offset). A note only: nothing is queued, scheduled or published because of it, and scheduling, queueing or publishing the post clears it. A `delivery` in the same call wins: carried out directly, the proposal ends up empty; sent to Review with a time, the proposal becomes that time. `null` means none."},"delivery":{"anyOf":[{"type":"object","properties":{"mode":{"type":"string","enum":["queue","prioritise"]},"queueIds":{"type":"object","additionalProperties":{"type":"string","format":"uuid","nullable":true},"description":"Which queue each channel's post joins: social account id → queue id, or null for the channel's Main queue. A channel left out goes to its queue for the post's category, else Main."},"timezone":{"type":"string"}},"required":["mode"],"additionalProperties":false},{"type":"object","properties":{"mode":{"type":"string","enum":["now"]},"timezone":{"type":"string"}},"required":["mode"],"additionalProperties":false},{"type":"object","properties":{"mode":{"type":"string","enum":["at"]},"at":{"type":"string","format":"date-time"},"timezone":{"type":"string"}},"required":["mode","at"],"additionalProperties":false}],"description":"Schedule, queue or publish in the same call. Without publish rights, or when the workspace requires approval, this sends the post to Review, labelled with this key; approving it carries the request out."},"idempotencyKey":{"type":"string","minLength":1,"maxLength":128,"description":"Retrying with the same key never repeats the work. Also accepted as the Idempotency-Key header."}},"required":["accounts","caption"],"additionalProperties":false}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["draft","pending_approval","approved","changes_requested","rejected","queued","scheduled","publishing","published","partially_published","failed","on_hold"]},"caption":{"type":"string"},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"linkUrl":{"type":"string","nullable":true},"mediaAssetIds":{"type":"array","items":{"type":"string"}},"scheduledAt":{"type":"string","nullable":true},"proposedPublishAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"attentionResolvedAt":{"type":"string","nullable":true,"description":"When a `partially_published` post was marked as dealt with (its failed channels posted another way, or a duplicate of it went out); null otherwise."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"createdVia":{"type":"object","properties":{"credentialId":{"type":"string"},"name":{"type":"string","nullable":true}},"required":["credentialId","name"],"additionalProperties":false,"nullable":true},"channels":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"platform":{"type":"string","enum":["facebook","instagram","threads","linkedin","tiktok","youtube","pinterest","bluesky","mastodon","google_business","devto"]},"handle":{"type":"string","nullable":true},"status":{"type":"string"},"scheduledAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"url":{"type":"string","nullable":true,"description":"The live post, once published."},"error":{"type":"string","nullable":true},"warnings":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"queuePosition":{"type":"number","nullable":true,"description":"Order within its queue, while queued."},"queueId":{"type":"string","nullable":true,"description":"The named queue it waits in, while queued; null is the channel's Main queue."},"caption":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"boardId":{"type":"string","nullable":true},"options":{"nullable":true}},"required":["accountId","platform","handle","status","scheduledAt","publishedAt","url","error","warnings","queuePosition","queueId","caption","title","firstComment","boardId"],"additionalProperties":false}}},"required":["id","status","caption","title","firstComment","linkUrl","mediaAssetIds","scheduledAt","proposedPublishAt","publishedAt","attentionResolvedAt","createdAt","updatedAt","createdVia","channels"],"additionalProperties":false},"routed":{"type":"string","enum":["draft","enacted","pending_approval"],"description":"`pending_approval`: in Review, waiting for someone to approve it."}},"required":["post","routed"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}},"/api/v1/posts/{id}":{"get":{"operationId":"post_get","summary":"One post, with each channel's status, time, live URL and error","x-permission":"read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"If-None-Match","in":"header","required":false,"description":"The ETag of an earlier answer: 304 when nothing changed since.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"ETag":{"description":"Send it back as If-None-Match to ask whether anything changed.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["draft","pending_approval","approved","changes_requested","rejected","queued","scheduled","publishing","published","partially_published","failed","on_hold"]},"caption":{"type":"string"},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"linkUrl":{"type":"string","nullable":true},"mediaAssetIds":{"type":"array","items":{"type":"string"}},"scheduledAt":{"type":"string","nullable":true},"proposedPublishAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"attentionResolvedAt":{"type":"string","nullable":true,"description":"When a `partially_published` post was marked as dealt with (its failed channels posted another way, or a duplicate of it went out); null otherwise."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"createdVia":{"type":"object","properties":{"credentialId":{"type":"string"},"name":{"type":"string","nullable":true}},"required":["credentialId","name"],"additionalProperties":false,"nullable":true},"channels":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"platform":{"type":"string","enum":["facebook","instagram","threads","linkedin","tiktok","youtube","pinterest","bluesky","mastodon","google_business","devto"]},"handle":{"type":"string","nullable":true},"status":{"type":"string"},"scheduledAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"url":{"type":"string","nullable":true,"description":"The live post, once published."},"error":{"type":"string","nullable":true},"warnings":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"queuePosition":{"type":"number","nullable":true,"description":"Order within its queue, while queued."},"queueId":{"type":"string","nullable":true,"description":"The named queue it waits in, while queued; null is the channel's Main queue."},"caption":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"boardId":{"type":"string","nullable":true},"options":{"nullable":true}},"required":["accountId","platform","handle","status","scheduledAt","publishedAt","url","error","warnings","queuePosition","queueId","caption","title","firstComment","boardId"],"additionalProperties":false}}},"required":["id","status","caption","title","firstComment","linkUrl","mediaAssetIds","scheduledAt","proposedPublishAt","publishedAt","attentionResolvedAt","createdAt","updatedAt","createdVia","channels"],"additionalProperties":false}}}},"304":{"description":"Nothing changed since the ETag sent as If-None-Match. No body.","headers":{"ETag":{"description":"Send it back as If-None-Match to ask whether anything changed.","schema":{"type":"string"}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}},"patch":{"operationId":"post_update","summary":"Edit a draft. Fields you leave out keep their value; null clears one.","x-permission":"create_posts","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"accounts":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":1,"maxItems":20},"caption":{"type":"string"},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"linkUrl":{"type":"string","maxLength":2048,"nullable":true},"perAccount":{"type":"object","additionalProperties":{"type":"object","properties":{"caption":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"boardId":{"type":"string","maxLength":128,"nullable":true},"options":{"type":"object","properties":{"tiktok":{"type":"object","properties":{"privacyLevel":{"type":"string","enum":["PUBLIC_TO_EVERYONE","MUTUAL_FOLLOW_FRIENDS","FOLLOWER_OF_CREATOR","SELF_ONLY"],"nullable":true},"allowComment":{"type":"boolean","nullable":true},"allowDuet":{"type":"boolean","nullable":true},"allowStitch":{"type":"boolean","nullable":true},"disclose":{"type":"boolean","nullable":true},"brandOrganic":{"type":"boolean","nullable":true},"brandContent":{"type":"boolean","nullable":true},"aiGenerated":{"type":"boolean","nullable":true},"coverMs":{"type":"integer","minimum":0,"nullable":true},"autoAddMusic":{"type":"boolean","nullable":true}},"additionalProperties":false,"nullable":true},"devto":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string","maxLength":100},"maxItems":20,"nullable":true},"canonicalUrl":{"type":"string","maxLength":2048,"nullable":true}},"additionalProperties":false,"nullable":true},"youtube":{"type":"object","properties":{"privacyStatus":{"type":"string","enum":["public","unlisted","private"],"nullable":true},"madeForKids":{"type":"boolean","nullable":true},"tags":{"type":"array","items":{"type":"string","minLength":1,"maxLength":500},"maxItems":500,"nullable":true},"categoryId":{"type":"string","pattern":"^\\d{1,4}$","nullable":true},"thumbnail":{"anyOf":[{"type":"object","properties":{"assetId":{"type":"string","format":"uuid"}},"required":["assetId"],"additionalProperties":false},{"type":"object","properties":{"coverFrame":{"type":"boolean","enum":[true]}},"required":["coverFrame"],"additionalProperties":false}],"nullable":true}},"additionalProperties":false,"nullable":true},"facebook":{"type":"object","properties":{"publishAs":{"type":"string","enum":["video","reel"],"nullable":true}},"additionalProperties":false,"nullable":true}},"additionalProperties":false,"nullable":true,"description":"Merged field by field: a field left out stays, `null` clears it, and `null` for the whole object clears every option."}},"additionalProperties":false}},"mediaAssetIds":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":35},"proposedPublishAt":{"type":"string","format":"date-time","nullable":true,"description":"A proposed publish time for the draft (ISO 8601 with an offset). A note only: nothing is queued, scheduled or published because of it, and scheduling, queueing or publishing the post clears it. A `delivery` in the same call wins: carried out directly, the proposal ends up empty; sent to Review with a time, the proposal becomes that time. `null` clears it."}},"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["draft","pending_approval","approved","changes_requested","rejected","queued","scheduled","publishing","published","partially_published","failed","on_hold"]},"caption":{"type":"string"},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"linkUrl":{"type":"string","nullable":true},"mediaAssetIds":{"type":"array","items":{"type":"string"}},"scheduledAt":{"type":"string","nullable":true},"proposedPublishAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"attentionResolvedAt":{"type":"string","nullable":true,"description":"When a `partially_published` post was marked as dealt with (its failed channels posted another way, or a duplicate of it went out); null otherwise."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"createdVia":{"type":"object","properties":{"credentialId":{"type":"string"},"name":{"type":"string","nullable":true}},"required":["credentialId","name"],"additionalProperties":false,"nullable":true},"channels":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"platform":{"type":"string","enum":["facebook","instagram","threads","linkedin","tiktok","youtube","pinterest","bluesky","mastodon","google_business","devto"]},"handle":{"type":"string","nullable":true},"status":{"type":"string"},"scheduledAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"url":{"type":"string","nullable":true,"description":"The live post, once published."},"error":{"type":"string","nullable":true},"warnings":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"queuePosition":{"type":"number","nullable":true,"description":"Order within its queue, while queued."},"queueId":{"type":"string","nullable":true,"description":"The named queue it waits in, while queued; null is the channel's Main queue."},"caption":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"boardId":{"type":"string","nullable":true},"options":{"nullable":true}},"required":["accountId","platform","handle","status","scheduledAt","publishedAt","url","error","warnings","queuePosition","queueId","caption","title","firstComment","boardId"],"additionalProperties":false}}},"required":["id","status","caption","title","firstComment","linkUrl","mediaAssetIds","scheduledAt","proposedPublishAt","publishedAt","attentionResolvedAt","createdAt","updatedAt","createdVia","channels"],"additionalProperties":false},"routed":{"type":"string","enum":["draft","enacted","pending_approval"],"description":"`pending_approval`: in Review, waiting for someone to approve it."}},"required":["post","routed"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}},"delete":{"operationId":"post_delete","summary":"Delete a post that isn't live. A scheduled or queued one needs publish rights.","x-permission":"create_posts","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Done."},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}},"/api/v1/posts/{id}/duplicate":{"post":{"operationId":"post_duplicate","summary":"Copy a post, in any state, into a new draft","description":"The copy keeps the words, media, channels and per-channel settings, and starts as a draft with no schedule and no approval. The original is not changed. Channels that can no longer be posted to are left out and listed in `droppedChannels`; a partially published post keeps only the channels that didn't go out. TikTok settings are not copied: TikTok requires them to be chosen for every post. It never schedules or publishes.","x-permission":"create_posts","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Retrying with the same key within 24 hours never repeats the work. Same as `idempotencyKey` in the body. An empty value counts as none.","schema":{"type":"string","maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"idempotencyKey":{"type":"string","minLength":1,"maxLength":128,"description":"Retrying with the same key never repeats the work. Also accepted as the Idempotency-Key header."}},"additionalProperties":false}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["draft","pending_approval","approved","changes_requested","rejected","queued","scheduled","publishing","published","partially_published","failed","on_hold"]},"caption":{"type":"string"},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"linkUrl":{"type":"string","nullable":true},"mediaAssetIds":{"type":"array","items":{"type":"string"}},"scheduledAt":{"type":"string","nullable":true},"proposedPublishAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"attentionResolvedAt":{"type":"string","nullable":true,"description":"When a `partially_published` post was marked as dealt with (its failed channels posted another way, or a duplicate of it went out); null otherwise."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"createdVia":{"type":"object","properties":{"credentialId":{"type":"string"},"name":{"type":"string","nullable":true}},"required":["credentialId","name"],"additionalProperties":false,"nullable":true},"channels":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"platform":{"type":"string","enum":["facebook","instagram","threads","linkedin","tiktok","youtube","pinterest","bluesky","mastodon","google_business","devto"]},"handle":{"type":"string","nullable":true},"status":{"type":"string"},"scheduledAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"url":{"type":"string","nullable":true,"description":"The live post, once published."},"error":{"type":"string","nullable":true},"warnings":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"queuePosition":{"type":"number","nullable":true,"description":"Order within its queue, while queued."},"queueId":{"type":"string","nullable":true,"description":"The named queue it waits in, while queued; null is the channel's Main queue."},"caption":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"boardId":{"type":"string","nullable":true},"options":{"nullable":true}},"required":["accountId","platform","handle","status","scheduledAt","publishedAt","url","error","warnings","queuePosition","queueId","caption","title","firstComment","boardId"],"additionalProperties":false}}},"required":["id","status","caption","title","firstComment","linkUrl","mediaAssetIds","scheduledAt","proposedPublishAt","publishedAt","attentionResolvedAt","createdAt","updatedAt","createdVia","channels"],"additionalProperties":false},"routed":{"type":"string","enum":["draft","enacted","pending_approval"],"description":"`pending_approval`: in Review, waiting for someone to approve it."},"droppedChannels":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"platform":{"type":"string"},"handle":{"type":"string"},"status":{"type":"string","description":"The channel's connection status, e.g. `disconnected`."}},"required":["id","platform","handle","status"],"additionalProperties":false},"description":"Channels of the original the copy left out, because they can't be posted to."},"keptFailedOnly":{"type":"boolean","description":"True when the original was partially published: the copy leaves out the channels that published, and keeps the ones that failed or never went out."}},"required":["post","routed","droppedChannels","keptFailedOnly"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}},"/api/v1/posts/{id}/submit":{"post":{"operationId":"post_submit","summary":"Carry out a delivery: directly with publish rights, otherwise (or when the workspace requires approval) through Review","x-permission":"create_posts","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Retrying with the same key within 24 hours never repeats the work. Same as `idempotencyKey` in the body. An empty value counts as none.","schema":{"type":"string","maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"delivery":{"anyOf":[{"type":"object","properties":{"mode":{"type":"string","enum":["queue","prioritise"]},"queueIds":{"type":"object","additionalProperties":{"type":"string","format":"uuid","nullable":true},"description":"Which queue each channel's post joins: social account id → queue id, or null for the channel's Main queue. A channel left out goes to its queue for the post's category, else Main."},"timezone":{"type":"string"}},"required":["mode"],"additionalProperties":false},{"type":"object","properties":{"mode":{"type":"string","enum":["now"]},"timezone":{"type":"string"}},"required":["mode"],"additionalProperties":false},{"type":"object","properties":{"mode":{"type":"string","enum":["at"]},"at":{"type":"string","format":"date-time"},"timezone":{"type":"string"}},"required":["mode","at"],"additionalProperties":false}]},"idempotencyKey":{"type":"string","minLength":1,"maxLength":128,"description":"Retrying with the same key never repeats the work. Also accepted as the Idempotency-Key header."}},"required":["delivery"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["draft","pending_approval","approved","changes_requested","rejected","queued","scheduled","publishing","published","partially_published","failed","on_hold"]},"caption":{"type":"string"},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"linkUrl":{"type":"string","nullable":true},"mediaAssetIds":{"type":"array","items":{"type":"string"}},"scheduledAt":{"type":"string","nullable":true},"proposedPublishAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"attentionResolvedAt":{"type":"string","nullable":true,"description":"When a `partially_published` post was marked as dealt with (its failed channels posted another way, or a duplicate of it went out); null otherwise."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"createdVia":{"type":"object","properties":{"credentialId":{"type":"string"},"name":{"type":"string","nullable":true}},"required":["credentialId","name"],"additionalProperties":false,"nullable":true},"channels":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"platform":{"type":"string","enum":["facebook","instagram","threads","linkedin","tiktok","youtube","pinterest","bluesky","mastodon","google_business","devto"]},"handle":{"type":"string","nullable":true},"status":{"type":"string"},"scheduledAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"url":{"type":"string","nullable":true,"description":"The live post, once published."},"error":{"type":"string","nullable":true},"warnings":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"queuePosition":{"type":"number","nullable":true,"description":"Order within its queue, while queued."},"queueId":{"type":"string","nullable":true,"description":"The named queue it waits in, while queued; null is the channel's Main queue."},"caption":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"boardId":{"type":"string","nullable":true},"options":{"nullable":true}},"required":["accountId","platform","handle","status","scheduledAt","publishedAt","url","error","warnings","queuePosition","queueId","caption","title","firstComment","boardId"],"additionalProperties":false}}},"required":["id","status","caption","title","firstComment","linkUrl","mediaAssetIds","scheduledAt","proposedPublishAt","publishedAt","attentionResolvedAt","createdAt","updatedAt","createdVia","channels"],"additionalProperties":false},"routed":{"type":"string","enum":["draft","enacted","pending_approval"],"description":"`pending_approval`: in Review, waiting for someone to approve it."}},"required":["post","routed"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}},"/api/v1/posts/{id}/withdraw":{"post":{"operationId":"post_withdraw","summary":"Take a post waiting for approval back to a draft","x-permission":"create_posts","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"idempotencyKey":{"type":"string","minLength":1,"maxLength":128,"description":"Accepted and ignored: a withdraw that already happened answers 409."}},"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["draft","pending_approval","approved","changes_requested","rejected","queued","scheduled","publishing","published","partially_published","failed","on_hold"]},"caption":{"type":"string"},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"linkUrl":{"type":"string","nullable":true},"mediaAssetIds":{"type":"array","items":{"type":"string"}},"scheduledAt":{"type":"string","nullable":true},"proposedPublishAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"attentionResolvedAt":{"type":"string","nullable":true,"description":"When a `partially_published` post was marked as dealt with (its failed channels posted another way, or a duplicate of it went out); null otherwise."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"createdVia":{"type":"object","properties":{"credentialId":{"type":"string"},"name":{"type":"string","nullable":true}},"required":["credentialId","name"],"additionalProperties":false,"nullable":true},"channels":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"platform":{"type":"string","enum":["facebook","instagram","threads","linkedin","tiktok","youtube","pinterest","bluesky","mastodon","google_business","devto"]},"handle":{"type":"string","nullable":true},"status":{"type":"string"},"scheduledAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"url":{"type":"string","nullable":true,"description":"The live post, once published."},"error":{"type":"string","nullable":true},"warnings":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"queuePosition":{"type":"number","nullable":true,"description":"Order within its queue, while queued."},"queueId":{"type":"string","nullable":true,"description":"The named queue it waits in, while queued; null is the channel's Main queue."},"caption":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"boardId":{"type":"string","nullable":true},"options":{"nullable":true}},"required":["accountId","platform","handle","status","scheduledAt","publishedAt","url","error","warnings","queuePosition","queueId","caption","title","firstComment","boardId"],"additionalProperties":false}}},"required":["id","status","caption","title","firstComment","linkUrl","mediaAssetIds","scheduledAt","proposedPublishAt","publishedAt","attentionResolvedAt","createdAt","updatedAt","createdVia","channels"],"additionalProperties":false},"routed":{"type":"string","enum":["draft","enacted","pending_approval"],"description":"`pending_approval`: in Review, waiting for someone to approve it."}},"required":["post","routed"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}},"/api/v1/posts/{id}/schedule":{"post":{"operationId":"post_schedule","summary":"Schedule or queue a post, or re-time one that is already scheduled","x-permission":"publish_directly","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Retrying with the same key within 24 hours never repeats the work. Same as `idempotencyKey` in the body. An empty value counts as none.","schema":{"type":"string","maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"at":{"type":"string","format":"date-time","description":"When to publish, with an offset (2026-10-06T09:00:00+02:00)."},"mode":{"type":"string","enum":["at","queue","prioritise"],"default":"at","description":"`at` a time, or add to the channel's queue (`prioritise` puts it first)."},"queueIds":{"type":"object","additionalProperties":{"type":"string","format":"uuid","nullable":true},"description":"Which queue each channel's post joins: social account id → queue id, or null for the channel's Main queue. A channel left out goes to its queue for the post's category, else Main."},"timezone":{"type":"string"},"idempotencyKey":{"type":"string","minLength":1,"maxLength":128,"description":"Retrying with the same key never repeats the work. Also accepted as the Idempotency-Key header."}},"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["draft","pending_approval","approved","changes_requested","rejected","queued","scheduled","publishing","published","partially_published","failed","on_hold"]},"caption":{"type":"string"},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"linkUrl":{"type":"string","nullable":true},"mediaAssetIds":{"type":"array","items":{"type":"string"}},"scheduledAt":{"type":"string","nullable":true},"proposedPublishAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"attentionResolvedAt":{"type":"string","nullable":true,"description":"When a `partially_published` post was marked as dealt with (its failed channels posted another way, or a duplicate of it went out); null otherwise."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"createdVia":{"type":"object","properties":{"credentialId":{"type":"string"},"name":{"type":"string","nullable":true}},"required":["credentialId","name"],"additionalProperties":false,"nullable":true},"channels":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"platform":{"type":"string","enum":["facebook","instagram","threads","linkedin","tiktok","youtube","pinterest","bluesky","mastodon","google_business","devto"]},"handle":{"type":"string","nullable":true},"status":{"type":"string"},"scheduledAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"url":{"type":"string","nullable":true,"description":"The live post, once published."},"error":{"type":"string","nullable":true},"warnings":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"queuePosition":{"type":"number","nullable":true,"description":"Order within its queue, while queued."},"queueId":{"type":"string","nullable":true,"description":"The named queue it waits in, while queued; null is the channel's Main queue."},"caption":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"boardId":{"type":"string","nullable":true},"options":{"nullable":true}},"required":["accountId","platform","handle","status","scheduledAt","publishedAt","url","error","warnings","queuePosition","queueId","caption","title","firstComment","boardId"],"additionalProperties":false}}},"required":["id","status","caption","title","firstComment","linkUrl","mediaAssetIds","scheduledAt","proposedPublishAt","publishedAt","attentionResolvedAt","createdAt","updatedAt","createdVia","channels"],"additionalProperties":false},"routed":{"type":"string","enum":["draft","enacted","pending_approval"],"description":"`pending_approval`: in Review, waiting for someone to approve it."}},"required":["post","routed"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}},"/api/v1/posts/{id}/publish":{"post":{"operationId":"post_publish","summary":"Publish a post now","x-permission":"publish_directly","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Retrying with the same key within 24 hours never repeats the work. Same as `idempotencyKey` in the body. An empty value counts as none.","schema":{"type":"string","maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"idempotencyKey":{"type":"string","minLength":1,"maxLength":128,"description":"Retrying with the same key never repeats the work. Also accepted as the Idempotency-Key header."}},"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["draft","pending_approval","approved","changes_requested","rejected","queued","scheduled","publishing","published","partially_published","failed","on_hold"]},"caption":{"type":"string"},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"linkUrl":{"type":"string","nullable":true},"mediaAssetIds":{"type":"array","items":{"type":"string"}},"scheduledAt":{"type":"string","nullable":true},"proposedPublishAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"attentionResolvedAt":{"type":"string","nullable":true,"description":"When a `partially_published` post was marked as dealt with (its failed channels posted another way, or a duplicate of it went out); null otherwise."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"createdVia":{"type":"object","properties":{"credentialId":{"type":"string"},"name":{"type":"string","nullable":true}},"required":["credentialId","name"],"additionalProperties":false,"nullable":true},"channels":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"platform":{"type":"string","enum":["facebook","instagram","threads","linkedin","tiktok","youtube","pinterest","bluesky","mastodon","google_business","devto"]},"handle":{"type":"string","nullable":true},"status":{"type":"string"},"scheduledAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"url":{"type":"string","nullable":true,"description":"The live post, once published."},"error":{"type":"string","nullable":true},"warnings":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"queuePosition":{"type":"number","nullable":true,"description":"Order within its queue, while queued."},"queueId":{"type":"string","nullable":true,"description":"The named queue it waits in, while queued; null is the channel's Main queue."},"caption":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"boardId":{"type":"string","nullable":true},"options":{"nullable":true}},"required":["accountId","platform","handle","status","scheduledAt","publishedAt","url","error","warnings","queuePosition","queueId","caption","title","firstComment","boardId"],"additionalProperties":false}}},"required":["id","status","caption","title","firstComment","linkUrl","mediaAssetIds","scheduledAt","proposedPublishAt","publishedAt","attentionResolvedAt","createdAt","updatedAt","createdVia","channels"],"additionalProperties":false},"routed":{"type":"string","enum":["draft","enacted","pending_approval"],"description":"`pending_approval`: in Review, waiting for someone to approve it."}},"required":["post","routed"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}},"/api/v1/posts/{id}/cancel":{"post":{"operationId":"post_cancel","summary":"Unschedule a scheduled, queued or failed post back to a draft","x-permission":"publish_directly","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Retrying with the same key within 24 hours never repeats the work. Same as `idempotencyKey` in the body. An empty value counts as none.","schema":{"type":"string","maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"idempotencyKey":{"type":"string","minLength":1,"maxLength":128,"description":"Retrying with the same key never repeats the work. Also accepted as the Idempotency-Key header."}},"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["draft","pending_approval","approved","changes_requested","rejected","queued","scheduled","publishing","published","partially_published","failed","on_hold"]},"caption":{"type":"string"},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"linkUrl":{"type":"string","nullable":true},"mediaAssetIds":{"type":"array","items":{"type":"string"}},"scheduledAt":{"type":"string","nullable":true},"proposedPublishAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"attentionResolvedAt":{"type":"string","nullable":true,"description":"When a `partially_published` post was marked as dealt with (its failed channels posted another way, or a duplicate of it went out); null otherwise."},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"createdVia":{"type":"object","properties":{"credentialId":{"type":"string"},"name":{"type":"string","nullable":true}},"required":["credentialId","name"],"additionalProperties":false,"nullable":true},"channels":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"platform":{"type":"string","enum":["facebook","instagram","threads","linkedin","tiktok","youtube","pinterest","bluesky","mastodon","google_business","devto"]},"handle":{"type":"string","nullable":true},"status":{"type":"string"},"scheduledAt":{"type":"string","nullable":true},"publishedAt":{"type":"string","nullable":true},"url":{"type":"string","nullable":true,"description":"The live post, once published."},"error":{"type":"string","nullable":true},"warnings":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"additionalProperties":false}},"queuePosition":{"type":"number","nullable":true,"description":"Order within its queue, while queued."},"queueId":{"type":"string","nullable":true,"description":"The named queue it waits in, while queued; null is the channel's Main queue."},"caption":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"firstComment":{"type":"string","nullable":true},"boardId":{"type":"string","nullable":true},"options":{"nullable":true}},"required":["accountId","platform","handle","status","scheduledAt","publishedAt","url","error","warnings","queuePosition","queueId","caption","title","firstComment","boardId"],"additionalProperties":false}}},"required":["id","status","caption","title","firstComment","linkUrl","mediaAssetIds","scheduledAt","proposedPublishAt","publishedAt","attentionResolvedAt","createdAt","updatedAt","createdVia","channels"],"additionalProperties":false},"routed":{"type":"string","enum":["draft","enacted","pending_approval"],"description":"`pending_approval`: in Review, waiting for someone to approve it."}},"required":["post","routed"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}},"/api/v1/analytics/posts/{id}":{"get":{"operationId":"post_analytics","summary":"A post's analytics snapshots per channel (refreshed hourly)","x-permission":"view_analytics","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"platform":{"type":"string","enum":["facebook","instagram","threads","linkedin","tiktok","youtube","pinterest","bluesky","mastodon","google_business","devto"]},"snapshots":{"type":"array","items":{"type":"object","properties":{"capturedAt":{"type":"string"},"metrics":{"type":"object","additionalProperties":{"type":"number"},"description":"Metric name → value. A metric the network doesn't report is absent, never 0."}},"required":["capturedAt","metrics"],"additionalProperties":false},"description":"Newest first."}},"required":["accountId","platform","snapshots"],"additionalProperties":false},"description":"One entry per channel the post targets."}},"required":["data"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}},"/api/v1/media/uploads":{"post":{"operationId":"media_request_upload","summary":"Start an upload: returns presigned URLs to PUT the first parts to. Ask for the rest with /parts.","x-permission":"upload_media","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Retrying with the same key within 24 hours never repeats the work. Same as `idempotencyKey` in the body. An empty value counts as none.","schema":{"type":"string","maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mediaType":{"type":"string","enum":["image","video","gif"]},"sizeBytes":{"type":"integer","exclusiveMinimum":true,"minimum":0,"description":"The file's size; checked again at complete."},"altText":{"type":"string","maxLength":1000},"idempotencyKey":{"type":"string","minLength":1,"maxLength":128,"description":"Retrying with the same key never repeats the work. Also accepted as the Idempotency-Key header."}},"required":["mediaType","sizeBytes"],"additionalProperties":false}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"mediaAssetId":{"type":"string","description":"The id `/parts`, `/complete` and `mediaAssetIds` take."},"partSize":{"type":"integer","description":"Bytes per part; the last part may be smaller."},"partCount":{"type":"integer"},"urls":{"type":"array","items":{"type":"object","properties":{"partNumber":{"type":"integer"},"url":{"type":"string"}},"required":["partNumber","url"],"additionalProperties":false},"description":"The first parts only (up to 8). Ask POST /api/v1/media/uploads/{id}/parts for the rest."}},"required":["mediaAssetId","partSize","partCount","urls"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}},"/api/v1/media/uploads/{id}/parts":{"post":{"operationId":"media_sign_parts","summary":"Presigned URLs for more parts of an upload (up to 16 at a time)","description":"Counts against the read limit, and takes no Idempotency-Key: asking again signs fresh URLs.","x-permission":"upload_media","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"fromPart":{"type":"integer","exclusiveMinimum":true,"minimum":0,"description":"The first part number to sign."},"count":{"type":"integer","exclusiveMinimum":true,"minimum":0,"maximum":16,"description":"How many parts, at most 16."}},"required":["fromPart","count"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"urls":{"type":"array","items":{"type":"object","properties":{"partNumber":{"type":"integer"},"url":{"type":"string"}},"required":["partNumber","url"],"additionalProperties":false},"description":"Presigned PUT URLs, one per part. Keep each response's ETag header for complete."}},"required":["urls"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}},"/api/v1/media/uploads/{id}/complete":{"post":{"operationId":"media_complete_upload","summary":"Finish an upload once every part is in","description":"Safe to repeat: finishing a finished upload answers with the same asset.","x-permission":"upload_media","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"parts":{"type":"array","items":{"type":"object","properties":{"partNumber":{"type":"integer","exclusiveMinimum":true,"minimum":0},"etag":{"type":"string","minLength":1}},"required":["partNumber","etag"],"additionalProperties":false},"minItems":1,"description":"Each uploaded part's number and the ETag R2 returned for it."},"durationSec":{"type":"number","exclusiveMinimum":true,"minimum":0,"maximum":86400},"width":{"type":"integer","exclusiveMinimum":true,"minimum":0,"maximum":16384},"height":{"type":"integer","exclusiveMinimum":true,"minimum":0,"maximum":16384}},"required":["parts"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Put it in `mediaAssetIds`."},"mediaType":{"type":"string","enum":["image","video","gif","document"]},"contentType":{"type":"string","nullable":true,"description":"Read from the uploaded bytes."},"sizeBytes":{"type":"integer","nullable":true,"description":"What was stored, not what was declared."},"width":{"type":"integer","nullable":true},"height":{"type":"integer","nullable":true},"durationSec":{"type":"number","nullable":true},"altText":{"type":"string","nullable":true},"processingStatus":{"type":"string","enum":["pending","processing","ready","failed"]},"uploadedAt":{"type":"string","nullable":true},"createdAt":{"type":"string"}},"required":["id","mediaType","contentType","sizeBytes","width","height","durationSec","altText","processingStatus","uploadedAt","createdAt"],"additionalProperties":false}}}},"default":{"description":"An error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"A short machine-readable code."},"detail":{"type":"string","description":"What went wrong, in words."},"tier":{"type":"string","description":"On 429: which limit (`credential_writes`, `platform_quota:instagram`, …)."},"limit":{"type":"number"},"remaining":{"type":"number"},"retry_after":{"type":"number","description":"On 429: seconds to wait."},"available_at":{"type":"string","nullable":true,"description":"On a platform quota: the earliest publish time that fits."},"post_id":{"type":"string","description":"On `POST /api/v1/posts/`: the post the call saved before it failed. After a 4xx it is still a draft: fix it (`PATCH /api/v1/posts/{id}`) and send it with `/submit`. After a 5xx, read it first (`GET`): it may have gone ahead. A 4xx without it saved nothing."}},"required":["error","detail"],"additionalProperties":false}}}}}}}}}