{
  "schema_version": "2.0",
  "project": "go-threads-api",
  "contract_version": "0.3.0",
  "protocol": "grpc",
  "authentication": {
    "type": "grpc_metadata",
    "key": "x-ig-token",
    "value_format": "IGT:2:<token>",
    "not_required_for": [
      "threads.auth.v1.AuthService/Login"
    ]
  },
  "request_routing": {
    "type": "grpc_metadata",
    "key": "x-threads-proxy-url",
    "value_format": "http[s]://[user:pass@]host:port or socks5://[user:pass@]host:port",
    "sensitive": true,
    "notes": "可选；按账号/请求创建独立连接池，禁止跨账号复用出口。"
  },
  "modules": [
    {
      "id": "auth",
      "title": "登录与当前账号",
      "client": "client.auth",
      "purpose": "登录、校验会话并读取当前账号身份。",
      "order": 1
    },
    {
      "id": "profile",
      "title": "资料与主页帖子",
      "client": "client.profile",
      "purpose": "读取用户资料、保存当前账号资料并分页采集主页帖子。",
      "order": 2
    },
    {
      "id": "posts",
      "title": "帖子发布与删除",
      "client": "client.posts",
      "purpose": "检查文本、发布文本帖、上传图片、发布单图帖和删除帖子。",
      "order": 3
    },
    {
      "id": "replies",
      "title": "回复采集",
      "client": "client.replies",
      "purpose": "分页读取指定账号的回复列表。",
      "order": 4
    },
    {
      "id": "search",
      "title": "搜索与主题校验",
      "client": "client.search",
      "purpose": "关键词搜索以及发帖主题标签校验。",
      "order": 5
    },
    {
      "id": "feed",
      "title": "推荐用户",
      "client": "client.feed",
      "purpose": "分页读取 Threads 推荐账号。",
      "order": 6
    },
    {
      "id": "friendships",
      "title": "关系状态",
      "client": "client.friendships",
      "purpose": "读取与目标账号之间的关注、拉黑和静音状态。",
      "order": 7
    },
    {
      "id": "insights",
      "title": "账号洞察",
      "client": "client.insights",
      "purpose": "读取账号洞察；当前缺少真实 GraphQL doc_id，尚不可用。",
      "order": 8
    },
    {
      "id": "mentions",
      "title": "提及建议",
      "client": "client.mentions",
      "purpose": "获取 @ 提及建议；当前缺少真实 GraphQL doc_id，尚不可用。",
      "order": 9
    }
  ],
  "workflows": [
    {
      "id": "verify_session",
      "title": "校验现有 IGT:2 会话",
      "module": "auth",
      "status": "available",
      "goal": "确认 token 对应的真实账号，并取得后续调用使用的 uid 和资料字段。",
      "prerequisites": [
        "ThreadsClient 已注入 IGT:2 token。",
        "每个账号使用独立 proxy_url；不要让多账号共享出口。"
      ],
      "steps": [
        {
          "rpc": "threads.auth.v1.AuthService/GetCurrentUser",
          "action": "调用 get_current_user(edit=False) 校验会话。",
          "inputs": "edit=False；token 和代理由 ThreadsClient metadata 自动注入。",
          "output": "核对 pk、username；将 pk 作为发帖 uid 或目标 user_id。"
        }
      ],
      "field_mappings": [],
      "missing_capabilities": []
    },
    {
      "id": "login",
      "title": "账密登录并取得 IGT:2",
      "module": "auth",
      "status": "experimental",
      "goal": "通过 CAA/Bloks 登录生成会话并即时核对账号身份。",
      "prerequisites": [
        "准备持久化设备参数 LoginDevice。",
        "生产使用必须在 Go server 通过 THREADS_KEYBOX_FILE 加载硬件 attestation signer；Docker 镜像从仓库根目录版本化 keybox.xml 构建。",
        "无硬件证明登录属于高风险操作，必须得到明确授权。"
      ],
      "steps": [
        {
          "rpc": "threads.auth.v1.AuthService/Login",
          "action": "提交账号、密码、设备和可选 2FA seed。",
          "inputs": "默认 allow_unattested_login=False；不要把密码或 2FA seed 写入日志。",
          "output": "核对 success、session.token、verified_user.username 和 assurance。"
        },
        {
          "rpc": "threads.auth.v1.AuthService/GetCurrentUser",
          "action": "用新 token 创建 ThreadsClient 后再次读取当前账号。",
          "inputs": "将 LoginResponse.session.token 注入 ThreadsClient.token。",
          "output": "确认 pk、username 与登录账号一致；即时成功不代表长期稳定。"
        }
      ],
      "field_mappings": [
        {
          "from": "LoginResponse.session.token",
          "to": "ThreadsClient.token",
          "description": "登录返回的 IGT:2 用于后续全部业务 RPC。"
        }
      ],
      "missing_capabilities": [
        "软件环境尚不能证明 session 可长期稳定；stable_for_automation 必须保持 false，直到硬件证明和存活观测通过。"
      ]
    },
    {
      "id": "edit_profile",
      "title": "安全编辑当前账号资料",
      "module": "profile",
      "status": "available",
      "goal": "修改指定资料字段，同时避免整表回传接口清空未携带的原值。",
      "prerequisites": [
        "已有有效 IGT:2 token。",
        "已持久化当前账号 uuid。"
      ],
      "steps": [
        {
          "rpc": "threads.auth.v1.AuthService/GetCurrentUser",
          "action": "先调用 get_current_user(edit=True) 读取完整当前资料。",
          "inputs": "edit=True。",
          "output": "保存 username、full_name、biography、is_private、external_url 和 bio_links。"
        },
        {
          "rpc": "threads.profile.v1.ProfileService/EditProfile",
          "action": "只替换目标字段，其余字段使用上一步原值并整表提交。",
          "inputs": "username/first_name/biography/is_private 必须完整回传；uuid 来自持久设备身份。",
          "output": "核对返回资料，再调用 get_current_user(edit=True) 验证保存结果。"
        }
      ],
      "field_mappings": [
        {
          "from": "CurrentUser.username/full_name/biography/is_private/external_url",
          "to": "EditProfileRequest 对应字段",
          "description": "未修改字段必须回填原值，不能省略。"
        }
      ],
      "missing_capabilities": [
        "头像和封面需要独立上传端点，当前契约未实现。",
        "location 不是 EditProfile 端点字段。"
      ]
    },
    {
      "id": "create_text_post",
      "title": "发布文本帖",
      "module": "posts",
      "status": "available",
      "goal": "使用当前账号和持久设备身份创建纯文本帖子。",
      "prerequisites": [
        "已有有效 IGT:2 token。",
        "已通过 get_current_user 取得 uid。",
        "已持久化 device_id 和 uuid。",
        "写操作已获得明确授权。"
      ],
      "steps": [
        {
          "rpc": "threads.posts.v1.PostsService/CreateTextPost",
          "action": "提交 caption、uid、device_id、uuid 和可选发帖功能字段。",
          "inputs": "纯文本帖不需要先上传媒体；upload_id 可省略或由调用方生成。",
          "output": "核对返回 Media 的作者、caption、code 和 permalink。"
        },
        {
          "rpc": "threads.profile.v1.ProfileService/ListProfileThreads",
          "action": "重新读取账号主页确认帖子真实出现。",
          "inputs": "user_id 使用发帖 uid。",
          "output": "在 threads[].items[].post 中找到新帖子。"
        }
      ],
      "field_mappings": [
        {
          "from": "CurrentUser.pk",
          "to": "CreateTextPostRequest.uid",
          "description": "发帖 uid 来自当前账号身份校验。"
        }
      ],
      "missing_capabilities": []
    },
    {
      "id": "create_image_post",
      "title": "发布单图帖",
      "module": "posts",
      "status": "available",
      "goal": "先上传 WebP 图片，再用上传结果创建单图帖子。",
      "prerequisites": [
        "已有有效 IGT:2 token。",
        "已通过 get_current_user 取得 uid。",
        "已持久化 device_id 和 uuid。",
        "准备 WebP 图片字节及真实宽高。",
        "写操作已获得明确授权。"
      ],
      "steps": [
        {
          "rpc": "threads.posts.v1.PostsService/UploadImage",
          "action": "上传图片二进制及真实宽高。",
          "inputs": "image_data 为 WebP bytes；original_width/original_height 来自图片元数据。",
          "output": "要求 status=ok，并保存响应 upload_id。"
        },
        {
          "rpc": "threads.posts.v1.PostsService/CreateImagePost",
          "action": "使用上传响应创建单图帖。",
          "inputs": "upload_id 必须使用 UploadImageResult.upload_id；宽高与上传步骤保持一致。",
          "output": "核对 media_type=1、caption、image_versions2 和帖子作者。"
        },
        {
          "rpc": "threads.profile.v1.ProfileService/ListProfileThreads",
          "action": "重新读取账号主页确认图片帖真实出现。",
          "inputs": "user_id 使用发帖 uid。",
          "output": "在 threads[].items[].post 中找到新帖子和图片字段。"
        }
      ],
      "field_mappings": [
        {
          "from": "UploadImageResult.upload_id",
          "to": "CreateImagePostRequest.upload_id",
          "description": "图片上传返回值必须原样传给创建接口。"
        },
        {
          "from": "UploadImageRequest.original_width/original_height",
          "to": "CreateImagePostRequest.original_width/original_height",
          "description": "创建接口使用与上传图片一致的真实尺寸。"
        }
      ],
      "missing_capabilities": []
    },
    {
      "id": "create_video_post",
      "title": "发布视频帖",
      "module": "posts",
      "status": "unavailable",
      "goal": "上传视频并创建视频帖子。",
      "prerequisites": [],
      "steps": [],
      "field_mappings": [],
      "missing_capabilities": [
        "当前 Proto、Go SDK 和 Python 门面均没有视频上传 RPC。",
        "当前没有视频 configure RPC、视频转码状态查询或封面上传工作流。",
        "在完成真实抓包、契约和黑盒测试前，禁止复用 UploadImage/CreateImagePost 伪装视频发布。"
      ]
    },
    {
      "id": "collect_profile_threads",
      "title": "分页采集主页帖子",
      "module": "profile",
      "status": "available",
      "goal": "读取指定账号主页帖子，并使用游标持续翻页。",
      "prerequisites": [
        "已有有效 IGT:2 token。",
        "准备目标 user_id。"
      ],
      "steps": [
        {
          "rpc": "threads.profile.v1.ProfileService/ListProfileThreads",
          "action": "首次调用不传 max_id，读取 threads 强类型结果。",
          "inputs": "user_id 为目标账号；exclude_reposts 按采集需求设置。",
          "output": "消费 threads[].items[].post，并读取 next_cursor。"
        },
        {
          "rpc": "threads.profile.v1.ProfileService/ListProfileThreads",
          "action": "next_cursor 非空时继续请求下一页。",
          "inputs": "将上一页 next_cursor 传入下一次 max_id。",
          "output": "next_cursor 为空时结束；raw_json 只用于未建模字段排错。"
        }
      ],
      "field_mappings": [
        {
          "from": "ProfileThreadsPage.next_cursor",
          "to": "ListProfileThreadsRequest.max_id",
          "description": "分页游标来自上一次真实响应。"
        }
      ],
      "missing_capabilities": []
    }
  ],
  "services": [
    {
      "name": "threads.auth.v1.AuthService",
      "rpcs": [
        {
          "name": "Login",
          "identifier": "threads.auth.v1.AuthService/Login",
          "request_type": "LoginRequest",
          "response_type": "LoginResponse",
          "proto": "/proto/auth.proto",
          "status": "experimental",
          "operation": "create",
          "idempotent": false,
          "side_effect": true,
          "python_method": "AuthClient.login",
          "business_success": "success=true、session.token 为 IGT:2，且 verified_user.username 与请求账号一致；stable_for_automation 仍必须为 false，直到硬件证明和长期存活观测另行通过。",
          "notes": "默认要求服务端硬件 attestation signer；Go server 通过 THREADS_KEYBOX_FILE 加载 keybox，生产镜像把仓库根目录版本化 keybox.xml 以 0400 固化为 /run/secrets/threads-keybox.xml。无 signer 时返回 FAILED_PRECONDITION；文件配置后缺失、不可读或 XML 无效则 Server 启动失败。allow_unattested_login=true 会真实提交账密且可能触发 logout_reason:33/封号，只能在明确授权的高风险测试中使用。",
          "title": "账号登录",
          "module": "auth",
          "use_when": "没有可用 IGT:2，并且需要通过账密建立新会话时。",
          "prerequisites": [
            "持久 LoginDevice；生产环境通过 THREADS_KEYBOX_FILE 加载与设备匹配的 keybox signer。"
          ],
          "parameter_sources": {
            "username": "用户输入：Threads/Instagram 登录账号。",
            "password": "受保护账号配置：禁止写入源码和日志。",
            "two_factor_seed": "受保护账号配置：启用 TOTP 时提供。",
            "device": "持久设备身份：同一账号长期复用，不能每次随机生成。",
            "app_version": "运行配置：通常省略并使用服务端支持版本。",
            "allow_unattested_login": "固定安全开关：默认 false；仅经明确高风险授权后设为 true。"
          },
          "response_usage": "提取 session.token，核对 verified_user.username 和 assurance；stable_for_automation 不能由即时成功推断。",
          "next_calls": [
            "threads.auth.v1.AuthService/GetCurrentUser"
          ],
          "workflows": [
            "login"
          ]
        },
        {
          "name": "GetCurrentUser",
          "identifier": "threads.auth.v1.AuthService/GetCurrentUser",
          "request_type": "GetCurrentUserRequest",
          "response_type": "CurrentUser",
          "proto": "/proto/auth.proto",
          "status": "available",
          "operation": "read",
          "idempotent": true,
          "side_effect": false,
          "python_method": "AuthClient.get_current_user",
          "business_success": "响应 pk 与注入 token 所属账号一致。",
          "notes": "读取当前账号；edit=true 时返回更完整的资料字段。",
          "title": "读取当前账号",
          "module": "auth",
          "use_when": "校验 token 身份、取得当前 uid，或在资料编辑前读取完整原值时。",
          "prerequisites": [
            "ThreadsClient 已注入有效 IGT:2 token。"
          ],
          "parameter_sources": {
            "edit": "调用场景：普通身份校验传 false；资料编辑前传 true。"
          },
          "response_usage": "pk 是发帖 uid 和当前账号 user_id；edit=true 时使用完整资料字段回填 EditProfile。",
          "next_calls": [
            "threads.profile.v1.ProfileService/EditProfile",
            "threads.posts.v1.PostsService/CreateTextPost",
            "threads.posts.v1.PostsService/CreateImagePost"
          ],
          "workflows": [
            "verify_session",
            "login",
            "edit_profile",
            "create_text_post",
            "create_image_post"
          ]
        }
      ]
    },
    {
      "name": "threads.feed.v1.FeedService",
      "rpcs": [
        {
          "name": "ListRecommendedUsers",
          "identifier": "threads.feed.v1.FeedService/ListRecommendedUsers",
          "request_type": "ListRecommendedUsersRequest",
          "response_type": "threads.common.v1.RawList",
          "proto": "/proto/feed.proto",
          "status": "available",
          "operation": "read",
          "idempotent": true,
          "side_effect": false,
          "python_method": "FeedClient.list_recommended_users",
          "business_success": "RawList.raw_json 可解析，且分页游标来自同一次真实响应。",
          "notes": "响应结构尚未强类型化，以 RawList 透传。",
          "title": "读取推荐用户",
          "module": "feed",
          "use_when": "需要获取 Threads 推荐账号或继续推荐流分页时。",
          "prerequisites": [
            "已有有效 IGT:2 token。"
          ],
          "parameter_sources": {
            "paging_token": "上一页响应：使用 RawList.next_cursor；首次调用省略。",
            "recommendation_type": "产品场景配置：按上游支持的推荐类型填写，未知时省略。"
          },
          "response_usage": "解析 raw_json 获取推荐对象；next_cursor 用于下一页 paging_token。",
          "next_calls": [
            "threads.feed.v1.FeedService/ListRecommendedUsers"
          ],
          "workflows": []
        }
      ]
    },
    {
      "name": "threads.friendships.v1.FriendshipsService",
      "rpcs": [
        {
          "name": "GetFriendshipStatus",
          "identifier": "threads.friendships.v1.FriendshipsService/GetFriendshipStatus",
          "request_type": "GetFriendshipStatusRequest",
          "response_type": "FriendshipStatus",
          "proto": "/proto/friendships.proto",
          "status": "available",
          "operation": "read",
          "idempotent": true,
          "side_effect": false,
          "python_method": "FriendshipsClient.get_friendship_status",
          "business_success": "返回关系状态属于请求 user_id 对应的目标账号。",
          "notes": "读取关注、拉黑、静音等关系状态。",
          "title": "读取关系状态",
          "module": "friendships",
          "use_when": "需要确认当前账号是否关注、被关注、拉黑或静音目标账号时。",
          "prerequisites": [
            "已有有效 IGT:2 token。"
          ],
          "parameter_sources": {
            "user_id": "目标对象：来自 GetUserInfo.pk、搜索结果或业务数据库。",
            "is_external_deeplink_profile_view": "调用场景：普通 API 调用使用 false。"
          },
          "response_usage": "直接读取 following、followed_by、blocking、muting 等强类型字段。",
          "next_calls": [],
          "workflows": []
        }
      ]
    },
    {
      "name": "threads.insights.v1.InsightsService",
      "rpcs": [
        {
          "name": "GetAccountInsights",
          "identifier": "threads.insights.v1.InsightsService/GetAccountInsights",
          "request_type": "GetAccountInsightsRequest",
          "response_type": "threads.common.v1.RawList",
          "proto": "/proto/insights.proto",
          "status": "unimplemented",
          "operation": "read",
          "idempotent": true,
          "side_effect": false,
          "python_method": "InsightsClient.get_account_insights",
          "business_success": "当前固定返回 gRPC UNIMPLEMENTED。",
          "notes": "缺少真实 GraphQL doc_id，禁止宣称可用。",
          "title": "读取账号洞察",
          "module": "insights",
          "use_when": "需要账号洞察数据时；当前接口尚不可用。",
          "prerequisites": [
            "当前缺少真实 GraphQL doc_id。"
          ],
          "parameter_sources": {
            "user_id": "目标对象：省略表示当前账号，或传目标账号 ID。"
          },
          "response_usage": "当前固定得到 UNIMPLEMENTED，不能解析为真实洞察。",
          "next_calls": [],
          "workflows": []
        }
      ]
    },
    {
      "name": "threads.mentions.v1.MentionsService",
      "rpcs": [
        {
          "name": "GetMentionSuggestions",
          "identifier": "threads.mentions.v1.MentionsService/GetMentionSuggestions",
          "request_type": "GetMentionSuggestionsRequest",
          "response_type": "threads.common.v1.RawList",
          "proto": "/proto/mentions.proto",
          "status": "unimplemented",
          "operation": "read",
          "idempotent": true,
          "side_effect": false,
          "python_method": "MentionsClient.get_mention_suggestions",
          "business_success": "当前固定返回 gRPC UNIMPLEMENTED。",
          "notes": "缺少真实 GraphQL doc_id，禁止宣称可用。",
          "title": "获取提及建议",
          "module": "mentions",
          "use_when": "输入 @ 用户名时需要候选建议；当前接口尚不可用。",
          "prerequisites": [
            "当前缺少真实 GraphQL doc_id。"
          ],
          "parameter_sources": {
            "query": "用户输入：@ 后正在输入的用户名片段。"
          },
          "response_usage": "当前固定得到 UNIMPLEMENTED，不能作为发帖候选。",
          "next_calls": [],
          "workflows": []
        }
      ]
    },
    {
      "name": "threads.posts.v1.PostsService",
      "rpcs": [
        {
          "name": "CheckOffensiveText",
          "identifier": "threads.posts.v1.PostsService/CheckOffensiveText",
          "request_type": "CheckOffensiveTextRequest",
          "response_type": "OffensiveCheck",
          "proto": "/proto/posts.proto",
          "status": "known-upstream-error",
          "operation": "read",
          "idempotent": true,
          "side_effect": false,
          "python_method": "PostsClient.check_offensive_text",
          "business_success": "当前真实上游返回 HTTP 404，测试以真实错误映射为验收结果。",
          "notes": "请求结构与真实 App 抓包一致，但当前线上端点不可用。",
          "title": "检查冒犯文本",
          "module": "posts",
          "use_when": "发帖前希望执行上游文本风险检查时；当前真实上游返回 404。",
          "prerequisites": [
            "已有有效 IGT:2 token。"
          ],
          "parameter_sources": {
            "text_list": "用户内容：待检查的一段或多段文本。",
            "media_id": "已有媒体上下文：编辑或关联媒体时提供，否则省略。"
          },
          "response_usage": "当前以 gRPC 错误路径处理，不能把未返回结果视为安全。",
          "next_calls": [],
          "workflows": []
        },
        {
          "name": "CreateTextPost",
          "identifier": "threads.posts.v1.PostsService/CreateTextPost",
          "request_type": "CreateTextPostRequest",
          "response_type": "Media",
          "proto": "/proto/posts.proto",
          "status": "available",
          "operation": "create",
          "idempotent": false,
          "side_effect": true,
          "python_method": "PostsClient.create_text_post",
          "business_success": "返回帖子属于请求 uid，caption 与请求一致，并能从账号主页重新读取。",
          "notes": "真实发帖操作；必须显式授权，调用方不得自动重试。",
          "title": "创建文本帖",
          "module": "posts",
          "use_when": "发布不带图片或视频的 Threads 文本帖子时。",
          "prerequisites": [
            "有效 token、当前账号 uid、持久 device_id/uuid、写操作授权。"
          ],
          "parameter_sources": {
            "caption": "用户输入：帖子正文。",
            "uid": "前置响应：GetCurrentUser.pk。",
            "device_id": "持久设备身份：账号绑定的 Android device_id。",
            "uuid": "持久设备身份：账号绑定的 uuid。",
            "upload_id": "运行时生成：可省略，由 Go SDK 生成；重试时不得随意复用。",
            "reply_control": "产品设置：回复权限枚举，默认 0。",
            "device": "持久设备身份：与 device_id/uuid 属于同一设备。",
            "camera_session_id": "运行时生成：模拟发布会话时提供，否则省略。",
            "nav_chain": "运行时上下文：导航链，普通调用可省略。",
            "timezone_offset": "运行环境：账号所在地时区偏移。",
            "tag_header": "主题功能：先用 ValidateTag 校验后按上游格式提供。",
            "location": "用户选择：posts_pb2.Location，未选择位置时省略。",
            "poll": "用户输入：posts_pb2.Poll，未创建投票时省略。"
          },
          "response_usage": "保存 id/code/permalink，并核对 caption、user 和主页重新读取结果。",
          "next_calls": [
            "threads.profile.v1.ProfileService/ListProfileThreads"
          ],
          "workflows": [
            "create_text_post"
          ]
        },
        {
          "name": "UploadImage",
          "identifier": "threads.posts.v1.PostsService/UploadImage",
          "request_type": "UploadImageRequest",
          "response_type": "UploadImageResult",
          "proto": "/proto/posts.proto",
          "status": "available",
          "operation": "create",
          "idempotent": false,
          "side_effect": true,
          "python_method": "PostsClient.upload_image",
          "business_success": "响应 status=ok 且 upload_id 非空；该 upload_id 可被 CreateImagePost 接受。",
          "notes": "真实图片二进制上传；当前抓包确认 image/webp，视频上传仍缺少抓包来源。",
          "title": "上传图片",
          "module": "posts",
          "use_when": "创建单图帖前上传 WebP 图片二进制时。",
          "prerequisites": [
            "有效 token、WebP 图片字节、真实宽高、写操作授权。"
          ],
          "parameter_sources": {
            "image_data": "本地文件或内存：WebP 原始 bytes。",
            "original_width": "图片元数据：解码后的真实像素宽度。",
            "original_height": "图片元数据：解码后的真实像素高度。",
            "upload_id": "运行时生成：通常省略并由 Go SDK 生成。",
            "mime_type": "固定协议值：当前确认 image/webp。",
            "is_optimistic_upload": "上传策略：通常省略使用 SDK 默认值。",
            "msssim": "图片质量计算：调用方实际计算时提供，否则省略。",
            "ssim": "图片质量计算：调用方实际计算时提供，否则省略。",
            "waterfall_id": "运行时生成：上传链路追踪 ID，通常省略。"
          },
          "response_usage": "status 必须为 ok；将 upload_id 原样传给 CreateImagePost。",
          "next_calls": [
            "threads.posts.v1.PostsService/CreateImagePost"
          ],
          "workflows": [
            "create_image_post"
          ]
        },
        {
          "name": "CreateImagePost",
          "identifier": "threads.posts.v1.PostsService/CreateImagePost",
          "request_type": "CreateImagePostRequest",
          "response_type": "Media",
          "proto": "/proto/posts.proto",
          "status": "available",
          "operation": "create",
          "idempotent": false,
          "side_effect": true,
          "python_method": "PostsClient.create_image_post",
          "business_success": "返回 media_type=1，正文与请求一致，image_versions2 非空，并能从账号主页重新读取。",
          "notes": "单图帖 configure；调用前必须先用 UploadImage 获取 upload_id，调用方不得自动重试。",
          "title": "创建单图帖",
          "module": "posts",
          "use_when": "UploadImage 成功后，把已上传图片发布为单图帖子时。",
          "prerequisites": [
            "UploadImageResult.status=ok、当前账号 uid、持久设备身份、写操作授权。"
          ],
          "parameter_sources": {
            "caption": "用户输入：帖子正文。",
            "uid": "前置响应：GetCurrentUser.pk。",
            "device_id": "持久设备身份：账号绑定的 Android device_id。",
            "uuid": "持久设备身份：账号绑定的 uuid。",
            "upload_id": "前置响应：UploadImageResult.upload_id。",
            "reply_control": "产品设置：回复权限枚举，默认 0。",
            "device": "持久设备身份：与 device_id/uuid 属于同一设备。",
            "camera_session_id": "运行时生成：模拟发布会话时提供，否则省略。",
            "nav_chain": "运行时上下文：导航链，普通调用可省略。",
            "timezone_offset": "运行环境：账号所在地时区偏移。",
            "tag_header": "主题功能：先用 ValidateTag 校验后按上游格式提供。",
            "location": "用户选择：posts_pb2.Location，未选择位置时省略。",
            "poll": "用户输入：posts_pb2.Poll，未创建投票时省略。",
            "original_width": "前置请求：与 UploadImage.original_width 保持一致。",
            "original_height": "前置请求：与 UploadImage.original_height 保持一致。",
            "custom_accessibility_caption": "用户输入：图片无障碍说明，可省略。"
          },
          "response_usage": "核对 media_type=1、image_versions2、caption 和 user，并保存 id/code/permalink。",
          "next_calls": [
            "threads.profile.v1.ProfileService/ListProfileThreads"
          ],
          "workflows": [
            "create_image_post"
          ]
        },
        {
          "name": "DeletePost",
          "identifier": "threads.posts.v1.PostsService/DeletePost",
          "request_type": "DeletePostRequest",
          "response_type": "DeletePostResult",
          "proto": "/proto/posts.proto",
          "status": "available",
          "operation": "delete",
          "idempotent": false,
          "side_effect": true,
          "python_method": "PostsClient.delete_post",
          "business_success": "did_delete=true，且重新读取主页确认目标帖子已经消失。",
          "notes": "真实删除操作；必须显式授权，调用方不得自动重试。",
          "title": "删除帖子",
          "module": "posts",
          "use_when": "删除当前账号已有帖子或清理测试帖子时。",
          "prerequisites": [
            "有效 token、目标 media_id、当前账号 uid/uuid、写操作授权。"
          ],
          "parameter_sources": {
            "media_id": "前置响应：创建接口返回的 Media.id，或主页帖子中的 post.id。",
            "uid": "前置响应：GetCurrentUser.pk。",
            "uuid": "持久设备身份：当前账号绑定的 uuid。"
          },
          "response_usage": "did_delete 必须为 true，并通过 ListProfileThreads 确认目标帖子消失。",
          "next_calls": [
            "threads.profile.v1.ProfileService/ListProfileThreads"
          ],
          "workflows": []
        }
      ]
    },
    {
      "name": "threads.profile.v1.ProfileService",
      "rpcs": [
        {
          "name": "GetUserInfo",
          "identifier": "threads.profile.v1.ProfileService/GetUserInfo",
          "request_type": "GetUserInfoRequest",
          "response_type": "User",
          "proto": "/proto/profile.proto",
          "status": "available",
          "operation": "read",
          "idempotent": true,
          "side_effect": false,
          "python_method": "ProfileClient.get_user_info",
          "business_success": "响应 pk 与请求 user_id 一致。",
          "notes": "读取指定用户资料。",
          "title": "读取指定用户资料",
          "module": "profile",
          "use_when": "已知用户 ID，需要读取公开资料和账号统计时。",
          "prerequisites": [
            "已有有效 IGT:2 token。"
          ],
          "parameter_sources": {
            "user_id": "目标对象：来自搜索结果、帖子作者 pk 或业务数据库。"
          },
          "response_usage": "核对 pk 等于请求 user_id，再使用 username、计数和隐私状态。",
          "next_calls": [
            "threads.profile.v1.ProfileService/ListProfileThreads",
            "threads.friendships.v1.FriendshipsService/GetFriendshipStatus"
          ],
          "workflows": []
        },
        {
          "name": "ListProfileThreads",
          "identifier": "threads.profile.v1.ProfileService/ListProfileThreads",
          "request_type": "ListProfileThreadsRequest",
          "response_type": "ProfileThreadsPage",
          "proto": "/proto/profile.proto",
          "status": "available",
          "operation": "read",
          "idempotent": true,
          "side_effect": false,
          "python_method": "ProfileClient.list_profile_threads",
          "business_success": "status=ok，threads[].items[].post 可直接读取，帖子作者与请求 user_id 的业务语义一致。",
          "notes": "主页帖子已强类型化；raw_json 仅为旧客户端线级兼容和未建模字段排错保留。",
          "title": "分页读取主页帖子",
          "module": "profile",
          "use_when": "采集指定账号主页帖子、验证发帖或验证删除结果时。",
          "prerequisites": [
            "已有有效 token 和目标 user_id。"
          ],
          "parameter_sources": {
            "user_id": "目标对象：当前账号用 GetCurrentUser.pk，其他账号来自搜索或资料接口。",
            "max_id": "上一页响应：使用 ProfileThreadsPage.next_cursor；首次调用省略。",
            "exclude_reposts": "采集策略：是否排除转发，可省略使用上游默认。"
          },
          "response_usage": "优先消费 threads[].items[].post；next_cursor 传给下一页 max_id；raw_json 仅用于兼容排错。",
          "next_calls": [
            "threads.profile.v1.ProfileService/ListProfileThreads"
          ],
          "workflows": [
            "create_text_post",
            "create_image_post",
            "collect_profile_threads"
          ]
        },
        {
          "name": "EditProfile",
          "identifier": "threads.profile.v1.ProfileService/EditProfile",
          "request_type": "EditProfileRequest",
          "response_type": "threads.auth.v1.CurrentUser",
          "proto": "/proto/profile.proto",
          "status": "available",
          "operation": "update",
          "idempotent": false,
          "side_effect": true,
          "python_method": "ProfileClient.edit_profile",
          "business_success": "保存后重新读取当前账号，资料字段与请求值一致。",
          "notes": "整表回传接口；支持 username/first_name/biography/is_private/external_url/url_title/uuid。调用前应读取当前值；头像和封面属于尚未抓包的独立上传端点，location 不是该端点字段。",
          "title": "保存当前账号资料",
          "module": "profile",
          "use_when": "修改用户名、显示名、简介、隐私状态或外部链接时。",
          "prerequisites": [
            "先调用 GetCurrentUser(edit=true) 读取完整原值。"
          ],
          "parameter_sources": {
            "username": "前置响应或用户修改：未修改时回填 CurrentUser.username。",
            "first_name": "前置响应或用户修改：未修改时回填 CurrentUser.full_name。",
            "biography": "前置响应或用户修改：未修改时回填 CurrentUser.biography。",
            "is_private": "前置响应或用户修改：未修改时回填 CurrentUser.is_private。",
            "uuid": "持久设备身份：当前账号绑定的 uuid。",
            "external_url": "前置响应或用户修改：未修改时回填 CurrentUser.external_url。",
            "url_title": "前置响应或用户修改：从 CurrentUser.bio_links 对应链接标题回填。"
          },
          "response_usage": "核对返回资料，并再次 GetCurrentUser(edit=true) 验证真实保存结果。",
          "next_calls": [
            "threads.auth.v1.AuthService/GetCurrentUser"
          ],
          "workflows": [
            "edit_profile"
          ]
        }
      ]
    },
    {
      "name": "threads.replies.v1.RepliesService",
      "rpcs": [
        {
          "name": "ListProfileReplies",
          "identifier": "threads.replies.v1.RepliesService/ListProfileReplies",
          "request_type": "ListProfileRepliesRequest",
          "response_type": "threads.common.v1.RawList",
          "proto": "/proto/replies.proto",
          "status": "available",
          "operation": "read",
          "idempotent": true,
          "side_effect": false,
          "python_method": "RepliesClient.list_profile_replies",
          "business_success": "RawList.raw_json 可解析，响应属于请求 user_id 的回复列表。",
          "notes": "响应结构尚未强类型化，以 RawList 透传。",
          "title": "分页读取账号回复",
          "module": "replies",
          "use_when": "采集指定账号发布的回复时。",
          "prerequisites": [
            "已有有效 token 和目标 user_id。"
          ],
          "parameter_sources": {
            "user_id": "目标对象：来自当前账号 pk、搜索结果或资料接口。",
            "max_id": "上一页响应：使用 RawList.next_cursor；首次调用省略。"
          },
          "response_usage": "解析 raw_json 获取回复对象；next_cursor 用于下一页 max_id。",
          "next_calls": [
            "threads.replies.v1.RepliesService/ListProfileReplies"
          ],
          "workflows": []
        }
      ]
    },
    {
      "name": "threads.search.v1.SearchService",
      "rpcs": [
        {
          "name": "KeywordSearch",
          "identifier": "threads.search.v1.SearchService/KeywordSearch",
          "request_type": "KeywordSearchRequest",
          "response_type": "threads.common.v1.RawList",
          "proto": "/proto/search.proto",
          "status": "available",
          "operation": "read",
          "idempotent": true,
          "side_effect": false,
          "python_method": "SearchClient.keyword_search",
          "business_success": "RawList.raw_json 可解析，结果与请求 query 相关。",
          "notes": "响应结构尚未强类型化，以 RawList 透传。",
          "title": "关键词搜索",
          "module": "search",
          "use_when": "按关键词查找 Threads 账号或内容时。",
          "prerequisites": [
            "已有有效 IGT:2 token。"
          ],
          "parameter_sources": {
            "query": "用户输入：搜索关键词。",
            "page_token": "上一页响应或 raw_json：按上游分页字段传递；首次调用省略。",
            "rank_token": "首次响应或搜索会话上下文：后续页保持同一值。",
            "search_session_id": "运行时生成：同一轮搜索分页复用同一个会话 ID。"
          },
          "response_usage": "解析 raw_json，保留未知字段；分页参数必须来自同一轮搜索响应和会话。",
          "next_calls": [
            "threads.search.v1.SearchService/KeywordSearch"
          ],
          "workflows": []
        },
        {
          "name": "ValidateTag",
          "identifier": "threads.search.v1.SearchService/ValidateTag",
          "request_type": "ValidateTagRequest",
          "response_type": "TagValidation",
          "proto": "/proto/search.proto",
          "status": "available",
          "operation": "read",
          "idempotent": true,
          "side_effect": false,
          "python_method": "SearchClient.validate_tag",
          "business_success": "返回 is_valid/is_sensitive 对应请求 tag_name。",
          "notes": "发帖主题的前置校验。",
          "title": "校验发帖主题",
          "module": "search",
          "use_when": "发帖前需要确认主题标签是否有效或敏感时。",
          "prerequisites": [
            "已有有效 IGT:2 token。"
          ],
          "parameter_sources": {
            "tag_name": "用户输入：不含 # 的主题名称。"
          },
          "response_usage": "is_valid=true 且业务允许时，才构造发帖 tag_header；is_sensitive 需触发产品确认。",
          "next_calls": [
            "threads.posts.v1.PostsService/CreateTextPost",
            "threads.posts.v1.PostsService/CreateImagePost"
          ],
          "workflows": []
        }
      ]
    }
  ]
}
