{
  "openapi": "3.1.0",
  "info": {
    "title": "popkit REST API",
    "version": "v1.0",
    "summary": "Create once, publish everywhere — programmatic access to popkit workspaces.",
    "description": "Every endpoint mirrors a popkit capability that the MCP server also exposes; the two share scopes, rate limits, idempotency and audit.\n\n每个端点对应一项 popkit 能力(与 MCP Server 同源),scope / 限流 / 幂等 / 审计完全共用。文档:https://popkit.loxily.com/docs/api",
    "contact": {
      "name": "popkit support",
      "email": "support@loxily.com",
      "url": "https://popkit.loxily.com/docs/api"
    }
  },
  "servers": [
    {
      "url": "https://popkit.loxily.com/api/v1",
      "description": "popkit (/api/v1)"
    }
  ],
  "tags": [
    {
      "name": "Workspace & accounts",
      "description": "Who am I, which accounts are connected, what quota is left. / 我是谁、有哪些账号、配额还剩多少。"
    },
    {
      "name": "Posts",
      "description": "Drafts, scheduling, edits, rescheduling, approval and prechecks. / 草稿、排期、修改、改期、送审与预检。"
    },
    {
      "name": "Publishing",
      "description": "The two endpoints that actually go out: confirm and publish / retry. / 真正外发的两个端点:确认发布与发布 / 重投。"
    },
    {
      "name": "Media",
      "description": "Hand images / videos to popkit in exchange for a publishable media URL. / 把图片 / 视频交给 popkit,换取可发布的媒体 URL。"
    },
    {
      "name": "Comments",
      "description": "The comment inbox and replying as the account. / 评论收件箱与以账号名义回复。"
    },
    {
      "name": "Idea board",
      "description": "Groups and ideas on the planning board. / 规划页看板的分组与选题。"
    },
    {
      "name": "Analytics",
      "description": "Metrics overview, same definitions as the in-app analytics page. / 与站内分析页同口径的指标概览。"
    }
  ],
  "paths": {
    "/workspace": {
      "get": {
        "operationId": "get_workspace_context",
        "tags": [
          "Workspace & accounts"
        ],
        "summary": "Workspace context / 工作区上下文",
        "description": "Who you are, which workspace, your role, this credential's scopes and publish tiers, usable platforms, quota left and deep links back into the app. Call it first; later endpoints take platform / account parameters from here.\n\n我是谁、在哪个工作区、什么角色、本凭证的 scope 与发布落点、可用平台、配额余量与回站深链。接入后建议先调它,后续端点的平台 / 账号参数都从这里取。",
        "security": [
          {
            "bearerAuth": [
              "workspace:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "responses": {
          "200": {
            "description": "Workspace context",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "OK / 成功",
                    "value": {
                      "data": {
                        "workspace": {
                          "id": "ws_01J7PZ4A8K3D",
                          "name": "popkit 团队",
                          "kind": "team",
                          "role": "admin"
                        },
                        "user": {
                          "id": "usr_01J7PZ3Y2M0Q"
                        },
                        "credential": {
                          "kind": "mcp_client",
                          "scopes": [
                            "workspace:read",
                            "accounts:read",
                            "posts:read",
                            "posts:write",
                            "schedule:write"
                          ],
                          "allowedActions": [
                            "draft",
                            "schedule"
                          ],
                          "confirmMode": "preauthorized"
                        },
                        "quotas": {
                          "scheduledThisMonth": {
                            "used": 3,
                            "limit": 15,
                            "left": 12
                          },
                          "rendersThisMonth": {
                            "used": 0,
                            "limit": 30,
                            "left": 30
                          },
                          "seats": {
                            "used": 2,
                            "limit": 3,
                            "left": 1
                          },
                          "aiThisMonth": {
                            "used": 4,
                            "limit": 50,
                            "left": 46
                          }
                        },
                        "platforms": {
                          "publishable": [
                            "x",
                            "youtube",
                            "instagram",
                            "tiktok",
                            "facebook",
                            "threads"
                          ],
                          "manual": [
                            "douyin",
                            "xiaohongshu",
                            "wechat_mp"
                          ]
                        },
                        "accounts": {
                          "total": 4,
                          "byPlatform": {
                            "x": 1,
                            "youtube": 1,
                            "instagram": 1,
                            "tiktok": 1
                          }
                        },
                        "deepLinks": {
                          "dashboard": "https://popkit.loxily.com/dashboard",
                          "drafts": "https://popkit.loxily.com/content",
                          "calendar": "https://popkit.loxily.com/calendar",
                          "history": "https://popkit.loxily.com/history",
                          "planning": "https://popkit.loxily.com/planning"
                        }
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c61"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "workspace:read"
        ],
        "x-popkit-rate-class": "read",
        "x-popkit-idempotency": "none",
        "x-popkit-read-only": true,
        "x-popkit-mcp-tool": "get_workspace_context"
      }
    },
    "/accounts": {
      "get": {
        "operationId": "list_accounts",
        "tags": [
          "Workspace & accounts"
        ],
        "summary": "Connected accounts / 已连接账号",
        "description": "Social accounts connected to this workspace: platform, handle, followers, authorization status (active / expiring / expired), whether popkit can publish for it, and groups. Expired accounts must be re-authorized by a user in the app.\n\n本工作区已连接的社交账号:平台、handle、粉丝数、授权状态(active / expiring / expired)、能否代发、所属分组。expired 的账号需要用户回站重新授权,外部系统无法代劳。",
        "security": [
          {
            "bearerAuth": [
              "accounts:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "responses": {
          "200": {
            "description": "Connected accounts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "OK / 成功",
                    "value": {
                      "data": {
                        "accounts": [
                          {
                            "id": "acc_01J8XQ5M9V2R",
                            "platform": "x",
                            "handle": "@popkit",
                            "displayName": "popkit",
                            "avatar": "https://…/avatar.jpg",
                            "followers": 12480,
                            "status": "active",
                            "connectedAt": "2026-07-03T02:11:09.000Z",
                            "lastSyncedAt": "2026-09-20T01:00:12.000Z",
                            "canPublish": true,
                            "isManual": false,
                            "profileUrl": "https://x.com/popkit",
                            "subscriptionType": null,
                            "groupIds": [
                              "grp_01J8…"
                            ]
                          },
                          {
                            "id": "acc_01J8YB7N4T8K",
                            "platform": "douyin",
                            "handle": "popkit官方",
                            "displayName": "popkit官方",
                            "avatar": null,
                            "followers": 0,
                            "status": "active",
                            "connectedAt": "2026-09-04T06:30:00.000Z",
                            "lastSyncedAt": null,
                            "canPublish": false,
                            "isManual": true,
                            "profileUrl": null,
                            "subscriptionType": null,
                            "groupIds": []
                          }
                        ],
                        "groups": [
                          {
                            "id": "grp_01J8…",
                            "name": "品牌号",
                            "count": 1
                          }
                        ],
                        "byPlatform": {
                          "x": 1,
                          "douyin": 1
                        },
                        "deepLink": "https://popkit.loxily.com/accounts"
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c62"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "accounts:read"
        ],
        "x-popkit-rate-class": "read",
        "x-popkit-idempotency": "none",
        "x-popkit-read-only": true,
        "x-popkit-mcp-tool": "list_accounts"
      }
    },
    "/posts": {
      "get": {
        "operationId": "list_posts",
        "tags": [
          "Posts"
        ],
        "summary": "List posts / 内容列表",
        "description": "Paginated list of drafts, scheduled and published posts (eight statuses). The cursor is opaque; pass back nextCursor to fetch the next page.\n\n分页列出草稿 / 排期 / 已发布内容(八态)。游标不透明,把上一页的 nextCursor 原样回传取下一页。",
        "security": [
          {
            "bearerAuth": [
              "posts:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by status; omit for all. / 按状态筛;不给 = 全部。",
            "schema": {
              "description": "按状态筛选;不给= 全部",
              "type": "string",
              "enum": [
                "draft",
                "reviewing",
                "approved",
                "rejected",
                "scheduled",
                "publishing",
                "published",
                "failed"
              ]
            }
          },
          {
            "name": "platform",
            "in": "query",
            "required": false,
            "description": "Filter by target platform. / 按目标平台筛(内容 platforms 含该平台)。",
            "schema": {
              "description": "按目标平台筛选",
              "type": "string",
              "enum": [
                "x",
                "youtube",
                "instagram",
                "tiktok",
                "facebook",
                "douyin",
                "xiaohongshu",
                "wechat_mp",
                "threads"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, default 20. / 每页条数,默认 20。",
            "schema": {
              "description": "每页条数,默认 20、上限 100",
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "nextCursor from the previous page. / 上一页返回的 nextCursor。",
            "schema": {
              "description": "上一页返回的 nextCursor",
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "responses": {
          "200": {
            "description": "List posts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "OK / 成功",
                    "value": {
                      "data": {
                        "items": [
                          {
                            "id": "pst_01J8ZK3R7QW6M2",
                            "title": null,
                            "content": "新功能上线:一次创作,发布到所有平台 🚀",
                            "platforms": [
                              "x",
                              "instagram"
                            ],
                            "targetAccountIds": [
                              "acc_01J8X…",
                              "acc_01J8Y…"
                            ],
                            "status": "scheduled",
                            "source": "api",
                            "scheduledAt": "2026-09-21T10:00:00.000Z",
                            "publishedAt": null,
                            "createdBy": "usr_01J7…",
                            "createdAt": "2026-09-20T08:12:31.000Z",
                            "updatedAt": "2026-09-20T08:12:31.000Z",
                            "deepLink": "https://popkit.loxily.com/calendar?post=pst_01J8ZK3R7QW6M2"
                          }
                        ],
                        "nextCursor": "eyJjIjoiMjAyNi0wOS0yMFQwODoxMjozMS4wMDBaIiwiaWQiOiJwc3RfMDFKOFpLM1I3UVc2TTIifQ"
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c63"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "posts:read"
        ],
        "x-popkit-rate-class": "read",
        "x-popkit-idempotency": "none",
        "x-popkit-read-only": true,
        "x-popkit-mcp-tool": "list_posts"
      },
      "post": {
        "operationId": "create_post",
        "tags": [
          "Posts"
        ],
        "summary": "Create a post / 新建内容",
        "description": "Create a post; action decides where it lands. draft saves it (prechecks only warn); schedule queues it (monthly quota + hard prechecks); publish sends it now (hard prechecks + rate caps; credentials that require confirmation get a draft plus a confirmToken, and only confirm actually publishes). Publishing is asynchronous: after status=publishing, poll the post for per-account results.\n\n新建一条内容,action 决定落点。draft = 存草稿(预检只提示不拦);schedule = 定时发布(走月配额与硬预检);publish = 立即对外发布(硬预检 + 频控;需要两步确认的凭证会先落草稿并回一枚 confirmToken,再调 confirm 才真正外发)。发布是异步的:返回 status=publishing 后用内容详情轮询各账号结果。",
        "security": [
          {
            "bearerAuth": [
              "posts:write",
              "schedule:write",
              "publish:execute"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "schedule",
                      "publish"
                    ],
                    "description": "Where the post lands: draft; schedule (needs scheduledAt); publish immediately. / 落点档位:draft 存草稿;schedule 定时发布(需 scheduledAt);publish 立即对外发布。"
                  },
                  "content": {
                    "type": "string",
                    "description": "Main copy; may be an empty string when every target platform has its own copy in variants. / 主文案;每个目标渠道都在 variants 里给了自己的文案时可留空字符串。"
                  },
                  "platforms": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "x",
                        "youtube",
                        "instagram",
                        "tiktok",
                        "facebook",
                        "douyin",
                        "xiaohongshu",
                        "wechat_mp",
                        "threads"
                      ]
                    },
                    "description": "Target platforms; may be empty for drafts. / 目标平台;draft 档可为空数组。"
                  },
                  "title": {
                    "description": "Title (required for YouTube). / 标题(YouTube 必填)。",
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "targetAccountIds": {
                    "description": "Account ids (from GET /v1/accounts). Omit = every usable account on those platforms (expired ones skipped), at most 20 per post. When given, only these accounts, and each must belong to a listed platform. / 目标账号 id(来自 GET /v1/accounts)。不给 = 发给 platforms 下全部可用账号(过期 / 缺发布权的自动跳过),单次上限 20 个。给了就只发这些号,且必须都属于 platforms 里的平台。",
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 36
                    }
                  },
                  "scheduledAt": {
                    "description": "Required for schedule; must carry a timezone (Z or ±HH:MM) and be at least 60 s in the future; on a draft it is only the planned date on the calendar. / schedule 档必填,必须带时区(Z 或 ±HH:MM)且至少比现在晚 60 秒;draft 档给了只作日历上的计划日期。",
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "media": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "kind": {
                          "type": "string",
                          "enum": [
                            "image",
                            "cover",
                            "video"
                          ]
                        },
                        "dataUrl": {
                          "type": "string",
                          "description": "必须是本站对象存储 URL(图片/封面可用 image data URL)"
                        },
                        "platform": {
                          "description": "只给某平台用的覆写媒体",
                          "type": "string",
                          "enum": [
                            "x",
                            "youtube",
                            "instagram",
                            "tiktok",
                            "facebook",
                            "douyin",
                            "xiaohongshu",
                            "wechat_mp",
                            "threads"
                          ]
                        },
                        "durationSec": {
                          "type": [
                            "number",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "kind",
                        "dataUrl"
                      ]
                    },
                    "description": "kind is image / cover / video; dataUrl must be a popkit storage URL (get one via POST /v1/media), image data URLs are accepted for images / covers; platform marks a per-platform override. / kind 为 image / cover / video;dataUrl 必须是本站对象存储 URL(先用 POST /v1/media 换取),图片 / 封面也可用 image data URL;platform 表示只给某平台用的覆写媒体。"
                  },
                  "variants": {
                    "description": "Per-platform copy overrides; send only the platforms that differ. / 逐平台文案覆写;只传与主文案不同的。",
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "platform": {
                          "type": "string",
                          "enum": [
                            "x",
                            "youtube",
                            "instagram",
                            "tiktok",
                            "facebook",
                            "douyin",
                            "xiaohongshu",
                            "wechat_mp",
                            "threads"
                          ]
                        },
                        "content": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "title": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "platform"
                      ]
                    }
                  },
                  "tiktokOptions": {
                    "description": "TikTok Direct Post compliance options: privacyLevel (must be chosen by a person when posting to TikTok), allowComment, allowDuet, allowStitch, commercial, yourBrand, brandedContent, coverTimestampMs. / TikTok Direct Post 合规选项:privacyLevel(发 TikTok 时必须由人选定)、allowComment、allowDuet、allowStitch、commercial、yourBrand、brandedContent、coverTimestampMs。",
                    "anyOf": [
                      {
                        "type": "object",
                        "properties": {
                          "privacyLevel": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "allowComment": {
                            "type": "boolean"
                          },
                          "allowDuet": {
                            "type": "boolean"
                          },
                          "allowStitch": {
                            "type": "boolean"
                          },
                          "commercial": {
                            "type": "boolean"
                          },
                          "yourBrand": {
                            "type": "boolean"
                          },
                          "brandedContent": {
                            "type": "boolean"
                          },
                          "coverTimestampMs": {
                            "type": [
                              "number",
                              "null"
                            ]
                          }
                        }
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "youtubeOptions": {
                    "anyOf": [
                      {
                        "type": "object",
                        "properties": {
                          "categoryId": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "madeForKids": {
                            "type": "boolean"
                          },
                          "playlistId": {
                            "description": "发布成功后把视频加入的播放列表 id(须属于目标频道;稍后观看等系统列表不可用;加入失败不影响发布)",
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        }
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "YouTube category, made-for-kids flag, and the id of a playlist to add the video to after publishing (must belong to the target channel; system lists such as Watch Later are unavailable; a failed add does not fail the publish). / YouTube 分类、「面向儿童」标记,以及发布成功后把视频加入的播放列表 id(须属于目标频道;稍后观看等系统列表不可用;加入失败不影响发布)。"
                  },
                  "threadsOptions": {
                    "description": "Threads options: replyControl — who can reply (everyone / followers_only / accounts_you_follow / mentioned_only / parent_post_author_only; default everyone); topicTag — topic tag (1–50 characters, no '.' or '&'); linkAttachment — link card (an http(s) URL; text-only posts only; counts toward the 5-link limit); replyApprovals — reply approvals (true = replies from others stay hidden until the author approves them; default off). / Threads 发布选项:replyControl 谁可以回复(everyone / followers_only / accounts_you_follow / mentioned_only / parent_post_author_only,缺省所有人);topicTag 话题标签(1–50 字,不能含 . 与 &);linkAttachment 链接卡片(http(s) 链接,只对纯文字帖生效,计入每帖 5 个链接的上限);replyApprovals 回复审批(true = 别人的回复要作者批准才公开,缺省关)。",
                    "anyOf": [
                      {
                        "type": "object",
                        "properties": {
                          "replyControl": {
                            "description": "谁可以回复;不给 = everyone(所有人)",
                            "anyOf": [
                              {
                                "type": "string",
                                "enum": [
                                  "everyone",
                                  "accounts_you_follow",
                                  "mentioned_only",
                                  "parent_post_author_only",
                                  "followers_only"
                                ]
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "topicTag": {
                            "description": "话题标签:1–50 字,不能含 . 与 &;开头的 # 会被去掉;不给 = Threads 取正文里第一个 #标签",
                            "anyOf": [
                              {
                                "type": "string",
                                "maxLength": 100
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "linkAttachment": {
                            "description": "链接卡片:公开可访问的 http(s) 链接,**只对纯文字帖生效**,与正文链接一起计入每帖 5 个链接的上限",
                            "anyOf": [
                              {
                                "type": "string",
                                "maxLength": 2048
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "replyApprovals": {
                            "description": "回复审批:true = 别人的回复要作者在 popkit 评论抽屉里批准才公开;不给 = 关",
                            "type": [
                              "boolean",
                              "null"
                            ]
                          }
                        },
                        "additionalProperties": false
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "clientRequestId": {
                    "description": "Idempotency key (8–128 chars of [A-Za-z0-9_.:-]); prefer the `Idempotency-Key` header, which wins over the body. Required for the publish tier. / 幂等键(8–128 chars of [A-Za-z0-9_.:-]);推荐放在 `Idempotency-Key` 请求头,头与 body 都给时以头为准。publish 档必填。",
                    "type": "string"
                  }
                },
                "required": [
                  "action",
                  "content",
                  "platforms"
                ],
                "additionalProperties": false
              },
              "example": {
                "action": "schedule",
                "content": "新功能上线:一次创作,发布到所有平台 🚀",
                "platforms": [
                  "x",
                  "instagram"
                ],
                "scheduledAt": "2026-09-21T18:00:00+08:00",
                "media": [
                  {
                    "kind": "image",
                    "dataUrl": "https://storage.googleapis.com/…/cover.jpg"
                  }
                ],
                "variants": [
                  {
                    "platform": "x",
                    "content": "新功能上线 🚀 一次创作,发布到所有平台 → popkit.loxily.com"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Create a post",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "Scheduled / 排期成功",
                    "value": {
                      "data": {
                        "postId": "pst_01J8ZK3R7QW6M2",
                        "status": "scheduled",
                        "scheduledAt": "2026-09-21T10:00:00.000Z",
                        "deepLink": "https://popkit.loxily.com/calendar?post=pst_01J8ZK3R7QW6M2"
                      },
                      "meta": {
                        "requestId": "3f9c1e2a-7d4b-4c1e-9a6f-0b2d8e5c7a11"
                      }
                    }
                  },
                  "example2": {
                    "summary": "publish tier: confirmation required / publish 档:需第二步确认",
                    "value": {
                      "data": {
                        "postId": "pst_01J8ZK3R7QW6M2",
                        "status": "draft",
                        "scheduledAt": null,
                        "deepLink": "https://popkit.loxily.com/content?post=pst_01J8ZK3R7QW6M2",
                        "confirm": {
                          "token": "cf_9u2K…",
                          "expiresInSec": 600
                        },
                        "note": "Draft saved. Call POST /v1/posts/{postId}/confirm with this token within 10 minutes to publish."
                      },
                      "meta": {
                        "requestId": "3f9c1e2a-7d4b-4c1e-9a6f-0b2d8e5c7a12"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "plan_limit — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "error1": {
                    "summary": "Tier not allowed for this credential / 落点不在凭证白名单",
                    "value": {
                      "error": {
                        "code": "forbidden",
                        "message": "credential is not allowed to use action \"publish\"",
                        "params": {
                          "action": "publish"
                        }
                      },
                      "meta": {
                        "requestId": "3f9c1e2a-7d4b-4c1e-9a6f-0b2d8e5c7a13"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "no_account / needs_reauth / replay — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "502": {
            "description": "platform_error — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "external_writes_off — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "posts:write",
          "schedule:write",
          "publish:execute"
        ],
        "x-popkit-rate-class": "write|publish",
        "x-popkit-idempotency": "optional|required",
        "x-popkit-read-only": false,
        "x-popkit-mcp-tool": "create_post"
      }
    },
    "/posts/{postId}": {
      "get": {
        "operationId": "get_post",
        "tags": [
          "Posts"
        ],
        "summary": "Get a post / 内容详情",
        "description": "One post in full: body, per-platform variants, media and each target account's delivery result (with machine-readable failure codes). Poll it after publishing. Only posts of this workspace are visible.\n\n一条内容的完整信息:主文案、逐平台变体、媒体、每个目标账号的发布结果(含失败机器码)。发布后用它轮询各账号结果。只能取本工作区的内容。",
        "security": [
          {
            "bearerAuth": [
              "posts:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "Post id (postId from list / create). / 内容 id(列表 / 新建返回的 postId)。",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 36
            }
          },
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "responses": {
          "200": {
            "description": "Get a post",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "OK / 成功",
                    "value": {
                      "data": {
                        "id": "pst_01J8ZK3R7QW6M2",
                        "title": null,
                        "content": "新功能上线:一次创作,发布到所有平台 🚀",
                        "platforms": [
                          "x",
                          "instagram"
                        ],
                        "targetAccountIds": [
                          "acc_01J8X…",
                          "acc_01J8Y…"
                        ],
                        "status": "published",
                        "source": "api",
                        "scheduledAt": "2026-09-21T10:00:00.000Z",
                        "publishedAt": "2026-09-21T10:00:04.000Z",
                        "createdBy": "usr_01J7…",
                        "createdAt": "2026-09-20T08:12:31.000Z",
                        "updatedAt": "2026-09-20T08:12:31.000Z",
                        "deepLink": "https://popkit.loxily.com/calendar?post=pst_01J8ZK3R7QW6M2",
                        "variants": [
                          {
                            "platform": "x",
                            "content": "新功能上线 🚀 一次创作,发布到所有平台 → popkit.loxily.com",
                            "title": null,
                            "digest": "b1e4…"
                          }
                        ],
                        "media": [
                          {
                            "kind": "image",
                            "url": "https://storage.googleapis.com/…/cover.jpg",
                            "inline": false,
                            "platform": null,
                            "durationSec": null,
                            "sortOrder": 0
                          }
                        ],
                        "targets": [
                          {
                            "id": "tgt_01J8…",
                            "accountId": "acc_01J8XQ5M9V2R",
                            "platform": "x",
                            "status": "published",
                            "permalink": "https://x.com/popkit/status/18…",
                            "publishedAt": "2026-09-21T10:00:04.000Z",
                            "attemptCount": 1,
                            "errorCode": null,
                            "errorParams": null,
                            "errorText": null
                          },
                          {
                            "id": "tgt_01J8…",
                            "accountId": "acc_01J8Y…",
                            "platform": "instagram",
                            "status": "failed",
                            "permalink": null,
                            "publishedAt": null,
                            "attemptCount": 1,
                            "errorCode": "media_required",
                            "errorParams": null,
                            "errorText": null
                          }
                        ]
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c64"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "error1": {
                    "summary": "Not found / 不存在或不属于本工作区",
                    "value": {
                      "error": {
                        "code": "not_found",
                        "message": "post not found",
                        "params": null
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c65"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "posts:read"
        ],
        "x-popkit-rate-class": "read",
        "x-popkit-idempotency": "none",
        "x-popkit-read-only": true,
        "x-popkit-mcp-tool": "get_post"
      },
      "patch": {
        "operationId": "update_post",
        "tags": [
          "Posts"
        ],
        "summary": "Update a post / 修改内容",
        "description": "Edit an existing post and choose its tier (same rules as create). Only drafts, rejected and scheduled posts can be edited; posts in review are locked and published posts are immutable. Omitting media / targetAccountIds keeps the current values; an empty list clears them.\n\n修改一条既有内容并决定落点(规则同新建)。只有草稿、被退回、已排期的内容可改;待审内容被锁定,已发布内容不可改。省略 media / targetAccountIds 即保留原值,传空数组才是清空 / 改回全部账号。",
        "security": [
          {
            "bearerAuth": [
              "posts:write",
              "schedule:write",
              "publish:execute"
            ]
          }
        ],
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "Post id. / 内容 id。",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 36
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "schedule",
                      "publish"
                    ],
                    "description": "Tier after this edit; same meaning as on create. / 落点档位,含义同新建;决定这次修改之后内容停在哪一档。"
                  },
                  "content": {
                    "type": "string",
                    "description": "Main copy; may be an empty string when every target platform has its own copy in variants. / 主文案;每个目标渠道都在 variants 里给了自己的文案时可留空字符串。"
                  },
                  "platforms": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "x",
                        "youtube",
                        "instagram",
                        "tiktok",
                        "facebook",
                        "douyin",
                        "xiaohongshu",
                        "wechat_mp",
                        "threads"
                      ]
                    },
                    "description": "Target platforms. / 目标平台。"
                  },
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Title (required for YouTube). / 标题(YouTube 必填)。"
                  },
                  "targetAccountIds": {
                    "description": "Omit = keep the current selection; a list replaces it; an empty list = back to \"all usable accounts\". / 省略 = 保留原有选号;传了就整组替换;传空数组 = 改回「全部可用账号」。",
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 36
                    }
                  },
                  "scheduledAt": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Required for schedule; must carry a timezone (Z or ±HH:MM) and be at least 60 s in the future; on a draft it is only the planned date on the calendar. / schedule 档必填,必须带时区(Z 或 ±HH:MM)且至少比现在晚 60 秒;draft 档给了只作日历上的计划日期。"
                  },
                  "media": {
                    "description": "Omit = keep existing media; empty list = clear. kind is image / cover / video; dataUrl must be a popkit storage URL (get one via POST /v1/media), image data URLs are accepted for images / covers; platform marks a per-platform override. / 省略 = 保留原有媒体,空数组 = 清空。kind 为 image / cover / video;dataUrl 必须是本站对象存储 URL(先用 POST /v1/media 换取),图片 / 封面也可用 image data URL;platform 表示只给某平台用的覆写媒体。",
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "kind": {
                          "type": "string",
                          "enum": [
                            "image",
                            "cover",
                            "video"
                          ]
                        },
                        "dataUrl": {
                          "type": "string"
                        },
                        "platform": {
                          "type": "string",
                          "enum": [
                            "x",
                            "youtube",
                            "instagram",
                            "tiktok",
                            "facebook",
                            "douyin",
                            "xiaohongshu",
                            "wechat_mp",
                            "threads"
                          ]
                        },
                        "durationSec": {
                          "type": [
                            "number",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "kind",
                        "dataUrl"
                      ]
                    }
                  },
                  "variants": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "platform": {
                          "type": "string",
                          "enum": [
                            "x",
                            "youtube",
                            "instagram",
                            "tiktok",
                            "facebook",
                            "douyin",
                            "xiaohongshu",
                            "wechat_mp",
                            "threads"
                          ]
                        },
                        "content": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "title": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "platform"
                      ]
                    },
                    "description": "Per-platform copy overrides; send only the platforms that differ. / 逐平台文案覆写;只传与主文案不同的。"
                  },
                  "tiktokOptions": {
                    "description": "Omit = keep existing settings; null = clear. TikTok Direct Post compliance options: privacyLevel (must be chosen by a person when posting to TikTok), allowComment, allowDuet, allowStitch, commercial, yourBrand, brandedContent, coverTimestampMs. / 省略 = 保留原有设置;null = 清空。TikTok Direct Post 合规选项:privacyLevel(发 TikTok 时必须由人选定)、allowComment、allowDuet、allowStitch、commercial、yourBrand、brandedContent、coverTimestampMs。",
                    "anyOf": [
                      {
                        "type": "object",
                        "properties": {},
                        "additionalProperties": {}
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "youtubeOptions": {
                    "description": "Omit = keep existing settings; null = clear. YouTube category, made-for-kids flag, and the id of a playlist to add the video to after publishing (must belong to the target channel; system lists such as Watch Later are unavailable; a failed add does not fail the publish). / 省略 = 保留原有设置;null = 清空。YouTube 分类、「面向儿童」标记,以及发布成功后把视频加入的播放列表 id(须属于目标频道;稍后观看等系统列表不可用;加入失败不影响发布)。",
                    "anyOf": [
                      {
                        "type": "object",
                        "properties": {},
                        "additionalProperties": {}
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "threadsOptions": {
                    "description": "Omit = keep existing settings; null = clear. Threads options: replyControl — who can reply (everyone / followers_only / accounts_you_follow / mentioned_only / parent_post_author_only; default everyone); topicTag — topic tag (1–50 characters, no '.' or '&'); linkAttachment — link card (an http(s) URL; text-only posts only; counts toward the 5-link limit); replyApprovals — reply approvals (true = replies from others stay hidden until the author approves them; default off). / 省略 = 保留原有设置;null = 清空。Threads 发布选项:replyControl 谁可以回复(everyone / followers_only / accounts_you_follow / mentioned_only / parent_post_author_only,缺省所有人);topicTag 话题标签(1–50 字,不能含 . 与 &);linkAttachment 链接卡片(http(s) 链接,只对纯文字帖生效,计入每帖 5 个链接的上限);replyApprovals 回复审批(true = 别人的回复要作者批准才公开,缺省关)。",
                    "anyOf": [
                      {
                        "type": "object",
                        "properties": {
                          "replyControl": {
                            "description": "谁可以回复;不给 = everyone(所有人)",
                            "anyOf": [
                              {
                                "type": "string",
                                "enum": [
                                  "everyone",
                                  "accounts_you_follow",
                                  "mentioned_only",
                                  "parent_post_author_only",
                                  "followers_only"
                                ]
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "topicTag": {
                            "description": "话题标签:1–50 字,不能含 . 与 &;开头的 # 会被去掉;不给 = Threads 取正文里第一个 #标签",
                            "anyOf": [
                              {
                                "type": "string",
                                "maxLength": 100
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "linkAttachment": {
                            "description": "链接卡片:公开可访问的 http(s) 链接,**只对纯文字帖生效**,与正文链接一起计入每帖 5 个链接的上限",
                            "anyOf": [
                              {
                                "type": "string",
                                "maxLength": 2048
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "replyApprovals": {
                            "description": "回复审批:true = 别人的回复要作者在 popkit 评论抽屉里批准才公开;不给 = 关",
                            "type": [
                              "boolean",
                              "null"
                            ]
                          }
                        },
                        "additionalProperties": false
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "clientRequestId": {
                    "type": "string",
                    "description": "Idempotency key (8–128 chars of [A-Za-z0-9_.:-]); prefer the `Idempotency-Key` header, which wins over the body. Required for the publish tier. / 幂等键(8–128 chars of [A-Za-z0-9_.:-]);推荐放在 `Idempotency-Key` 请求头,头与 body 都给时以头为准。publish 档必填。"
                  }
                },
                "required": [
                  "action",
                  "content",
                  "platforms"
                ],
                "additionalProperties": false
              },
              "example": {
                "action": "schedule",
                "content": "文案改了一版 ✨",
                "platforms": [
                  "x",
                  "instagram"
                ],
                "scheduledAt": "2026-09-22T09:30:00+08:00"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Update a post",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "Scheduled / 排期成功",
                    "value": {
                      "data": {
                        "postId": "pst_01J8ZK3R7QW6M2",
                        "status": "scheduled",
                        "scheduledAt": "2026-09-21T10:00:00.000Z",
                        "deepLink": "https://popkit.loxily.com/calendar?post=pst_01J8ZK3R7QW6M2"
                      },
                      "meta": {
                        "requestId": "3f9c1e2a-7d4b-4c1e-9a6f-0b2d8e5c7a11"
                      }
                    }
                  },
                  "example2": {
                    "summary": "publish tier: confirmation required / publish 档:需第二步确认",
                    "value": {
                      "data": {
                        "postId": "pst_01J8ZK3R7QW6M2",
                        "status": "draft",
                        "scheduledAt": null,
                        "deepLink": "https://popkit.loxily.com/content?post=pst_01J8ZK3R7QW6M2",
                        "confirm": {
                          "token": "cf_9u2K…",
                          "expiresInSec": 600
                        },
                        "note": "Draft saved. Call POST /v1/posts/{postId}/confirm with this token within 10 minutes to publish."
                      },
                      "meta": {
                        "requestId": "3f9c1e2a-7d4b-4c1e-9a6f-0b2d8e5c7a12"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "plan_limit — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "error1": {
                    "summary": "Tier not allowed for this credential / 落点不在凭证白名单",
                    "value": {
                      "error": {
                        "code": "forbidden",
                        "message": "credential is not allowed to use action \"publish\"",
                        "params": {
                          "action": "publish"
                        }
                      },
                      "meta": {
                        "requestId": "3f9c1e2a-7d4b-4c1e-9a6f-0b2d8e5c7a13"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "no_account / needs_reauth / replay — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "502": {
            "description": "platform_error — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "external_writes_off — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "posts:write",
          "schedule:write",
          "publish:execute"
        ],
        "x-popkit-rate-class": "write|publish",
        "x-popkit-idempotency": "optional|required",
        "x-popkit-read-only": false,
        "x-popkit-mcp-tool": "update_post"
      }
    },
    "/posts/precheck": {
      "post": {
        "operationId": "check_publish_constraints",
        "tags": [
          "Posts"
        ],
        "summary": "Publish precheck / 发布预检",
        "description": "Dry-run a piece of copy + media per platform and per account, without writing or publishing: text length, media specs, account authorization, 24-hour rate caps, TikTok / YouTube required fields. Same rules as a real publish; only ok=true is worth sending. Manual channels warn but never block.\n\n对一份文案 + 媒体做逐平台、逐账号的预检,不写库、不外发:文本长度、媒体规格、账号授权状态、24 小时频控、TikTok / YouTube 必填项。口径与真实发布完全一致 —— ok=true 才值得走发布流程。手动渠道只给 warning,不阻断。",
        "security": [
          {
            "bearerAuth": [
              "posts:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "platforms": {
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "x",
                        "youtube",
                        "instagram",
                        "tiktok",
                        "facebook",
                        "douyin",
                        "xiaohongshu",
                        "wechat_mp",
                        "threads"
                      ]
                    },
                    "description": "Target platforms, at least one. / 目标平台,至少一个。"
                  },
                  "content": {
                    "type": "string",
                    "description": "Main copy; may be empty when every platform has a variant. / 主文案;逐渠道都给了 variants 时可留空。"
                  },
                  "title": {
                    "description": "Title (required for YouTube). / 标题(YouTube 必填)。",
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "variants": {
                    "description": "Per-platform overrides; the check measures override ?? main copy. / 逐平台文案覆写;预检按「覆写 ?? 主文案」量字数。",
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "platform": {
                          "type": "string",
                          "enum": [
                            "x",
                            "youtube",
                            "instagram",
                            "tiktok",
                            "facebook",
                            "douyin",
                            "xiaohongshu",
                            "wechat_mp",
                            "threads"
                          ]
                        },
                        "content": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "title": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "platform"
                      ]
                    }
                  },
                  "targetAccountIds": {
                    "description": "Target accounts; omit = all usable accounts per platform, max 20. / 目标账号;不给 = 每平台全部可用账号,上限 20。",
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "media": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "kind": {
                          "type": "string",
                          "enum": [
                            "image",
                            "video"
                          ]
                        },
                        "url": {
                          "type": "string",
                          "description": "必须是本站对象存储 URL(图片/封面可用 image data URL)"
                        },
                        "bytes": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "durationSec": {
                          "type": [
                            "number",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "kind",
                        "url"
                      ]
                    },
                    "description": "kind is image / video; url must be a popkit storage URL. / kind 为 image / video;url 必须是本站对象存储 URL。"
                  },
                  "tiktokOptions": {
                    "description": "privacyLevel must be chosen by a person for TikTok. / 发 TikTok 时 privacyLevel 必须由人选定。",
                    "anyOf": [
                      {
                        "type": "object",
                        "properties": {
                          "privacyLevel": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        }
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "threadsOptions": {
                    "description": "A text-only Threads post's link card counts toward the 5-link limit. / Threads 纯文字帖的链接卡片计入每帖 5 个链接的上限。",
                    "anyOf": [
                      {
                        "type": "object",
                        "properties": {
                          "replyControl": {
                            "description": "谁可以回复;不给 = everyone(所有人)",
                            "anyOf": [
                              {
                                "type": "string",
                                "enum": [
                                  "everyone",
                                  "accounts_you_follow",
                                  "mentioned_only",
                                  "parent_post_author_only",
                                  "followers_only"
                                ]
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "topicTag": {
                            "description": "话题标签:1–50 字,不能含 . 与 &;开头的 # 会被去掉;不给 = Threads 取正文里第一个 #标签",
                            "anyOf": [
                              {
                                "type": "string",
                                "maxLength": 100
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "linkAttachment": {
                            "description": "链接卡片:公开可访问的 http(s) 链接,**只对纯文字帖生效**,与正文链接一起计入每帖 5 个链接的上限",
                            "anyOf": [
                              {
                                "type": "string",
                                "maxLength": 2048
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "replyApprovals": {
                            "description": "回复审批:true = 别人的回复要作者在 popkit 评论抽屉里批准才公开;不给 = 关",
                            "type": [
                              "boolean",
                              "null"
                            ]
                          }
                        },
                        "additionalProperties": false
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "required": [
                  "platforms",
                  "content"
                ],
                "additionalProperties": false
              },
              "example": {
                "platforms": [
                  "x",
                  "youtube"
                ],
                "content": "本周更新速览",
                "title": "本周更新速览",
                "media": [
                  {
                    "kind": "video",
                    "url": "https://storage.googleapis.com/…/weekly.mp4",
                    "bytes": 48213000,
                    "durationSec": 62
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Publish precheck",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "OK (with a blocker) / 成功(有阻断项)",
                    "value": {
                      "data": {
                        "ok": false,
                        "platforms": [
                          {
                            "platform": "x",
                            "blocker": null,
                            "accounts": [
                              {
                                "accountId": "acc_01J8XQ5M9V2R",
                                "handle": "@popkit",
                                "textMax": 280,
                                "ok": true,
                                "error": null,
                                "warning": null
                              }
                            ],
                            "expiredAccounts": []
                          },
                          {
                            "platform": "youtube",
                            "blocker": "needs_reauth",
                            "accounts": [],
                            "expiredAccounts": [
                              {
                                "accountId": "acc_01J8Z…",
                                "handle": "popkit 频道"
                              }
                            ]
                          }
                        ]
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c66"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "posts:read"
        ],
        "x-popkit-rate-class": "read",
        "x-popkit-idempotency": "none",
        "x-popkit-read-only": true,
        "x-popkit-mcp-tool": "check_publish_constraints"
      }
    },
    "/posts/{postId}/reschedule": {
      "post": {
        "operationId": "reschedule_post",
        "tags": [
          "Posts"
        ],
        "summary": "Reschedule / 改期",
        "description": "Change a post's time. For scheduled posts this is the real publish time (needs the schedule tier and counts against the monthly quota); for drafts only the planned date on the calendar. Other statuses cannot be rescheduled. The time must carry a timezone and lie in the future.\n\n改内容的时间。已排期内容改的是真实发布时间(需发布权与排期档凭证,走月配额);草稿改的只是日历上的计划日期。其它状态不可改期。时间必须带时区且不能是过去。",
        "security": [
          {
            "bearerAuth": [
              "schedule:write",
              "posts:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "Post id. / 内容 id。",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 36
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scheduledAt": {
                    "type": "string",
                    "description": "The new time. / 新的时间。"
                  },
                  "clientRequestId": {
                    "type": "string",
                    "description": "Idempotency key (8–128 chars of [A-Za-z0-9_.:-]); prefer the `Idempotency-Key` header, which wins over the body. / 幂等键(8–128 chars of [A-Za-z0-9_.:-]);推荐放在 `Idempotency-Key` 请求头,头与 body 都给时以头为准。"
                  }
                },
                "required": [
                  "scheduledAt"
                ],
                "additionalProperties": false
              },
              "example": {
                "scheduledAt": "2026-09-23T20:00:00+08:00"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reschedule",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "OK / 成功",
                    "value": {
                      "data": {
                        "postId": "pst_01J8ZK3R7QW6M2",
                        "status": "scheduled",
                        "scheduledAt": "2026-09-23T12:00:00.000Z",
                        "deepLink": "https://popkit.loxily.com/calendar?post=pst_01J8ZK3R7QW6M2"
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c67"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "plan_limit — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "no_account / needs_reauth / replay — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "502": {
            "description": "platform_error — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "external_writes_off — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "schedule:write",
          "posts:write"
        ],
        "x-popkit-rate-class": "write",
        "x-popkit-idempotency": "optional",
        "x-popkit-read-only": false,
        "x-popkit-mcp-tool": "reschedule_post"
      }
    },
    "/posts/{postId}/submit": {
      "post": {
        "operationId": "submit_for_approval",
        "tags": [
          "Posts"
        ],
        "summary": "Submit for approval / 提交审批",
        "description": "Send a draft into the approval flow for a workspace admin to review — the natural end point for draft-only credentials. The post becomes reviewing and is locked; once approved it stays approved and is not sent automatically: someone with the publish tier must call the publish endpoint (or click Publish in the app). precheckOk tells you whether it could go out right now.\n\n把一条草稿送进审批流,交给工作区的管理员审 —— 只有草稿权限的凭证把内容推进下去的正常终点。提交后内容进入待审并被锁定;通过后停在「已批准」,不会自动发出,需要有发布权的人再调发布端点(或在站内点「去发布」)。precheckOk 告诉你这篇当前发不发得出去。",
        "security": [
          {
            "bearerAuth": [
              "approvals:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "Draft id. / 草稿 id。",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 36
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "clientRequestId": {
                    "type": "string",
                    "description": "Idempotency key (8–128 chars of [A-Za-z0-9_.:-]); prefer the `Idempotency-Key` header, which wins over the body. / 幂等键(8–128 chars of [A-Za-z0-9_.:-]);推荐放在 `Idempotency-Key` 请求头,头与 body 都给时以头为准。"
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Submit for approval",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "OK / 成功",
                    "value": {
                      "data": {
                        "postId": "pst_01J8ZK3R7QW6M2",
                        "status": "reviewing",
                        "approvalId": "apr_01J8ZM…",
                        "deepLink": "https://popkit.loxily.com/approvals",
                        "precheckOk": true
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c68"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "plan_limit — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "no_account / needs_reauth / replay — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "502": {
            "description": "platform_error — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "external_writes_off — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "approvals:write"
        ],
        "x-popkit-rate-class": "write",
        "x-popkit-idempotency": "optional",
        "x-popkit-read-only": false,
        "x-popkit-mcp-tool": "submit_for_approval"
      }
    },
    "/posts/{postId}/cover-suggestions": {
      "post": {
        "operationId": "suggest_covers",
        "tags": [
          "Posts"
        ],
        "summary": "Suggest covers / 推荐封面",
        "description": "Suggest covers from a post's media: frames picked from its video, or — without a video — the best first image among 2+ images, ranked (rank 1 is best) with a one-line reason. Videos take two calls: the first starts frame extraction (status queued / running, about 10–30 s), call again after retryAfterSec to get ranked candidates; image posts come back ranked right away. Idempotent per media, so repeat calls are not billed again and never use the monthly AI quota. Only the post's shared media is used, and it must be stored on popkit. This endpoint changes nothing; apply a pick with Set cover.\n\n按内容的素材推荐封面:有视频就从视频里抽帧挑图,否则在 2 张以上的配图里挑首图,按推荐度排好(rank 1 最推荐)并附一句理由。视频要调两次:第一次触发抽帧(status 为 queued / running,约 10–30 秒),按 retryAfterSec 再调一次拿到 ranked;图文当场返回 ranked。同一份素材幂等,重复调用不会重复计费,也不扣 AI 月配额。只看内容的通用媒体,媒体必须已存在本站。本端点不改动内容,选好后用「设为封面」应用。",
        "security": [
          {
            "bearerAuth": [
              "posts:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "Post id. / 内容 id。",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 36,
              "description": "要推荐封面的内容"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "language": {
                    "description": "Language of the reasons; defaults to Chinese and leaves existing reasons alone. If given and different from the existing reasons, only the reasons are rewritten — the ranking stays. / 推荐理由的语言;不给 = 中文,且不改写已有推荐的理由。给了且与已有理由的语言不同,会只重写理由、名次不变。",
                    "anyOf": [
                      {
                        "type": "string",
                        "enum": [
                          "zh",
                          "en"
                        ]
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "clientRequestId": {
                    "type": "string",
                    "description": "Idempotency key (8–128 chars of [A-Za-z0-9_.:-]); prefer the `Idempotency-Key` header, which wins over the body. / 幂等键(8–128 chars of [A-Za-z0-9_.:-]);推荐放在 `Idempotency-Key` 请求头,头与 body 都给时以头为准。"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "language": "zh"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Suggest covers",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "Video: extracting / 视频:抽帧中",
                    "value": {
                      "data": {
                        "postId": "pst_01J8ZK3R7QW6M2",
                        "suggestionId": "3f2b9c1e-6d0a-4e8b-9a7c-5b1d2e3f4a60",
                        "status": "queued",
                        "sourceKind": "video",
                        "engine": null,
                        "candidates": [],
                        "retryAfterSec": 5,
                        "next": "frames are still being extracted from the video (usually 10–30 s); call suggest_covers again with the same postId in 5s"
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c81"
                      }
                    }
                  },
                  "example2": {
                    "summary": "Ranked / 已排好",
                    "value": {
                      "data": {
                        "postId": "pst_01J8ZK3R7QW6M2",
                        "suggestionId": "3f2b9c1e-6d0a-4e8b-9a7c-5b1d2e3f4a60",
                        "status": "ranked",
                        "sourceKind": "video",
                        "engine": "llm",
                        "candidates": [
                          {
                            "id": "f03",
                            "rank": 1,
                            "url": "https://storage.googleapis.com/socialhub-media/covers/3f2b9c1e…/f03.jpg",
                            "reason": "人物正脸清晰、画面明亮,竖版构图居中",
                            "timestampMs": 12000,
                            "width": 720,
                            "height": 1280
                          },
                          {
                            "id": "f07",
                            "rank": 2,
                            "url": "https://storage.googleapis.com/socialhub-media/covers/3f2b9c1e…/f07.jpg",
                            "reason": "动作瞬间有张力,但主体略偏右",
                            "timestampMs": 31500,
                            "width": 720,
                            "height": 1280
                          }
                        ],
                        "retryAfterSec": null,
                        "next": "candidates are sorted best first; call set_post_cover with suggestionId and a candidate id to apply one"
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c82"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "error1": {
                    "summary": "No usable media / 没有可推荐的素材",
                    "value": {
                      "error": {
                        "code": "invalid_input",
                        "message": "this post has neither a video nor at least 2 images in its shared media — there is nothing to suggest a cover from",
                        "params": {
                          "minImages": 2
                        }
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c83"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "plan_limit — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "no_account / needs_reauth / replay — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "502": {
            "description": "platform_error — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "external_writes_off — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "posts:write"
        ],
        "x-popkit-rate-class": "write",
        "x-popkit-idempotency": "optional",
        "x-popkit-read-only": false,
        "x-popkit-mcp-tool": "suggest_covers"
      }
    },
    "/posts/{postId}/cover": {
      "post": {
        "operationId": "set_post_cover",
        "tags": [
          "Posts"
        ],
        "summary": "Set cover / 设为封面",
        "description": "Apply one candidate from Suggest covers as a cover. Video posts: the image is saved separately on YouTube, Instagram, Facebook, and each manual channel (changing one later in the composer does not change the others); YouTube / Instagram / Facebook set it via API after publishing, and manual channels include it in their kit. When TikTok is a target, its cover frame is set to that candidate's timestamp. Image posts: the image is moved to the first position (the first image is the cover).\n\n把「推荐封面」返回的某张候选设为封面。视频内容:这张图会分别记到 YouTube、Instagram、Facebook 和每个手动渠道上(之后在创作页改某一个不会带动其它渠道);发布时 YouTube / Instagram / Facebook 经接口设封面,手动渠道放进各自的素材包。目标含 TikTok 时顺带把 TikTok 的封面帧定到这张的时间点;图文内容:把这张挪成第一张图(图文的封面就是首图)。",
        "security": [
          {
            "bearerAuth": [
              "posts:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "Post id. / 内容 id。",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 36
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "suggestionId": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 36,
                    "description": "suggestionId from Suggest covers. / 「推荐封面」返回的 suggestionId。"
                  },
                  "candidateId": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 16,
                    "description": "candidates[].id from Suggest covers. / 「推荐封面」返回的 candidates[].id。"
                  },
                  "clientRequestId": {
                    "type": "string",
                    "description": "Idempotency key (8–128 chars of [A-Za-z0-9_.:-]); prefer the `Idempotency-Key` header, which wins over the body. / 幂等键(8–128 chars of [A-Za-z0-9_.:-]);推荐放在 `Idempotency-Key` 请求头,头与 body 都给时以头为准。"
                  }
                },
                "required": [
                  "suggestionId",
                  "candidateId"
                ],
                "additionalProperties": false
              },
              "example": {
                "suggestionId": "3f2b9c1e-6d0a-4e8b-9a7c-5b1d2e3f4a60",
                "candidateId": "f03"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Set cover",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "Video post / 视频内容",
                    "value": {
                      "data": {
                        "postId": "pst_01J8ZK3R7QW6M2",
                        "suggestionId": "3f2b9c1e-6d0a-4e8b-9a7c-5b1d2e3f4a60",
                        "candidateId": "f03",
                        "appliedAs": "cover",
                        "coverUrl": "https://storage.googleapis.com/socialhub-media/posts/u_01J7…/7c1e….jpg",
                        "tiktokCoverTimestampMs": 12000,
                        "deepLink": "https://popkit.loxily.com/composer?draft=pst_01J8ZK3R7QW6M2"
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c84"
                      }
                    }
                  },
                  "example2": {
                    "summary": "Image post / 图文内容",
                    "value": {
                      "data": {
                        "postId": "pst_01J8ZK9T2BC4N7",
                        "suggestionId": "8d4e2a7b-1c9f-4b3e-8a6d-2f0c1e9b7a55",
                        "candidateId": "i03",
                        "appliedAs": "first_image",
                        "coverUrl": "https://storage.googleapis.com/socialhub-media/posts/u_01J7…/2b9a….jpg",
                        "tiktokCoverTimestampMs": null,
                        "deepLink": "https://popkit.loxily.com/composer?draft=pst_01J8ZK9T2BC4N7"
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c85"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "error1": {
                    "summary": "Media changed / 素材已变",
                    "value": {
                      "error": {
                        "code": "invalid_input",
                        "message": "this suggestion was made for different media than the post has now; call suggest_covers again",
                        "params": {
                          "reason": "stale_suggestion"
                        }
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c86"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "plan_limit — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "no_account / needs_reauth / replay — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "502": {
            "description": "platform_error — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "external_writes_off — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "posts:write"
        ],
        "x-popkit-rate-class": "write",
        "x-popkit-idempotency": "optional",
        "x-popkit-read-only": false,
        "x-popkit-mcp-tool": "set_post_cover"
      }
    },
    "/posts/{postId}/confirm": {
      "post": {
        "operationId": "confirm_publish",
        "tags": [
          "Publishing"
        ],
        "summary": "Confirm publish / 确认发布",
        "description": "Second step of two-step confirmation: use the confirmToken returned by create / update to actually publish. The token is single-use, valid for ten minutes and bound to the content at that moment — editing in between voids it. Account status, safety valve and rate caps are re-checked on confirm.\n\n两步确认的第二步:拿新建 / 修改返回的 confirmToken 真正对外发布。令牌一次性、十分钟内有效,并绑定当时的内容 —— 内容在两步之间被改过,令牌即失效。确认时会重新校验账号状态、安全阀与频控。",
        "security": [
          {
            "bearerAuth": [
              "publish:execute"
            ]
          }
        ],
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "Post id. / 内容 id。",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 36
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "confirmToken": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 64,
                    "description": "confirm.token from create / update. / 新建 / 修改返回的 confirm.token。"
                  },
                  "clientRequestId": {
                    "type": "string",
                    "description": "Idempotency key (8–128 chars of [A-Za-z0-9_.:-]); prefer the `Idempotency-Key` header, which wins over the body. / 幂等键(8–128 chars of [A-Za-z0-9_.:-]);推荐放在 `Idempotency-Key` 请求头,头与 body 都给时以头为准。"
                  }
                },
                "required": [
                  "confirmToken"
                ],
                "additionalProperties": false
              },
              "example": {
                "confirmToken": "cf_9u2K…"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirm publish",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "Publishing / 已进入发布",
                    "value": {
                      "data": {
                        "postId": "pst_01J8ZK3R7QW6M2",
                        "status": "publishing",
                        "scheduledAt": null,
                        "deepLink": "https://popkit.loxily.com/history?post=pst_01J8ZK3R7QW6M2"
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c69"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "error1": {
                    "summary": "Token invalid / 令牌失效",
                    "value": {
                      "error": {
                        "code": "invalid_input",
                        "message": "confirm token is invalid, expired or no longer matches the post",
                        "params": null
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c70"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "plan_limit — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "no_account / needs_reauth / replay — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "502": {
            "description": "platform_error — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "external_writes_off — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "publish:execute"
        ],
        "x-popkit-rate-class": "publish",
        "x-popkit-idempotency": "required",
        "x-popkit-read-only": false,
        "x-popkit-mcp-tool": "confirm_publish"
      }
    },
    "/posts/{postId}/publish": {
      "post": {
        "operationId": "publish_post",
        "tags": [
          "Publishing"
        ],
        "summary": "Publish / retry / 发布 / 重投",
        "description": "Publish an existing post: send an approved post, or retry a failed one (optionally a single account via accountId). Passing scheduledAt for an approved post turns it into a scheduled publish (schedule tier). Any other status is refused.\n\n对已存在的内容执行发布:审批通过(approved)的内容出站,或失败(failed)内容重试(可指定 accountId 只重投一个账号)。给 approved 内容传 scheduledAt 则改为定时发布(走排期档权限)。其它状态一律拒绝。",
        "security": [
          {
            "bearerAuth": [
              "publish:execute",
              "schedule:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "Post id. / 内容 id。",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 36
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "accountId": {
                    "description": "Retry only this account (for failed posts). / 只重投这一个账号(失败重试时用)。",
                    "type": "string",
                    "maxLength": 36
                  },
                  "scheduledAt": {
                    "description": "For approved posts: publish at this time instead of now. / 给 approved 内容指定时间 = 改成定时发布。",
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "clientRequestId": {
                    "type": "string",
                    "description": "Idempotency key (8–128 chars of [A-Za-z0-9_.:-]); prefer the `Idempotency-Key` header, which wins over the body. / 幂等键(8–128 chars of [A-Za-z0-9_.:-]);推荐放在 `Idempotency-Key` 请求头,头与 body 都给时以头为准。"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "accountId": "acc_01J8Y…"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Publish / retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "Publishing / 已进入发布",
                    "value": {
                      "data": {
                        "postId": "pst_01J8ZK3R7QW6M2",
                        "status": "publishing",
                        "scheduledAt": null,
                        "deepLink": "https://popkit.loxily.com/history?post=pst_01J8ZK3R7QW6M2"
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c71"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "plan_limit — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "no_account / needs_reauth / replay — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "error1": {
                    "summary": "Daily cap / 触发日上限",
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "daily publish cap reached for this credential",
                        "params": {
                          "reason": "publish_daily_cap",
                          "limit": 50,
                          "used": 50
                        }
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c72"
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "502": {
            "description": "platform_error — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "external_writes_off — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "publish:execute",
          "schedule:write"
        ],
        "x-popkit-rate-class": "publish",
        "x-popkit-idempotency": "required",
        "x-popkit-read-only": false,
        "x-popkit-mcp-tool": "publish_post"
      }
    },
    "/media": {
      "post": {
        "operationId": "attach_media",
        "tags": [
          "Media"
        ],
        "summary": "Attach media / 上传素材",
        "description": "Turn an image / video into a popkit media URL. Media used for publishing must live in popkit storage (external links expire), so get a mediaUrl here first and put it into media[].dataUrl on create / update. mode=upload returns a direct-upload URL valid for five minutes; PUT the bytes yourself with the returned Content-Type (bytes never touch popkit servers: images 8 MB, videos 500 MB). mode=transload lets popkit fetch and copy a public link — images and covers only, 8 MB max. It only produces a URL and changes nothing else.\n\n把一张图 / 一段视频变成本站可用的媒体 URL。发布用的媒体必须存在本站对象存储上(外链会过期),所以先用它拿到 mediaUrl,再填进新建 / 修改的 media[].dataUrl。mode=upload 回一条五分钟有效的直传地址,你自己按返回的 Content-Type 发 PUT(字节不经过本站服务器:图片 8MB、视频 500MB);mode=transload 由本站代抓你给的外链再转存,只收图片与封面,上限 8MB。只产出 URL,不会改动任何内容。",
        "security": [
          {
            "bearerAuth": [
              "posts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "upload",
                      "transload"
                    ],
                    "description": "upload = get a direct-upload URL and PUT yourself; transload = popkit fetches your link. / upload = 拿直传地址自己 PUT;transload = 让本站代抓外链转存。"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "image",
                      "cover",
                      "video"
                    ],
                    "description": "image body picture · cover · video. / image 正文配图 · cover 封面 · video 视频。"
                  },
                  "contentType": {
                    "description": "Required for upload: image/jpeg | png | webp or video/mp4 | webm | quicktime. / upload 档必填:image/jpeg | png | webp 或 video/mp4 | webm | quicktime。",
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 80
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "bytes": {
                    "description": "Required for upload: file size in bytes; over the limit is refused. / upload 档必填:文件字节数,超上限直接拒。",
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "url": {
                    "description": "Required for transload: the link to copy (images / covers only). / transload 档必填:要转存的外链(只支持图片 / 封面)。",
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 2048
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "clientRequestId": {
                    "type": "string",
                    "description": "Idempotency key (8–128 chars of [A-Za-z0-9_.:-]); prefer the `Idempotency-Key` header, which wins over the body. / 幂等键(8–128 chars of [A-Za-z0-9_.:-]);推荐放在 `Idempotency-Key` 请求头,头与 body 都给时以头为准。"
                  }
                },
                "required": [
                  "mode",
                  "kind"
                ],
                "additionalProperties": false
              },
              "example": {
                "mode": "upload",
                "kind": "video",
                "contentType": "video/mp4",
                "bytes": 48213000
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Attach media",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "upload / upload 档",
                    "value": {
                      "data": {
                        "mode": "upload",
                        "kind": "video",
                        "mediaUrl": "https://storage.googleapis.com/socialhub-media/ws_01J7…/media/01J8ZN….mp4",
                        "uploadUrl": "https://storage.googleapis.com/socialhub-media/…?X-Goog-Signature=…",
                        "contentType": "video/mp4",
                        "expiresInSec": 300,
                        "next": "PUT the file bytes to uploadUrl with Content-Type: video/mp4 within 300s, then pass mediaUrl in media[].dataUrl."
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c73"
                      }
                    }
                  },
                  "example2": {
                    "summary": "transload / transload 档",
                    "value": {
                      "data": {
                        "mode": "transload",
                        "kind": "image",
                        "mediaUrl": "https://storage.googleapis.com/socialhub-media/ws_01J7…/media/01J8ZP….jpg",
                        "uploadUrl": null,
                        "contentType": null,
                        "expiresInSec": null,
                        "next": "Pass mediaUrl in media[].dataUrl of create / update."
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c74"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "plan_limit — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "no_account / needs_reauth / replay — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "502": {
            "description": "platform_error — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "external_writes_off — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "posts:write"
        ],
        "x-popkit-rate-class": "write",
        "x-popkit-idempotency": "optional",
        "x-popkit-read-only": false,
        "x-popkit-mcp-tool": "attach_media"
      }
    },
    "/comments": {
      "get": {
        "operationId": "list_comments",
        "tags": [
          "Comments"
        ],
        "summary": "Comment inbox / 评论收件箱",
        "description": "External comments received by the workspace (excluding your own replies), newest first, 30 per page, paged with nextCursor. canReply:false means that account cannot reply right now (replyBlock says whether a scope or the Facebook Page MODERATE task is missing) — do not call the reply endpoint for it.\n\n工作区收到的外部评论(不含我方自己的回复),最新的在前,每页 30 条,用 nextCursor 翻页。每条带 canReply:false 表示该账号当前回不了(replyBlock 说明是缺 scope 还是 Facebook 主页缺 MODERATE 任务),别对它调回复端点。",
        "security": [
          {
            "bearerAuth": [
              "comments:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "platform",
            "in": "query",
            "required": false,
            "description": "One platform only. / 只看某个平台。",
            "schema": {
              "description": "只看某个平台",
              "type": "string",
              "enum": [
                "x",
                "youtube",
                "instagram",
                "tiktok",
                "facebook",
                "douyin",
                "xiaohongshu",
                "wechat_mp",
                "threads"
              ]
            }
          },
          {
            "name": "accountIds",
            "in": "query",
            "required": false,
            "description": "Only comments under these accounts, max 20. / 只看这几个我方账号名下的评论,最多 20 个。",
            "schema": {
              "description": "只看这几个我方账号名下的评论",
              "maxItems": 20,
              "type": "array",
              "items": {
                "type": "string",
                "maxLength": 36
              }
            },
            "style": "form",
            "explode": true
          },
          {
            "name": "unrepliedOnly",
            "in": "query",
            "required": false,
            "description": "Unreplied only. / 只看未回复的。",
            "schema": {
              "description": "只看未回复的",
              "type": "boolean"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "nextCursor from the previous page. / 上一页返回的 nextCursor。",
            "schema": {
              "description": "上一页返回的 nextCursor",
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 512
                },
                {
                  "type": "null"
                }
              ]
            }
          },
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "responses": {
          "200": {
            "description": "Comment inbox",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "OK / 成功",
                    "value": {
                      "data": {
                        "items": [
                          {
                            "id": "cmt_01J8ZQ7H2K4W",
                            "platform": "tiktok",
                            "accountHandle": "@popkit",
                            "author": {
                              "handle": "@creator_amy",
                              "displayName": "Amy"
                            },
                            "text": "这个功能什么时候支持小红书?",
                            "likeCount": 12,
                            "commentedAt": "2026-09-20T07:41:20.000Z",
                            "repliedAt": null,
                            "canReply": true,
                            "replyBlock": null,
                            "post": {
                              "id": "pst_01J8ZK3R7QW6M2",
                              "title": "新功能上线",
                              "permalink": "https://www.tiktok.com/@popkit/video/73…"
                            },
                            "deepLink": "https://popkit.loxily.com/engagement?comment=cmt_01J8ZQ7H2K4W"
                          }
                        ],
                        "nextCursor": null,
                        "deepLink": "https://popkit.loxily.com/engagement"
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c75"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "comments:read"
        ],
        "x-popkit-rate-class": "read",
        "x-popkit-idempotency": "none",
        "x-popkit-read-only": true,
        "x-popkit-mcp-tool": "list_comments"
      }
    },
    "/comments/{commentId}/replies": {
      "post": {
        "operationId": "reply_comment",
        "tags": [
          "Comments"
        ],
        "summary": "Reply to a comment / 回复评论",
        "description": "Reply as the account that received the comment. This speaks publicly on behalf of the workspace and is treated like publishing: Idempotency-Key required, at most 5 per minute. If the comment already has a reply from you the call is refused with a summary (params.reason=already_replied); pass force:true to add another. One comment takes at most 3 replies per 24 h. Length follows the platform (e.g. X's 280 weighted characters).\n\n以收到该评论的那个账号的身份回复。这是以工作区名义对外发声,与发布同级:必须带 Idempotency-Key,每分钟最多 5 次。若该评论已有我方回复,默认会被拒并回已有回复摘要(params.reason=already_replied),确认要追加再传 force:true;同一条评论 24 小时内最多接收 3 条我方回复。长度按平台口径(X 280 加权字数等)。",
        "security": [
          {
            "bearerAuth": [
              "comments:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "commentId",
            "in": "path",
            "required": true,
            "description": "Comment id from the inbox (never a replyRowId). / 评论收件箱返回的评论 id(不是 replyRowId)。",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 36,
              "description": "list_comments 返回的评论 id"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000,
                    "description": "Reply text. / 回复正文。"
                  },
                  "force": {
                    "description": "Add another reply even though one exists. / 该评论已有我方回复时仍要追加。",
                    "type": "boolean"
                  },
                  "clientRequestId": {
                    "type": "string",
                    "description": "Idempotency key (8–128 chars of [A-Za-z0-9_.:-]); prefer the `Idempotency-Key` header, which wins over the body. / 幂等键(8–128 chars of [A-Za-z0-9_.:-]);推荐放在 `Idempotency-Key` 请求头,头与 body 都给时以头为准。"
                  }
                },
                "required": [
                  "text"
                ],
                "additionalProperties": false
              },
              "example": {
                "text": "小红书已经以素材包方式支持啦,可以到账号页看看 🙌"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Reply to a comment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "Replied / 已回复",
                    "value": {
                      "data": {
                        "commentId": "cmt_01J8ZQ7H2K4W",
                        "replyRowId": "cmt_01J8ZR…",
                        "platformCommentId": "7412…",
                        "platform": "tiktok",
                        "text": "小红书已经以素材包方式支持啦,可以到账号页看看 🙌",
                        "repliedAt": "2026-09-20T08:02:11.000Z",
                        "deepLink": "https://popkit.loxily.com/engagement?comment=cmt_01J8ZQ7H2K4W"
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c76"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "plan_limit — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "no_account / needs_reauth / replay — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "error1": {
                    "summary": "Already replied / 已回复过",
                    "value": {
                      "error": {
                        "code": "platform_error",
                        "message": "this comment already has a reply from you; pass force:true to add another",
                        "params": {
                          "reason": "already_replied"
                        }
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c77"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "502": {
            "description": "platform_error — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "external_writes_off — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "comments:write"
        ],
        "x-popkit-rate-class": "publish",
        "x-popkit-idempotency": "required",
        "x-popkit-read-only": false,
        "x-popkit-mcp-tool": "reply_comment"
      }
    },
    "/ideas": {
      "get": {
        "operationId": "list_ideas",
        "tags": [
          "Idea board"
        ],
        "summary": "List ideas / 选题列表",
        "description": "Paginated groups and ideas from the idea board (the planning page's data). Read-only; it never creates groups, so a brand-new workspace may return an empty groups array.\n\n分页列出选题库的分组与选题(规划页看板的数据源)。纯读,不会补建分组;全新工作区 groups 可能为空数组,不是异常。",
        "security": [
          {
            "bearerAuth": [
              "ideas:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "groupId",
            "in": "query",
            "required": false,
            "description": "One group only. / 只看某个分组。",
            "schema": {
              "description": "只看某个分组",
              "type": "string",
              "maxLength": 36
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, default 20. / 每页条数,默认 20。",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "nextCursor from the previous page. / 上一页返回的 nextCursor。",
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "responses": {
          "200": {
            "description": "List ideas",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "OK / 成功",
                    "value": {
                      "data": {
                        "groups": [
                          {
                            "id": "idg_01J8…",
                            "name": "未分组",
                            "isDefault": true
                          },
                          {
                            "id": "idg_01J9…",
                            "name": "十月活动",
                            "isDefault": false
                          }
                        ],
                        "ideas": [
                          {
                            "id": "ide_01J8ZS…",
                            "groupId": "idg_01J9…",
                            "content": "国庆档「一次创作,多平台发布」教程短视频",
                            "tags": [
                              {
                                "label": "教程",
                                "color": "blue"
                              }
                            ],
                            "image": null,
                            "plannedAt": "2026-09-28T02:00:00.000Z",
                            "targetAccountIds": [],
                            "createdBy": "usr_01J7…",
                            "creatorName": "Amy",
                            "createdAt": "2026-09-20T03:15:00.000Z"
                          }
                        ],
                        "nextCursor": null,
                        "deepLink": "https://popkit.loxily.com/planning"
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c78"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "ideas:read"
        ],
        "x-popkit-rate-class": "read",
        "x-popkit-idempotency": "none",
        "x-popkit-read-only": true,
        "x-popkit-mcp-tool": "list_ideas"
      },
      "post": {
        "operationId": "create_idea",
        "tags": [
          "Idea board"
        ],
        "summary": "Create an idea / 新建选题",
        "description": "Add an idea to the board; no external side effects. It is attributed as agent output and shows up on the planning page for people to refine. Repeating a call with the same Idempotency-Key creates it once.\n\n往选题库写一条选题,零对外副作用。归因记为「智能体产出」,用户在规划页可见并可继续加工。同一 Idempotency-Key 重复调用只创建一次。",
        "security": [
          {
            "bearerAuth": [
              "ideas:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Idea text. / 选题内容。"
                  },
                  "groupId": {
                    "description": "Target group; omit for the default group. / 目标分组;不给 = 落默认「未分组」列。",
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 36
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "tags": {
                    "description": "Up to 6 hashtags, added to the start of the post when the idea is turned into a draft. Don't use them to track progress (planned / scheduled / posted…); the system tracks that. color is a palette key (violet / blue / teal / green / …), defaulting when omitted. / 话题标签,最多 6 个;转草稿时以 #话题 写进正文开头,不要用来记录进度(planned / scheduled / posted 之类,进度由系统决定)。color 为色板键(violet / blue / teal / green / …),不给用默认色。",
                    "maxItems": 6,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "label": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 20
                        },
                        "color": {
                          "description": "标签色板键;不给用默认色",
                          "type": "string",
                          "enum": [
                            "violet",
                            "blue",
                            "teal",
                            "green",
                            "amber",
                            "rose",
                            "slate"
                          ]
                        }
                      },
                      "required": [
                        "label"
                      ]
                    }
                  },
                  "image": {
                    "description": "Image URL. / 配图 URL。",
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "plannedAt": {
                    "description": "Carried over as the draft's scheduled time when the idea is converted (editable afterwards in the composer). It never publishes on its own and is not shown on the idea board. Omit for none. / 转草稿时带给草稿的定时发布时间(之后可在创作页改);本身不触发发布,选题界面也不展示。不给 = 不设。",
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "targetAccountIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Pre-selected target accounts. / 预选的目标账号。"
                  },
                  "clientRequestId": {
                    "description": "Idempotency key (8–128 chars of [A-Za-z0-9_.:-]); prefer the `Idempotency-Key` header, which wins over the body. / 幂等键(8–128 chars of [A-Za-z0-9_.:-]);推荐放在 `Idempotency-Key` 请求头,头与 body 都给时以头为准。",
                    "type": "string"
                  }
                },
                "required": [
                  "content"
                ],
                "additionalProperties": false
              },
              "example": {
                "content": "国庆档「一次创作,多平台发布」教程短视频",
                "tags": [
                  {
                    "label": "教程",
                    "color": "blue"
                  }
                ],
                "plannedAt": "2026-09-28T10:00:00+08:00"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Create an idea",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "Created / 已创建",
                    "value": {
                      "data": {
                        "id": "ide_01J8ZS…",
                        "groupId": "idg_01J8…",
                        "deepLink": "https://popkit.loxily.com/planning?idea=ide_01J8ZS…"
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c79"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "plan_limit — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "no_account / needs_reauth / replay — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "502": {
            "description": "platform_error — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "external_writes_off — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "ideas:write"
        ],
        "x-popkit-rate-class": "write",
        "x-popkit-idempotency": "optional",
        "x-popkit-read-only": false,
        "x-popkit-mcp-tool": "create_idea"
      }
    },
    "/ideas/{ideaId}": {
      "patch": {
        "operationId": "update_idea",
        "tags": [
          "Idea board"
        ],
        "summary": "Update an idea / 修改选题",
        "description": "Edit an existing idea; no external side effects. Omitted fields keep their current values; pass null to clear (image / plannedAt). Editors can only change ideas they created. plannedAt is neither shown nor editable on the idea board, so this endpoint is the only way to change a value written at import or create time.\n\n改一条既有选题,零对外副作用。**省略的字段一律保留原值**,传 null 才是清空(image / plannedAt)。editor 角色只能改自己创建的选题。plannedAt 在选题界面既不展示也不可编辑,要把导入 / 新建时写进去的值改回来,只能经本端点。",
        "security": [
          {
            "bearerAuth": [
              "ideas:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "ideaId",
            "in": "path",
            "required": true,
            "description": "Idea id. / 选题 id。",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 36
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "content": {
                    "description": "Idea text; omit to keep it, and it must not be blank when given. / 选题内容;省略 = 不改,给了不能是空白。",
                    "type": "string"
                  },
                  "groupId": {
                    "description": "Move it to another group; the group must belong to this workspace. / 改投到别的分组;分组须属于本工作区。",
                    "type": "string",
                    "maxLength": 36
                  },
                  "tags": {
                    "description": "Up to 6 hashtags; giving it replaces the whole set, an empty list clears them. They're added to the start of the post when the idea is turned into a draft, so don't use them to track progress. / 话题标签,最多 6 个;给了就整组替换,传空数组 = 清空。转草稿时以 #话题 写进正文开头,不要用来记录进度。",
                    "maxItems": 6,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "label": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 20
                        },
                        "color": {
                          "description": "标签色板键;不给用默认色",
                          "type": "string",
                          "enum": [
                            "violet",
                            "blue",
                            "teal",
                            "green",
                            "amber",
                            "rose",
                            "slate"
                          ]
                        }
                      },
                      "required": [
                        "label"
                      ]
                    }
                  },
                  "image": {
                    "description": "Image URL; null removes it. / 配图 URL;传 null = 去掉配图。",
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "plannedAt": {
                    "description": "Carried over as the draft's scheduled time when the idea is converted (editable afterwards in the composer). It never publishes on its own and is not shown on the idea board. Pass null to clear it. / 转草稿时带给草稿的定时发布时间(之后可在创作页改);本身不触发发布,选题界面也不展示。传 null = 清空。",
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "targetAccountIds": {
                    "description": "Pre-selected target accounts; giving it replaces the whole set, an empty list clears them. / 预选的目标账号;给了就整组替换,传空数组 = 清空。",
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "clientRequestId": {
                    "description": "Idempotency key (8–128 chars of [A-Za-z0-9_.:-]); prefer the `Idempotency-Key` header, which wins over the body. / 幂等键(8–128 chars of [A-Za-z0-9_.:-]);推荐放在 `Idempotency-Key` 请求头,头与 body 都给时以头为准。",
                    "type": "string"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "plannedAt": null,
                "tags": [
                  {
                    "label": "教程",
                    "color": "blue"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Update an idea",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "OK / 成功",
                    "value": {
                      "data": {
                        "id": "ide_01J8ZS…",
                        "groupId": "idg_01J8…",
                        "deepLink": "https://popkit.loxily.com/planning?idea=ide_01J8ZS…"
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c7a"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "plan_limit — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "error1": {
                    "summary": "Not found / 选题不存在",
                    "value": {
                      "error": {
                        "code": "not_found",
                        "message": "idea not found in this workspace",
                        "params": null
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c7b"
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "no_account / needs_reauth / replay — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "502": {
            "description": "platform_error — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "external_writes_off — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "ideas:write"
        ],
        "x-popkit-rate-class": "write",
        "x-popkit-idempotency": "optional",
        "x-popkit-read-only": false,
        "x-popkit-mcp-tool": "update_idea"
      }
    },
    "/analytics/summary": {
      "get": {
        "operationId": "get_analytics_summary",
        "tags": [
          "Analytics"
        ],
        "summary": "Analytics summary / 数据概览",
        "description": "Metrics overview and trends: views, engagement, likes, comments, shares, posts, plus engagement rate, platform share, top posts, follower curve and a best-time heatmap. Filter by range / platform / account. Same definitions as the in-app analytics page (Beijing calendar days).\n\n指标概览与趋势:曝光、互动、点赞、评论、分享、发布量,以及互动率、平台占比、Top 内容、粉丝曲线、最佳发布时段热力图。可按时间范围 / 平台 / 单账号筛。口径与站内分析页完全一致(北京日历日)。",
        "security": [
          {
            "bearerAuth": [
              "analytics:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "range",
            "in": "query",
            "required": false,
            "description": "Range, default 30. / 时间范围,默认 30。",
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "const": "week"
                },
                {
                  "type": "number",
                  "const": 30
                },
                {
                  "type": "number",
                  "const": 90
                },
                {
                  "type": "string",
                  "const": "all"
                }
              ]
            }
          },
          {
            "name": "platform",
            "in": "query",
            "required": false,
            "description": "One platform only. / 只看某个平台。",
            "schema": {
              "type": "string",
              "enum": [
                "x",
                "youtube",
                "instagram",
                "tiktok",
                "facebook",
                "douyin",
                "xiaohongshu",
                "wechat_mp",
                "threads"
              ]
            }
          },
          {
            "name": "accountId",
            "in": "query",
            "required": false,
            "description": "One account only. / 只看单个账号。",
            "schema": {
              "description": "按单个账号筛(social_account.id)",
              "type": "string",
              "maxLength": 36
            }
          },
          {
            "$ref": "#/components/parameters/XRequestId"
          }
        ],
        "responses": {
          "200": {
            "description": "Analytics summary",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                "examples": {
                  "example1": {
                    "summary": "OK (excerpt) / 成功(节选)",
                    "value": {
                      "data": {
                        "range": 30,
                        "metrics": [
                          {
                            "key": "views",
                            "value": 128430,
                            "deltaPct": 12.4
                          },
                          {
                            "key": "eng",
                            "value": 5210,
                            "deltaPct": 8.1
                          },
                          {
                            "key": "likes",
                            "value": 4120,
                            "deltaPct": 6.3
                          },
                          {
                            "key": "comments",
                            "value": 640,
                            "deltaPct": 21.5
                          },
                          {
                            "key": "shares",
                            "value": 450,
                            "deltaPct": -3.2
                          },
                          {
                            "key": "posts",
                            "value": 18,
                            "deltaPct": 0
                          }
                        ],
                        "trend": {
                          "dates": [
                            "2026-08-22",
                            "2026-08-23",
                            "…"
                          ],
                          "eng": [
                            120,
                            98,
                            0
                          ]
                        },
                        "filled": 30,
                        "bucket": "day",
                        "snapshotFrom": null,
                        "series": {
                          "views": [
                            4100,
                            3980,
                            0
                          ],
                          "eng": [
                            120,
                            98,
                            0
                          ],
                          "likes": [
                            90,
                            80,
                            0
                          ],
                          "comments": [
                            20,
                            11,
                            0
                          ],
                          "shares": [
                            10,
                            7,
                            0
                          ],
                          "posts": [
                            1,
                            0,
                            0
                          ]
                        },
                        "engRate": {
                          "value": 4.06,
                          "prev": 3.9,
                          "deltaPts": 0.16,
                          "series": [
                            2.9,
                            2.5,
                            0
                          ]
                        },
                        "share": [
                          {
                            "platform": "x",
                            "eng": 3100,
                            "pct": 59.5
                          },
                          {
                            "platform": "instagram",
                            "eng": 2110,
                            "pct": 40.5
                          }
                        ],
                        "top": [
                          {
                            "id": "pst_01J8ZK3R7QW6M2",
                            "platform": "x",
                            "title": "新功能上线",
                            "eng": 890,
                            "ratePct": 6.2
                          }
                        ],
                        "followers": {
                          "dates": [
                            "2026-08-22",
                            "…"
                          ],
                          "cum": [
                            12100,
                            12480
                          ],
                          "totalNow": 12480,
                          "addedInRange": 380,
                          "fromSnapshots": true
                        },
                        "heat": {
                          "grid": [
                            [
                              0,
                              3,
                              12,
                              40,
                              55,
                              30,
                              8,
                              2,
                              0,
                              0,
                              0,
                              0
                            ],
                            [
                              "…"
                            ]
                          ],
                          "max": 55
                        },
                        "peak": {
                          "day": 2,
                          "block": 4,
                          "mult": 2.4
                        },
                        "hasData": true,
                        "deepLink": "https://popkit.loxily.com/analytics?range=30&platform=x"
                      },
                      "meta": {
                        "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c80"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "unauthenticated — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "forbidden / workspace_not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "not_found — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "internal — see /docs/api/errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-popkit-scopes": [
          "analytics:read"
        ],
        "x-popkit-rate-class": "read",
        "x-popkit-idempotency": "none",
        "x-popkit-read-only": true,
        "x-popkit-mcp-tool": "get_analytics_summary"
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Long-lived integration credential issued under Settings → Integrations → Long-lived credentials (prefix popkit_it_). Scopes are fixed at issue time. / 设置页「接入外部系统 → 长期凭证」签发的长期密钥(popkit_it_ 开头),scope 在签发时确定。",
        "x-popkit-scopes": {
          "accounts:read": "accounts:read",
          "analytics:read": "analytics:read",
          "approvals:write": "approvals:write",
          "comments:read": "comments:read",
          "comments:write": "comments:write",
          "ideas:read": "ideas:read",
          "ideas:write": "ideas:write",
          "posts:read": "posts:read",
          "posts:write": "posts:write",
          "publish:execute": "publish:execute",
          "schedule:write": "schedule:write",
          "workspace:read": "workspace:read"
        }
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Idempotency key (8–128 chars of [A-Za-z0-9_.:-]). Required for publish-tier operations; when present, a retry with the same key returns the first result with meta.replayed=true. / 幂等键,发布档必填。",
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_.:-]{8,128}$"
        }
      },
      "XRequestId": {
        "name": "X-Request-Id",
        "in": "header",
        "required": false,
        "description": "Optional caller-supplied request id (≤36 chars of [A-Za-z0-9._:-]); echoed back in the X-Request-Id header and meta.requestId. / 调用方自带的请求 id,原样回显。",
        "schema": {
          "type": "string",
          "maxLength": 36
        }
      }
    },
    "schemas": {
      "DataEnvelope": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "description": "Endpoint-specific payload / 端点各自的数据主体"
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "error",
          "meta"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "unauthenticated",
                  "workspace_not_found",
                  "forbidden",
                  "not_found",
                  "invalid_input",
                  "plan_limit",
                  "no_account",
                  "needs_reauth",
                  "rate_limited",
                  "replay",
                  "external_writes_off",
                  "platform_error",
                  "internal"
                ]
              },
              "message": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "English, self-correction hint; not UI copy / 英文自纠提示,不是界面文案"
              },
              "params": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": {
                  "type": [
                    "string",
                    "number"
                  ]
                }
              }
            }
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        }
      },
      "Meta": {
        "type": "object",
        "required": [
          "requestId"
        ],
        "properties": {
          "requestId": {
            "type": "string"
          },
          "replayed": {
            "type": "boolean",
            "description": "Present and true when this is the cached result of an earlier call with the same Idempotency-Key / 幂等重放"
          }
        }
      }
    }
  },
  "x-popkit-error-codes": [
    {
      "code": "unauthenticated",
      "status": 401,
      "meaning": "Missing Bearer credential, or it is invalid, disabled, expired, or not a long-lived credential (`popkit_it_…`).",
      "fix": "Check or re-issue the credential under Settings → Integrations → Long-lived credentials; send `Authorization: Bearer <credential>`."
    },
    {
      "code": "forbidden",
      "status": 403,
      "meaning": "The credential lacks the required scope, the issuer's workspace role forbids the action, or the requested publish tier is not on the credential's allow-list. `params` carries `scope` / `action`.",
      "fix": "Issue a credential with the required scope / tier; role problems need the workspace admin."
    },
    {
      "code": "workspace_not_found",
      "status": 403,
      "meaning": "The workspace bound to the credential was dissolved, or the issuer is no longer an active member or is banned.",
      "fix": "Have an admin who is still in the workspace issue a new credential."
    },
    {
      "code": "not_found",
      "status": 404,
      "meaning": "The target does not exist or does not belong to this workspace (not distinguished, to prevent enumeration).",
      "fix": "Fetch the id again from the list endpoint; never reuse a replyRowId as a commentId."
    },
    {
      "code": "invalid_input",
      "status": 400,
      "meaning": "Invalid input: missing required field, bad enum, timestamp without timezone, broken cursor, publish tier without `Idempotency-Key`, etc. `message` names the fields.",
      "fix": "Fix the fields named in message and retry; the same Idempotency-Key may be reused (failures are not cached)."
    },
    {
      "code": "plan_limit",
      "status": 402,
      "meaning": "Plan quota exhausted (e.g. scheduled posts this month). `params` carries `limit` / `used`.",
      "fix": "Check quotas with `GET /v1/workspace`; wait for next month or upgrade the plan."
    },
    {
      "code": "no_account",
      "status": 409,
      "meaning": "The target platform has no usable account in this workspace.",
      "fix": "Have a user connect an account under Accounts in the app; integrations cannot do this."
    },
    {
      "code": "needs_reauth",
      "status": 409,
      "meaning": "The target account's platform authorization has expired.",
      "fix": "Have the user re-authorize in the app; look for `status: \"expired\"` in `GET /v1/accounts`."
    },
    {
      "code": "rate_limited",
      "status": 429,
      "meaning": "This credential exceeded the per-minute cap for the tier, or hit the daily publish / comment-reply cap or the per-account minimum interval. `params` carries `limit` / `windowSec` / `retryAfterSec` or `reason`.",
      "fix": "Wait for the `Retry-After` header and retry; daily-cap reasons do not clear until the next day."
    },
    {
      "code": "replay",
      "status": 409,
      "meaning": "A request with the same `Idempotency-Key` is still in progress.",
      "fix": "Retry the same key shortly; once finished you get the first result back with `meta.replayed: true`."
    },
    {
      "code": "external_writes_off",
      "status": 503,
      "meaning": "External writes are disabled in this environment (test-environment safety valve); publishing / replying will not go out.",
      "fix": "Environment configuration, not an input problem; call the production environment."
    },
    {
      "code": "platform_error",
      "status": 502,
      "meaning": "The platform rejected or failed the request (policy, duplicate, 5xx). `params.reason` carries a machine code.",
      "fix": "Inspect per-account `errorCode` via `GET /v1/posts/{postId}`; retry through `POST /v1/posts/{postId}/publish`."
    },
    {
      "code": "internal",
      "status": 500,
      "meaning": "Unclassified server error; it has been logged.",
      "fix": "Contact us with the `meta.requestId` from the response."
    }
  ]
}