# go-threads-api Full API Reference

> 本文件是给 API 调用方和外部 AI 一次性读取的完整接口能力快照。

契约版本：0.5.0。项目使用 Go 封装 Threads Android
（Barcelona 421.0.0.50.67）私有 API，并通过 gRPC 暴露给 Python。
它不是 Meta 官方 Graph API，不承诺私有上游长期稳定。

## API Boundary And Authentication

- `AuthService/Login` 接收账号、密码、可选 2FA 与可选强类型 `Proxy`，成功后返回完整 `AccountState`。
- 业务 RPC 的账号认证、设备和可选代理都来自请求消息内的 `account_state`；不存在拆分 token metadata 入口。
- SDK 使用 `client.account(state)` 注入状态，并在成功响应后以 `account.state` 暴露刷新结果。
- `dump_account_state()` 只编码不加密；持久化层必须加密完整状态并限制日志、备份和数据库访问。
- Token、代理认证、账号密码和 2FA seed 都是敏感凭据；本文档不会提供真实值。
- 当前默认通道为本机明文 gRPC；跨主机必须由 Go server 配置 TLS 证书/私钥，SDK 使用 `tls=True`。
- 非 loopback 监听必须设置至少 32 字符的 `THREADS_GRPC_API_KEY`，SDK 通过 `api_key` 自动注入。
- 不得记录 token、cookie、密码、2FA seed、设备密钥或身份文件。

## Capability Summary

| 接口 | 模块 | 状态 | 操作 | 幂等 | 副作用 | Python 方法 |
| --- | --- | --- | --- | --- | --- | --- |
| 账号登录<br>`threads.auth.v1.AuthService/Login` | 登录与当前账号 | `experimental` | `create` | 否 | 有 | `AuthClient.login` |
| 读取当前账号<br>`threads.auth.v1.AuthService/GetCurrentUser` | 登录与当前账号 | `available` | `read` | 是 | 无 | `AccountAuthClient.get_current_user` |
| 读取推荐用户<br>`threads.feed.v1.FeedService/ListRecommendedUsers` | 推荐用户 | `available` | `read` | 是 | 无 | `FeedClient.list_recommended_users` |
| 读取关系状态<br>`threads.friendships.v1.FriendshipsService/GetFriendshipStatus` | 关系状态 | `available` | `read` | 是 | 无 | `FriendshipsClient.get_friendship_status` |
| 关注用户<br>`threads.friendships.v1.FriendshipsService/FollowUser` | 关系状态 | `available` | `create` | 是 | 有 | `FriendshipsClient.follow_user` |
| 取消关注<br>`threads.friendships.v1.FriendshipsService/UnfollowUser` | 关系状态 | `available` | `delete` | 是 | 有 | `FriendshipsClient.unfollow_user` |
| 检查冒犯文本<br>`threads.posts.v1.PostsService/CheckOffensiveText` | 帖子发布与删除 | `known-upstream-error` | `read` | 是 | 无 | `PostsClient.check_offensive_text` |
| 创建文本帖<br>`threads.posts.v1.PostsService/CreateTextPost` | 帖子发布与删除 | `available` | `create` | 否 | 有 | `PostsClient.create_text_post` |
| 上传图片<br>`threads.posts.v1.PostsService/UploadImage` | 帖子发布与删除 | `available` | `create` | 否 | 有 | `PostsClient.upload_image` |
| 创建单图帖<br>`threads.posts.v1.PostsService/CreateImagePost` | 帖子发布与删除 | `available` | `create` | 否 | 有 | `PostsClient.create_image_post` |
| 删除帖子<br>`threads.posts.v1.PostsService/DeletePost` | 帖子发布与删除 | `available` | `delete` | 否 | 有 | `PostsClient.delete_post` |
| 点赞帖子<br>`threads.posts.v1.PostsService/LikeMedia` | 帖子发布与删除 | `available` | `create` | 是 | 有 | `PostsClient.like_media` |
| 取消点赞<br>`threads.posts.v1.PostsService/UnlikeMedia` | 帖子发布与删除 | `available` | `delete` | 是 | 有 | `PostsClient.unlike_media` |
| 读取指定用户资料<br>`threads.profile.v1.ProfileService/GetUserInfo` | 资料与主页帖子 | `available` | `read` | 是 | 无 | `ProfileClient.get_user_info` |
| 分页读取主页帖子<br>`threads.profile.v1.ProfileService/ListProfileThreads` | 资料与主页帖子 | `available` | `read` | 是 | 无 | `ProfileClient.list_profile_threads` |
| 保存当前账号资料<br>`threads.profile.v1.ProfileService/EditProfile` | 资料与主页帖子 | `available` | `update` | 否 | 有 | `ProfileClient.edit_profile` |
| 分页读取账号回复<br>`threads.replies.v1.RepliesService/ListProfileReplies` | 回复采集 | `available` | `read` | 是 | 无 | `RepliesClient.list_profile_replies` |
| 关键词搜索<br>`threads.search.v1.SearchService/KeywordSearch` | 搜索与主题校验 | `available` | `read` | 是 | 无 | `SearchClient.keyword_search` |
| 校验发帖主题<br>`threads.search.v1.SearchService/ValidateTag` | 搜索与主题校验 | `available` | `read` | 是 | 无 | `SearchClient.validate_tag` |

## Embedded gRPC Full Reference

本文件由 `scripts/llms_docs.py` 根据 `proto/*.proto` 和
`docs/llms-metadata.json` 生成。禁止手工修改。

## Contract Rules

- Proto 是请求、响应、字段号和 RPC 名称的唯一事实来源。
- 除 `AuthService/Login` 外，请求消息必须携带完整 `AccountState`；它包含账号 uid、设备、Session、
  可选 Proxy 和 App 版本，成功响应会返回刷新后的状态。
- 可选 `LoginRequest.proxy` / `AccountState.proxy` 为账号选择独立 HTTP(S)/SOCKS5/SOCKS5H 出口；
  认证信息可成对省略以支持 IP 白名单代理，存在时必须脱敏。
- server 默认监听 `127.0.0.1:50051`；跨主机必须成对配置 gRPC TLS 证书/私钥，SDK 使用 `tls=True`。
  非 loopback 监听还必须配置至少 32 字符的 `THREADS_GRPC_API_KEY`，SDK 通过 `api_key` 注入。
- 列表端点返回 `ListPage(items/paging/metadata)`；调用方无需解析 JSON 字符串。
- 写接口不能因为超时自动重试，必须先查询实际副作用。

## Workflow Orchestration

<a id="workflow-verify_session"></a>

### 校验已保存的完整账号状态 `verify_session`

- 状态：`available`
- 目标：确认 AccountState 对应的真实账号，并取得后续调用使用的资料字段。
- 前置条件：已从使用者存储恢复完整 AccountState。；每个账号状态携带自己的可选 Proxy；不要让多账号共享可变状态对象。

| 步骤 | 调用接口 | 目的 | 输入与参数来源 | 响应与下一步 |
| ---: | --- | --- | --- | --- |
| 1 | `AccountAuthClient.get_current_user`<br>`threads.auth.v1.AuthService/GetCurrentUser` | 调用 get_current_user(edit=False) 校验会话。 | 先通过 client.account(state) 创建账号客户端，再传 edit=False。 | 核对 pk、username；任务结束后保存 account.state。 |

<a id="workflow-login"></a>

### 账密登录并取得 IGT:2 `login`

- 状态：`experimental`
- 目标：通过 CAA/Bloks 登录生成会话并即时核对账号身份。
- 前置条件：准备账号、密码和可选 TOTP seed；Proxy 可省略。；生产容器已经固定加载仓库版本化 keybox.xml；SDK 用户不传 keybox。；登录成功后由使用者持久化返回的 AccountState。

| 步骤 | 调用接口 | 目的 | 输入与参数来源 | 响应与下一步 |
| ---: | --- | --- | --- | --- |
| 1 | `AuthClient.login`<br>`threads.auth.v1.AuthService/Login` | 提交账号、密码、可选 2FA seed 和可选账号代理；Go server 生成首次登录设备。 | 代理可省略；传递 Proxy(host, port, protocol, username, password)。username/password 成对可选，省略时用于 IP 白名单代理；协议支持 HTTP、HTTPS、SOCKS5、SOCKS5H。配置字符串只能先通过 Proxy.from_url() 转成强类型对象。不要把密码、2FA seed 或代理凭据写入日志。 | 核对 success、account_state、verified_user.username 和 assurance；把 account_state 保存到使用者自己的存储。 |

#### 步骤间参数传递

- `LoginResponse.account_state` → `ThreadsClient.account(state)`：后续独立任务从使用者存储加载完整状态；SDK 不负责持久化。

#### 当前缺失能力

- 软件环境尚不能证明 session 可长期稳定；stable_for_automation 必须保持 false，直到硬件证明和存活观测通过。

<a id="workflow-edit_profile"></a>

### 安全编辑当前账号资料 `edit_profile`

- 状态：`available`
- 目标：修改指定资料字段，同时避免整表回传接口清空未携带的原值。
- 前置条件：已从使用者存储恢复完整 AccountState。

| 步骤 | 调用接口 | 目的 | 输入与参数来源 | 响应与下一步 |
| ---: | --- | --- | --- | --- |
| 1 | `AccountAuthClient.get_current_user`<br>`threads.auth.v1.AuthService/GetCurrentUser` | 先调用 get_current_user(edit=True) 读取完整当前资料。 | edit=True。 | 保存 username、full_name、biography、is_private、external_url 和 bio_links。 |
| 2 | `ProfileClient.edit_profile`<br>`threads.profile.v1.ProfileService/EditProfile` | 只替换目标字段，其余字段使用上一步原值并整表提交。 | username/first_name/biography/is_private 必须完整回传；设备身份由 AccountState 内部提供。 | 核对返回资料，再调用 get_current_user(edit=True) 验证保存结果。 |

#### 步骤间参数传递

- `CurrentUser.username/full_name/biography/is_private/external_url` → `EditProfileRequest 对应字段`：未修改字段必须回填原值，不能省略。

#### 当前缺失能力

- 头像和封面需要独立上传端点，当前契约未实现。
- location 不是 EditProfile 端点字段。

<a id="workflow-create_text_post"></a>

### 发布文本帖 `create_text_post`

- 状态：`available`
- 目标：使用当前账号和持久设备身份创建纯文本帖子。
- 前置条件：已从登录响应或使用者存储取得完整 AccountState。；写操作已获得明确授权。

| 步骤 | 调用接口 | 目的 | 输入与参数来源 | 响应与下一步 |
| ---: | --- | --- | --- | --- |
| 1 | `PostsClient.create_text_post`<br>`threads.posts.v1.PostsService/CreateTextPost` | 用 ThreadsClient.account(state).posts 提交 caption 和可选发帖功能字段。 | AccountState 自动提供 uid、完整设备、Session、代理与 App 版本；纯文本帖不需要先上传媒体。 | 核对 Media 的作者、caption、code 和 permalink，并把 account.state 的刷新结果写回使用者存储。 |
| 2 | `ProfileClient.list_profile_threads`<br>`threads.profile.v1.ProfileService/ListProfileThreads` | 重新读取账号主页确认帖子真实出现。 | user_id 使用 AccountState.uid。 | 在 threads[].items[].post 中找到新帖子。 |

#### 步骤间参数传递

- `LoginResponse.account_state 或使用者存储` → `CreateTextPostRequest.account_state`：每个独立任务显式传入完整状态；响应状态覆盖保存后供下一任务使用。

<a id="workflow-create_image_post"></a>

### 发布单图帖 `create_image_post`

- 状态：`available`
- 目标：先上传 WebP 图片，再用上传结果创建单图帖子。
- 前置条件：已从登录响应或使用者存储取得完整 AccountState。；准备 WebP 图片字节及真实宽高。；写操作已获得明确授权。

| 步骤 | 调用接口 | 目的 | 输入与参数来源 | 响应与下一步 |
| ---: | --- | --- | --- | --- |
| 1 | `PostsClient.upload_image`<br>`threads.posts.v1.PostsService/UploadImage` | 上传图片二进制及真实宽高。 | image_data 为 WebP bytes；original_width/original_height 来自图片元数据。 | 要求 status=ok，并保存响应 upload_id。 |
| 2 | `PostsClient.create_image_post`<br>`threads.posts.v1.PostsService/CreateImagePost` | 使用上传响应创建单图帖。 | upload_id 必须使用 UploadImageResult.upload_id；宽高与上传步骤保持一致。 | 核对 media_type=1、caption、image_versions2 和帖子作者。 |
| 3 | `ProfileClient.list_profile_threads`<br>`threads.profile.v1.ProfileService/ListProfileThreads` | 重新读取账号主页确认图片帖真实出现。 | user_id 使用发帖 uid。 | 在 threads[].items[].post 中找到新帖子和图片字段。 |

#### 步骤间参数传递

- `UploadImageResult.upload_id` → `CreateImagePostRequest.upload_id`：图片上传返回值必须原样传给创建接口。
- `UploadImageRequest.original_width/original_height` → `CreateImagePostRequest.original_width/original_height`：创建接口使用与上传图片一致的真实尺寸。

<a id="workflow-create_video_post"></a>

### 发布视频帖 `create_video_post`

- 状态：`unavailable`
- 目标：上传视频并创建视频帖子。
- 前置条件：无

当前没有可执行调用步骤。

#### 当前缺失能力

- 当前 Proto、Go SDK 和 Python 门面均没有视频上传 RPC。
- 当前没有视频 configure RPC、视频转码状态查询或封面上传工作流。
- 在完成真实抓包、契约和黑盒测试前，禁止复用 UploadImage/CreateImagePost 伪装视频发布。

<a id="workflow-collect_profile_threads"></a>

### 分页采集主页帖子 `collect_profile_threads`

- 状态：`available`
- 目标：读取指定账号主页帖子，并使用游标持续翻页。
- 前置条件：已从使用者存储恢复完整 AccountState。；准备目标 user_id。

| 步骤 | 调用接口 | 目的 | 输入与参数来源 | 响应与下一步 |
| ---: | --- | --- | --- | --- |
| 1 | `ProfileClient.list_profile_threads`<br>`threads.profile.v1.ProfileService/ListProfileThreads` | 首次调用不传 max_id，读取 threads 强类型结果。 | user_id 为目标账号；exclude_reposts 按采集需求设置。 | 消费 threads[].items[].post，并读取 next_cursor。 |
| 2 | `ProfileClient.list_profile_threads`<br>`threads.profile.v1.ProfileService/ListProfileThreads` | next_cursor 非空时继续请求下一页。 | 将上一页 next_cursor 传入下一次 max_id。 | next_cursor 为空时结束；raw_json 只用于未建模字段排错。 |

#### 步骤间参数传递

- `ProfileThreadsPage.next_cursor` → `ListProfileThreadsRequest.max_id`：分页游标来自上一次真实响应。

## RPC Inventory

| 接口 | 模块 | 状态 | 操作 | 幂等 | 副作用 | Python 方法 |
| --- | --- | --- | --- | --- | --- | --- |
| 账号登录<br>`threads.auth.v1.AuthService/Login` | 登录与当前账号 | `experimental` | `create` | 否 | 有 | `AuthClient.login` |
| 读取当前账号<br>`threads.auth.v1.AuthService/GetCurrentUser` | 登录与当前账号 | `available` | `read` | 是 | 无 | `AccountAuthClient.get_current_user` |
| 读取推荐用户<br>`threads.feed.v1.FeedService/ListRecommendedUsers` | 推荐用户 | `available` | `read` | 是 | 无 | `FeedClient.list_recommended_users` |
| 读取关系状态<br>`threads.friendships.v1.FriendshipsService/GetFriendshipStatus` | 关系状态 | `available` | `read` | 是 | 无 | `FriendshipsClient.get_friendship_status` |
| 关注用户<br>`threads.friendships.v1.FriendshipsService/FollowUser` | 关系状态 | `available` | `create` | 是 | 有 | `FriendshipsClient.follow_user` |
| 取消关注<br>`threads.friendships.v1.FriendshipsService/UnfollowUser` | 关系状态 | `available` | `delete` | 是 | 有 | `FriendshipsClient.unfollow_user` |
| 检查冒犯文本<br>`threads.posts.v1.PostsService/CheckOffensiveText` | 帖子发布与删除 | `known-upstream-error` | `read` | 是 | 无 | `PostsClient.check_offensive_text` |
| 创建文本帖<br>`threads.posts.v1.PostsService/CreateTextPost` | 帖子发布与删除 | `available` | `create` | 否 | 有 | `PostsClient.create_text_post` |
| 上传图片<br>`threads.posts.v1.PostsService/UploadImage` | 帖子发布与删除 | `available` | `create` | 否 | 有 | `PostsClient.upload_image` |
| 创建单图帖<br>`threads.posts.v1.PostsService/CreateImagePost` | 帖子发布与删除 | `available` | `create` | 否 | 有 | `PostsClient.create_image_post` |
| 删除帖子<br>`threads.posts.v1.PostsService/DeletePost` | 帖子发布与删除 | `available` | `delete` | 否 | 有 | `PostsClient.delete_post` |
| 点赞帖子<br>`threads.posts.v1.PostsService/LikeMedia` | 帖子发布与删除 | `available` | `create` | 是 | 有 | `PostsClient.like_media` |
| 取消点赞<br>`threads.posts.v1.PostsService/UnlikeMedia` | 帖子发布与删除 | `available` | `delete` | 是 | 有 | `PostsClient.unlike_media` |
| 读取指定用户资料<br>`threads.profile.v1.ProfileService/GetUserInfo` | 资料与主页帖子 | `available` | `read` | 是 | 无 | `ProfileClient.get_user_info` |
| 分页读取主页帖子<br>`threads.profile.v1.ProfileService/ListProfileThreads` | 资料与主页帖子 | `available` | `read` | 是 | 无 | `ProfileClient.list_profile_threads` |
| 保存当前账号资料<br>`threads.profile.v1.ProfileService/EditProfile` | 资料与主页帖子 | `available` | `update` | 否 | 有 | `ProfileClient.edit_profile` |
| 分页读取账号回复<br>`threads.replies.v1.RepliesService/ListProfileReplies` | 回复采集 | `available` | `read` | 是 | 无 | `RepliesClient.list_profile_replies` |
| 关键词搜索<br>`threads.search.v1.SearchService/KeywordSearch` | 搜索与主题校验 | `available` | `read` | 是 | 无 | `SearchClient.keyword_search` |
| 校验发帖主题<br>`threads.search.v1.SearchService/ValidateTag` | 搜索与主题校验 | `available` | `read` | 是 | 无 | `SearchClient.validate_tag` |

## RPC Requests And Responses

### 账号登录

- RPC：`threads.auth.v1.AuthService/Login`
- gRPC 方法：`/threads.auth.v1.AuthService/Login`
- Python 门面：`AuthClient.login`
- 所属模块：登录与当前账号（`auth`）
- 何时调用：没有可用 IGT:2，并且需要通过账密建立新会话时。
- 前置条件：生产容器已固定加载 keybox signer；账号代理可选。
- Metadata：无需账号 metadata；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`create`
- 状态：`experimental`
- 幂等：否
- 副作用：有
- 业务成功：success=true、account_state.session.token 为 IGT:2，且 verified_user.username 与请求账号一致；stable_for_automation 仍必须为 false，直到硬件证明和长期存活观测另行通过。
- 响应用途：保存 account_state，核对 verified_user.username 和 assurance；后续任务用 ThreadsClient.account(state) 恢复，stable_for_automation 不能由即时成功推断。
- 后续接口：threads.posts.v1.PostsService/CreateTextPost
- 所属工作流：login
- 说明：account_state 是后续独立任务的完整输入，SDK 与 Go server 均不负责持久化。Go server 从成套真机模板生成首次登录设备，并使用生产镜像内固定 keybox signer；keybox 不得由 SDK 用户传递或从 Go server 镜像移除，无 signer 时返回 FAILED_PRECONDITION。代理可省略；登录只接受 Python Proxy 强类型对象，Go server 契约同样使用 Proxy 消息。协议支持 HTTP(S)、SOCKS5、SOCKS5H，认证账号密码必须成对提供或同时省略，省略时支持 IP 白名单代理。配置字符串只能通过 Proxy.from_url() 显式转换，不存在 proxy_url 兼容入口。登录成功后必须保存并复用 account_state。

#### 请求 `threads.auth.v1.LoginRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `username` | `string` | 1 | 单值 | 用户输入：Threads/Instagram 登录账号。 | - |
| `password` | `string` | 2 | 单值 | 受保护账号配置：禁止写入源码和日志。 | - |
| `two_factor_seed` | `string` | 3 | 可选 | 受保护账号配置：启用 TOTP 时提供。 | - |
| `proxy` | `Proxy` | 7 | 可选 | 可选账号出口：由 Proxy 强类型对象提供；认证账号密码必须同时提供或同时省略。 | - |

#### 响应 `threads.auth.v1.LoginResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `success` | `bool` | 1 | 单值 | success 只有在取到 IGT:2 且用同一代理、设备完成 whoami 身份校验后才为 true。 |
| `steps` | `LoginStep` | 3 | 数组 | - |
| `verified_user` | `CurrentUser` | 5 | 可选 | - |
| `assurance` | `LoginAssurance` | 6 | 单值 | - |
| `stable_for_automation` | `bool` | 7 | 单值 | 当前实现永不把即时登录成功等价为长期稳定；必须由真实硬件证明和存活观测另行确认。 |
| `stability_warning` | `string` | 8 | 单值 | - |
| `account_state` | `AccountState` | 9 | 可选 | 登录成功后的完整状态。使用者负责保存，并在发帖等后续独立任务中重新传入。 |

#### Python 调用签名

```python
async def login(*, username: str, password: str, two_factor_seed: str | None=None, proxy: Proxy | None=None, timeout: float | None=None) -> LoginResult
```

### 读取当前账号

- RPC：`threads.auth.v1.AuthService/GetCurrentUser`
- gRPC 方法：`/threads.auth.v1.AuthService/GetCurrentUser`
- Python 门面：`AccountAuthClient.get_current_user`
- 所属模块：登录与当前账号（`auth`）
- 何时调用：校验 AccountState 会话、取得当前 uid，或在资料编辑前读取完整原值时。
- 前置条件：已通过 client.account(state) 恢复完整账号状态。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`read`
- 状态：`available`
- 幂等：是
- 副作用：无
- 业务成功：响应 pk 与 AccountState.uid/session 所属账号一致，且 AccountClient.state 已由内部响应信封刷新。
- 响应用途：pk 是发帖 uid 和当前账号 user_id；edit=true 时使用完整资料字段回填 EditProfile。
- 后续接口：threads.profile.v1.ProfileService/EditProfile；threads.posts.v1.PostsService/CreateTextPost；threads.posts.v1.PostsService/CreateImagePost
- 所属工作流：verify_session；login；edit_profile；create_text_post；create_image_post
- 说明：读取当前账号；edit=true 时返回更完整的资料字段。必须从 AccountClient 调用。

#### 请求 `threads.auth.v1.GetCurrentUserRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `edit` | `bool` | 1 | 可选 | 调用场景：普通身份校验传 false；资料编辑前传 true。 | edit=true：编辑资料页的读取场景（字段更全）。 |
| `account_state` | `AccountState` | 2 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 响应 `threads.auth.v1.GetCurrentUserResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `user` | `CurrentUser` | 1 | 单值 | - |
| `account_state` | `AccountState` | 2 | 单值 | - |

#### Python 返回 `threads.auth.v1.CurrentUser`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `pk` | `string` | 1 | 单值 | - |
| `username` | `string` | 2 | 单值 | - |
| `full_name` | `string` | 3 | 可选 | - |
| `biography` | `string` | 4 | 可选 | - |
| `profile_pic_url` | `string` | 5 | 可选 | - |
| `email` | `string` | 6 | 可选 | - |
| `text_app_biography` | `string` | 7 | 可选 | - |
| `external_url` | `string` | 8 | 可选 | - |
| `bio_links` | `BioLink` | 9 | 数组 | - |
| `text_app_cover_photo_url` | `string` | 10 | 可选 | - |
| `is_private` | `bool` | 11 | 可选 | - |
| `is_verified` | `bool` | 12 | 可选 | - |

#### Python 调用签名

```python
async def get_current_user(edit: bool | None=None, *, timeout: float | None=None) -> auth_pb2.CurrentUser
```

### 读取推荐用户

- RPC：`threads.feed.v1.FeedService/ListRecommendedUsers`
- gRPC 方法：`/threads.feed.v1.FeedService/ListRecommendedUsers`
- Python 门面：`FeedClient.list_recommended_users`
- 所属模块：推荐用户（`feed`）
- 何时调用：需要获取 Threads 推荐账号或继续推荐流分页时。
- 前置条件：AccountClient 已绑定完整 AccountState。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`read`
- 状态：`available`
- 幂等：是
- 副作用：无
- 业务成功：ListPage.items 与请求推荐场景一致，paging 游标来自同一次真实响应。
- 响应用途：直接遍历 items；paging.next_cursor 用于下一页 paging_token；metadata 保留未知顶层字段。
- 后续接口：threads.feed.v1.FeedService/ListRecommendedUsers
- 所属工作流：无
- 说明：返回结构化 items/paging/metadata，不暴露原始 JSON 字符串。

#### 请求 `threads.feed.v1.ListRecommendedUsersRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `paging_token` | `string` | 1 | 可选 | 上一页响应：使用 ListPage.paging.next_cursor；首次调用省略。 | 分页游标（对应 paging_token）。 |
| `recommendation_type` | `string` | 2 | 可选 | 产品场景配置：按上游支持的推荐类型填写，未知时省略。 | 推荐类型（recommended_users / great_accounts / ...，见报告 §5.1）。 |
| `account_state` | `threads.auth.v1.AccountState` | 3 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 响应 `threads.common.v1.ListPageResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `page` | `ListPage` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

#### Python 返回 `threads.common.v1.ListPage`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `items` | `google.protobuf.Struct` | 1 | 数组 | - |
| `paging` | `Paging` | 2 | 单值 | - |
| `metadata` | `google.protobuf.Struct` | 3 | 单值 | 除列表和分页字段外的上游顶层字段，供兼容未知增量字段。 |

#### Python 调用签名

```python
async def list_recommended_users(paging_token: str | None=None, recommendation_type: str | None=None, *, timeout: float | None=None) -> common_pb2.ListPage
```

### 读取关系状态

- RPC：`threads.friendships.v1.FriendshipsService/GetFriendshipStatus`
- gRPC 方法：`/threads.friendships.v1.FriendshipsService/GetFriendshipStatus`
- Python 门面：`FriendshipsClient.get_friendship_status`
- 所属模块：关系状态（`friendships`）
- 何时调用：需要确认当前账号是否关注、被关注、拉黑或静音目标账号时。
- 前置条件：AccountClient 已绑定完整 AccountState。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`read`
- 状态：`available`
- 幂等：是
- 副作用：无
- 业务成功：返回关系状态属于请求 user_id 对应的目标账号。
- 响应用途：直接读取 following、followed_by、blocking、muting 等强类型字段。
- 后续接口：无
- 所属工作流：无
- 说明：读取关注、拉黑、静音等关系状态。

#### 请求 `threads.friendships.v1.GetFriendshipStatusRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `user_id` | `string` | 1 | 单值 | 目标对象：来自 GetUserInfo.pk、搜索结果或业务数据库。 | 目标用户数字 id。 |
| `is_external_deeplink_profile_view` | `bool` | 2 | 可选 | 调用场景：普通 API 调用使用 false。 | 对应请求参数 is_external_deeplink_profile_view（默认 false）。 |
| `account_state` | `threads.auth.v1.AccountState` | 3 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 响应 `threads.friendships.v1.GetFriendshipStatusResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `result` | `FriendshipStatus` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

#### Python 返回 `threads.friendships.v1.FriendshipStatus`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `following` | `bool` | 1 | 可选 | - |
| `followed_by` | `bool` | 2 | 可选 | - |
| `outgoing_request` | `bool` | 3 | 可选 | - |
| `incoming_request` | `bool` | 4 | 可选 | - |
| `blocking` | `bool` | 5 | 可选 | - |
| `is_blocking_reel` | `bool` | 6 | 可选 | - |
| `muting` | `bool` | 7 | 可选 | - |
| `is_muting_reel` | `bool` | 8 | 可选 | - |
| `is_muting_notes` | `bool` | 9 | 可选 | - |
| `is_muting_media_notes` | `bool` | 10 | 可选 | - |
| `is_muting_media_reposts` | `bool` | 11 | 可选 | - |
| `is_private` | `bool` | 12 | 可选 | - |
| `is_eligible_to_subscribe` | `bool` | 14 | 可选 | - |
| `subscribed` | `bool` | 13 | 可选 | 以下字段真机响应未出现，仅 JADX 静态可见。 TODO(抓包核对): 置信度=高概率; 来源=JADX parser C31991Ov; 三份真机样本均未返回 |
| `is_viewer_unconnected` | `bool` | 15 | 可选 | - |
| `should_show_profile_upsell` | `bool` | 16 | 可选 | - |
| `is_banner_profile_upsell` | `bool` | 17 | 可选 | - |
| `reachability_status` | `int64` | 18 | 可选 | int 枚举（apis/friendships/关注关系.md） |
| `is_bestie` | `bool` | 20 | 可选 | 真机 show 与 create/destroy 均返回，此前模型缺失。 |
| `is_restricted` | `bool` | 21 | 可选 | - |
| `is_feed_favorite` | `bool` | 22 | 可选 | - |
| `text_post_app_pre_following` | `bool` | 23 | 可选 | - |

#### Python 调用签名

```python
async def get_friendship_status(user_id: str, is_external_deeplink_profile_view: bool=False, *, timeout: float | None=None) -> 'friendships_pb2.FriendshipStatus'
```

### 关注用户

- RPC：`threads.friendships.v1.FriendshipsService/FollowUser`
- gRPC 方法：`/threads.friendships.v1.FriendshipsService/FollowUser`
- Python 门面：`FriendshipsClient.follow_user`
- 所属模块：关系状态（`friendships`）
- 何时调用：关注目标账号时。
- 前置条件：完整 AccountState、目标 user_id、写操作授权。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`create`
- 状态：`available`
- 幂等：是
- 副作用：有
- 业务成功：result.status.following=true，并用 GetFriendshipStatus 独立回读确认关注生效。
- 响应用途：以 result.status.following 判定结果；previous_following 区分「本次新增关注」与「此前已关注」。
- 后续接口：threads.friendships.v1.FriendshipsService/GetFriendshipStatus
- 所属工作流：无
- 说明：真实关系写操作；previous_following=false 表示本次确实产生变化，true 表示此前已关注。

#### 请求 `threads.friendships.v1.FollowUserRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `user_id` | `string` | 1 | 单值 | 用户输入或前置响应：目标用户数字 id，可来自 GetUserInfo、推荐流或搜索结果。 | 目标用户数字 id。 |
| `container_module` | `string` | 2 | 可选 | 固定参数：缺省 ig_text_feed_profile；从帖子详情页操作时传 ig_text_post_permalink。 | 埋点归因；缺省 container_module=ig_text_feed_profile。 |
| `nav_chain` | `string` | 3 | 可选 | 埋点参数：可留空；需要贴合真机时用进入该用户页那次调用的导航链。 | - |
| `attribution_media_id` | `string` | 4 | 可选 | 前置响应：从某个帖子发起关注时传该帖复合 media_id（{pk}_{author_uid}）；直接按 user_id 操作时留空。 | 非空表示这次关注来自某个帖子，会同时写入 media_id 与 media_id_attribution； 直接按 user_id 关注时留空。 |
| `ranking_info_token` | `string` | 5 | 可选 | 前置响应：时间线/回复流里该帖的排序归因 token；留空则不发送。 | 该帖的排序归因 token，留空则不发送。 |
| `account_state` | `threads.auth.v1.AccountState` | 6 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 响应 `threads.friendships.v1.FollowUserResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `result` | `FollowResult` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

#### Python 返回 `threads.friendships.v1.FollowResult`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `status` | `FriendshipStatus` | 1 | 单值 | 操作后的关系状态；判定成功以 status.following 为准。 |
| `previous_following` | `bool` | 2 | 可选 | 操作前是否已关注。create 返回布尔值，destroy 真机返回 null（此处不设值）。 |
| `error` | `string` | 3 | 可选 | 上游业务级错误文案；两份真机样本均为 null。 TODO(抓包核对): 置信度=推测; 非空取值形态未观察到。 |

#### Python 调用签名

```python
async def follow_user(user_id: str, container_module: str | None=None, nav_chain: str | None=None, attribution_media_id: str | None=None, ranking_info_token: str | None=None, *, timeout: float | None=None) -> 'friendships_pb2.FollowResult'
```

### 取消关注

- RPC：`threads.friendships.v1.FriendshipsService/UnfollowUser`
- gRPC 方法：`/threads.friendships.v1.FriendshipsService/UnfollowUser`
- Python 门面：`FriendshipsClient.unfollow_user`
- 所属模块：关系状态（`friendships`）
- 何时调用：取消对目标账号的关注时。
- 前置条件：完整 AccountState、目标 user_id、写操作授权。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`delete`
- 状态：`available`
- 幂等：是
- 副作用：有
- 业务成功：result.status.following=false，并用 GetFriendshipStatus 独立回读确认已取关。
- 响应用途：以 result.status.following=false 判定结果；该接口不返回 previous_following。
- 后续接口：threads.friendships.v1.FriendshipsService/GetFriendshipStatus
- 所属工作流：无
- 说明：真实关系写操作；取关不返回 previous_following，不要依赖该字段判断结果。

#### 请求 `threads.friendships.v1.UnfollowUserRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `user_id` | `string` | 1 | 单值 | 用户输入或前置响应：目标用户数字 id，可来自 GetUserInfo、推荐流或搜索结果。 | - |
| `container_module` | `string` | 2 | 可选 | 固定参数：缺省 ig_text_feed_profile；从帖子详情页操作时传 ig_text_post_permalink。 | - |
| `nav_chain` | `string` | 3 | 可选 | 埋点参数：可留空；需要贴合真机时用进入该用户页那次调用的导航链。 | - |
| `attribution_media_id` | `string` | 4 | 可选 | 前置响应：从某个帖子发起关注时传该帖复合 media_id（{pk}_{author_uid}）；直接按 user_id 操作时留空。 | - |
| `ranking_info_token` | `string` | 5 | 可选 | 前置响应：时间线/回复流里该帖的排序归因 token；留空则不发送。 | - |
| `account_state` | `threads.auth.v1.AccountState` | 6 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 响应 `threads.friendships.v1.UnfollowUserResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `result` | `FollowResult` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

#### Python 返回 `threads.friendships.v1.FollowResult`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `status` | `FriendshipStatus` | 1 | 单值 | 操作后的关系状态；判定成功以 status.following 为准。 |
| `previous_following` | `bool` | 2 | 可选 | 操作前是否已关注。create 返回布尔值，destroy 真机返回 null（此处不设值）。 |
| `error` | `string` | 3 | 可选 | 上游业务级错误文案；两份真机样本均为 null。 TODO(抓包核对): 置信度=推测; 非空取值形态未观察到。 |

#### Python 调用签名

```python
async def unfollow_user(user_id: str, container_module: str | None=None, nav_chain: str | None=None, attribution_media_id: str | None=None, ranking_info_token: str | None=None, *, timeout: float | None=None) -> 'friendships_pb2.FollowResult'
```

### 检查冒犯文本

- RPC：`threads.posts.v1.PostsService/CheckOffensiveText`
- gRPC 方法：`/threads.posts.v1.PostsService/CheckOffensiveText`
- Python 门面：`PostsClient.check_offensive_text`
- 所属模块：帖子发布与删除（`posts`）
- 何时调用：发帖前希望执行上游文本风险检查时；当前真实上游返回 404。
- 前置条件：AccountClient 已绑定完整 AccountState。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`read`
- 状态：`known-upstream-error`
- 幂等：是
- 副作用：无
- 业务成功：当前真实上游返回 HTTP 404，测试以真实错误映射为验收结果。
- 响应用途：当前以 gRPC 错误路径处理，不能把未返回结果视为安全。
- 后续接口：无
- 所属工作流：无
- 说明：请求结构与真实 App 抓包一致，但当前线上端点不可用。

#### 请求 `threads.posts.v1.CheckOffensiveTextRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `text_list` | `string` | 1 | 数组 | 用户内容：待检查的一段或多段文本。 | 待检测文本（对应 form text_list，JSON 数组）。 |
| `media_id` | `string` | 2 | 可选 | 已有媒体上下文：编辑或关联媒体时提供，否则省略。 | 可选关联 media_id。 |
| `account_state` | `threads.auth.v1.AccountState` | 3 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 响应 `threads.posts.v1.CheckOffensiveTextResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `result` | `OffensiveCheck` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

#### Python 返回 `threads.posts.v1.OffensiveCheck`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `is_offensive` | `bool` | 1 | 单值 | - |
| `minimum_next_timestamp` | `int64` | 2 | 可选 | - |
| `text_language` | `string` | 3 | 可选 | - |

#### Python 调用签名

```python
async def check_offensive_text(text_list: Sequence[str], media_id: str | None=None, *, timeout: float | None=None) -> posts_pb2.OffensiveCheck
```

### 创建文本帖

- RPC：`threads.posts.v1.PostsService/CreateTextPost`
- gRPC 方法：`/threads.posts.v1.PostsService/CreateTextPost`
- Python 门面：`PostsClient.create_text_post`
- 所属模块：帖子发布与删除（`posts`）
- 何时调用：发布不带图片或视频的 Threads 文本帖子时；传 reply_id 时同一接口用于发布回复（评论）。
- 前置条件：完整 AccountState、写操作授权。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`create`
- 状态：`available`
- 幂等：否
- 副作用：有
- 业务成功：返回帖子属于 account_state.uid，caption 与请求一致，并能从账号主页重新读取；AccountClient.state 已刷新。
- 响应用途：保存 id/code/permalink，核对 caption、user 和主页结果；SDK 从内部响应信封刷新状态，调用方将 AccountClient.state 覆盖写回自己的存储。
- 后续接口：threads.profile.v1.ProfileService/ListProfileThreads
- 所属工作流：create_text_post
- 说明：推荐通过 AccountClient 调用并在任务完成后保存 AccountClient.state。真实发帖操作；必须显式授权，调用方不得自动重试。

#### 请求 `threads.posts.v1.CreateTextPostRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `caption` | `string` | 1 | 单值 | 用户输入：帖子正文。 | 正文（对应 caption）。 |
| `upload_id` | `string` | 5 | 可选 | 运行时生成：可省略，由 Go SDK 生成；重试时不得随意复用。 | 幂等批次 id（缺省由 SDK 生成 upload_id、publish_id 固定 "1"）。 |
| `reply_control` | `int32` | 6 | 可选 | 产品设置：回复权限枚举，默认 0。 | 回复权限：0=everyone（对应 text_post_app_info.reply_control）。 |
| `camera_session_id` | `string` | 8 | 可选 | 运行时生成：模拟发布会话时提供，否则省略。 | 埋点/会话（必填，缺省由 SDK 生成/留空）。 |
| `nav_chain` | `string` | 9 | 可选 | 运行时上下文：导航链，普通调用可省略。 | - |
| `timezone_offset` | `string` | 10 | 可选 | 运行环境：账号所在地时区偏移。 | - |
| `tag_header` | `string` | 11 | 可选 | 主题功能：先用 ValidateTag 校验后按上游格式提供。 | 可选功能：主题 / 位置 / 投票（勾选才传）。 主题 display_text |
| `location` | `Location` | 12 | 可选 | 用户选择：posts_pb2.Location，未选择位置时省略。 | - |
| `poll` | `Poll` | 13 | 可选 | 用户输入：posts_pb2.Poll，未创建投票时省略。 | - |
| `account_state` | `threads.auth.v1.AccountState` | 14 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态，包含 uid、设备、Session、可选代理和 App 版本。 | 登录返回或由使用者存储恢复的完整状态。 |
| `reply_id` | `string` | 15 | 可选 | 可选参数：非空即表示发布回复（评论），取被回复帖的纯数字 pk（Media.pk 或 post.pk，不是 {pk}_{author_uid} 复合 id）；留空为新建独立帖。 | 非空表示这是对某帖的回复（评论），值为被回复帖的 pk（纯数字，不带 _uid 后缀）。 回复与新建帖是同一端点的两种形态，仅 text_post_app_info 内的入口字段不同。 |
| `ranking_info_token` | `string` | 16 | 可选 | 前置响应：时间线/回复流响应里该帖的排序归因 token；留空则不发送该字段。 | 时间线/回复流响应里该帖的排序归因 token；回复时真机会带上，置空则不发送。 |

#### 响应 `threads.posts.v1.CreateTextPostResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `media` | `Media` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

#### Python 返回 `threads.posts.v1.Media`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `id` | `string` | 1 | 单值 | - |
| `pk` | `int64` | 2 | 单值 | - |
| `fbid` | `int64` | 3 | 单值 | - |
| `taken_at` | `int64` | 4 | 单值 | - |
| `media_type` | `int32` | 5 | 单值 | 19 = text_post |
| `code` | `string` | 6 | 单值 | 短代码 |
| `product_type` | `string` | 7 | 单值 | text_post |
| `permalink` | `string` | 8 | 单值 | - |
| `integrity_review_decision` | `string` | 9 | 单值 | pending 表示异步审核中 |
| `caption` | `Caption` | 10 | 单值 | - |
| `text_post_app_info` | `TextPostAppInfo` | 11 | 单值 | - |
| `has_liked` | `bool` | 12 | 单值 | - |
| `like_count` | `int64` | 13 | 单值 | - |
| `meta_place` | `MetaPlace` | 14 | 可选 | 位置回显（带位置发帖时） |
| `image_versions2` | `ImageVersions2` | 15 | 可选 | - |
| `original_width` | `int32` | 16 | 单值 | - |
| `original_height` | `int32` | 17 | 单值 | - |
| `user` | `MediaUser` | 18 | 可选 | 列表端点的顶层作者 |

#### Python 调用签名

```python
async def create_text_post(caption: str, upload_id: str | None=None, reply_control: int=0, camera_session_id: str | None=None, nav_chain: str | None=None, timezone_offset: str | None=None, tag_header: str | None=None, location: posts_pb2.Location | None=None, poll: posts_pb2.Poll | None=None, reply_id: str | None=None, ranking_info_token: str | None=None, *, timeout: float | None=None) -> posts_pb2.Media
```

### 上传图片

- RPC：`threads.posts.v1.PostsService/UploadImage`
- gRPC 方法：`/threads.posts.v1.PostsService/UploadImage`
- Python 门面：`PostsClient.upload_image`
- 所属模块：帖子发布与删除（`posts`）
- 何时调用：创建单图帖前上传 WebP 图片二进制时。
- 前置条件：完整 AccountState、WebP 图片字节、真实宽高、写操作授权。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`create`
- 状态：`available`
- 幂等：否
- 副作用：有
- 业务成功：响应 status=ok 且 upload_id 非空；该 upload_id 可被 CreateImagePost 接受。
- 响应用途：status 必须为 ok；将 upload_id 原样传给 CreateImagePost。
- 后续接口：threads.posts.v1.PostsService/CreateImagePost
- 所属工作流：create_image_post
- 说明：真实图片二进制上传；当前抓包确认 image/webp，视频上传仍缺少抓包来源。

#### 请求 `threads.posts.v1.UploadImageRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `image_data` | `bytes` | 1 | 单值 | 本地文件或内存：WebP 原始 bytes。 | 原始图片字节；当前已确认样本为 image/webp。 |
| `original_width` | `int32` | 2 | 单值 | 图片元数据：解码后的真实像素宽度。 | - |
| `original_height` | `int32` | 3 | 单值 | 图片元数据：解码后的真实像素高度。 | - |
| `upload_id` | `string` | 4 | 可选 | 运行时生成：通常省略并由 Go SDK 生成。 | 缺省由 SDK 生成；后续 CreateImagePost 必须使用响应中的 upload_id。 |
| `mime_type` | `string` | 5 | 可选 | 固定协议值：当前确认 image/webp。 | 缺省 image/webp；当前仅确认 image/webp。 |
| `is_optimistic_upload` | `bool` | 6 | 可选 | 上传策略：通常省略使用 SDK 默认值。 | App 抓包存在乐观预上传与正式上传两种模式；缺省为正式上传。 |
| `msssim` | `double` | 7 | 可选 | 图片质量计算：调用方实际计算时提供，否则省略。 | 图片压缩质量指标；调用方掌握真实编码结果时再传。 |
| `ssim` | `double` | 8 | 可选 | 图片质量计算：调用方实际计算时提供，否则省略。 | - |
| `waterfall_id` | `string` | 9 | 可选 | 运行时生成：上传链路追踪 ID，通常省略。 | 缺省由 SDK 生成。 |
| `account_state` | `threads.auth.v1.AccountState` | 10 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 响应 `threads.posts.v1.UploadImageResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `result` | `UploadImageResult` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

#### Python 返回 `threads.posts.v1.UploadImageResult`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `upload_id` | `string` | 1 | 单值 | - |
| `status` | `string` | 2 | 单值 | - |

#### Python 调用签名

```python
async def upload_image(image_data: bytes, original_width: int, original_height: int, upload_id: str | None=None, mime_type: str | None=None, is_optimistic_upload: bool | None=None, msssim: float | None=None, ssim: float | None=None, waterfall_id: str | None=None, *, timeout: float | None=None) -> 'posts_pb2.UploadImageResult'
```

### 创建单图帖

- RPC：`threads.posts.v1.PostsService/CreateImagePost`
- gRPC 方法：`/threads.posts.v1.PostsService/CreateImagePost`
- Python 门面：`PostsClient.create_image_post`
- 所属模块：帖子发布与删除（`posts`）
- 何时调用：UploadImage 成功后，把已上传图片发布为单图帖子时。
- 前置条件：UploadImageResult.status=ok、完整 AccountState、写操作授权。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`create`
- 状态：`available`
- 幂等：否
- 副作用：有
- 业务成功：返回 media_type=1，正文与请求一致，image_versions2 非空，并能从账号主页重新读取。
- 响应用途：核对 media_type=1、image_versions2、caption 和 user，并保存 id/code/permalink。
- 后续接口：threads.profile.v1.ProfileService/ListProfileThreads
- 所属工作流：create_image_post
- 说明：单图帖 configure；调用前必须先用 UploadImage 获取 upload_id，调用方不得自动重试。

#### 请求 `threads.posts.v1.CreateImagePostRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `caption` | `string` | 1 | 单值 | 用户输入：帖子正文。 | 图片帖允许空正文。 |
| `upload_id` | `string` | 5 | 单值 | 前置响应：UploadImageResult.upload_id。 | 必须使用 UploadImage 返回的 upload_id。 |
| `reply_control` | `int32` | 6 | 可选 | 产品设置：回复权限枚举，默认 0。 | - |
| `camera_session_id` | `string` | 8 | 可选 | 运行时生成：模拟发布会话时提供，否则省略。 | - |
| `nav_chain` | `string` | 9 | 可选 | 运行时上下文：导航链，普通调用可省略。 | - |
| `timezone_offset` | `string` | 10 | 可选 | 运行环境：账号所在地时区偏移。 | - |
| `tag_header` | `string` | 11 | 可选 | 主题功能：先用 ValidateTag 校验后按上游格式提供。 | - |
| `location` | `Location` | 12 | 可选 | 用户选择：posts_pb2.Location，未选择位置时省略。 | - |
| `poll` | `Poll` | 13 | 可选 | 用户输入：posts_pb2.Poll，未创建投票时省略。 | - |
| `original_width` | `int32` | 14 | 单值 | 前置请求：与 UploadImage.original_width 保持一致。 | - |
| `original_height` | `int32` | 15 | 单值 | 前置请求：与 UploadImage.original_height 保持一致。 | - |
| `custom_accessibility_caption` | `string` | 16 | 可选 | 用户输入：图片无障碍说明，可省略。 | - |
| `account_state` | `threads.auth.v1.AccountState` | 17 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 响应 `threads.posts.v1.CreateImagePostResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `media` | `Media` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

#### Python 返回 `threads.posts.v1.Media`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `id` | `string` | 1 | 单值 | - |
| `pk` | `int64` | 2 | 单值 | - |
| `fbid` | `int64` | 3 | 单值 | - |
| `taken_at` | `int64` | 4 | 单值 | - |
| `media_type` | `int32` | 5 | 单值 | 19 = text_post |
| `code` | `string` | 6 | 单值 | 短代码 |
| `product_type` | `string` | 7 | 单值 | text_post |
| `permalink` | `string` | 8 | 单值 | - |
| `integrity_review_decision` | `string` | 9 | 单值 | pending 表示异步审核中 |
| `caption` | `Caption` | 10 | 单值 | - |
| `text_post_app_info` | `TextPostAppInfo` | 11 | 单值 | - |
| `has_liked` | `bool` | 12 | 单值 | - |
| `like_count` | `int64` | 13 | 单值 | - |
| `meta_place` | `MetaPlace` | 14 | 可选 | 位置回显（带位置发帖时） |
| `image_versions2` | `ImageVersions2` | 15 | 可选 | - |
| `original_width` | `int32` | 16 | 单值 | - |
| `original_height` | `int32` | 17 | 单值 | - |
| `user` | `MediaUser` | 18 | 可选 | 列表端点的顶层作者 |

#### Python 调用签名

```python
async def create_image_post(caption: str, upload_id: str, original_width: int, original_height: int, reply_control: int=0, camera_session_id: str | None=None, nav_chain: str | None=None, timezone_offset: str | None=None, tag_header: str | None=None, location: 'posts_pb2.Location | None'=None, poll: 'posts_pb2.Poll | None'=None, custom_accessibility_caption: str | None=None, *, timeout: float | None=None) -> 'posts_pb2.Media'
```

### 删除帖子

- RPC：`threads.posts.v1.PostsService/DeletePost`
- gRPC 方法：`/threads.posts.v1.PostsService/DeletePost`
- Python 门面：`PostsClient.delete_post`
- 所属模块：帖子发布与删除（`posts`）
- 何时调用：删除当前账号已有帖子或清理测试帖子时。
- 前置条件：完整 AccountState、目标 media_id、写操作授权。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`delete`
- 状态：`available`
- 幂等：否
- 副作用：有
- 业务成功：did_delete=true，且重新读取主页确认目标帖子已经消失。
- 响应用途：did_delete 必须为 true，并通过 ListProfileThreads 确认目标帖子消失。
- 后续接口：threads.profile.v1.ProfileService/ListProfileThreads
- 所属工作流：无
- 说明：真实删除操作；必须显式授权，调用方不得自动重试。

#### 请求 `threads.posts.v1.DeletePostRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `media_id` | `string` | 1 | 单值 | 前置响应：创建接口返回的 Media.id，或主页帖子中的 post.id。 | = URL 中的 media_id（pk_uid） |
| `account_state` | `threads.auth.v1.AccountState` | 4 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 响应 `threads.posts.v1.DeletePostResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `result` | `DeletePostResult` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

#### Python 返回 `threads.posts.v1.DeletePostResult`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `did_delete` | `bool` | 1 | 单值 | - |
| `cxp_deep_deletion_waterfall_id` | `string` | 2 | 可选 | - |

#### Python 调用签名

```python
async def delete_post(media_id: str, *, timeout: float | None=None) -> posts_pb2.DeletePostResult
```

### 点赞帖子

- RPC：`threads.posts.v1.PostsService/LikeMedia`
- gRPC 方法：`/threads.posts.v1.PostsService/LikeMedia`
- Python 门面：`PostsClient.like_media`
- 所属模块：帖子发布与删除（`posts`）
- 何时调用：对指定帖子点赞时。
- 前置条件：完整 AccountState、目标帖子的复合 media_id、写操作授权。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`create`
- 状态：`available`
- 幂等：是
- 副作用：有
- 业务成功：调用成功后重新读取该帖，has_liked 必须为 true。
- 响应用途：无业务返回值；成功判据是重新读取该帖，has_liked 为 true。
- 后续接口：threads.profile.v1.ProfileService/ListProfileThreads
- 所属工作流：无
- 说明：真实互动写操作；media_id 必须是 {pk}_{author_uid} 复合 id。上游成功响应只有 status，方法无返回值。

#### 请求 `threads.posts.v1.LikeMediaRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `media_id` | `string` | 1 | 单值 | 前置响应：创建接口返回的 Media.id，或主页帖子/时间线中的 post.id（形如 {pk}_{author_uid}）。 | URL 与请求体共用的复合 id：{pk}_{author_uid}。 |
| `container_module` | `string` | 2 | 可选 | 固定参数：缺省 ig_text_feed_timeline，从其他入口点赞时按实际来源传入。 | 埋点归因；缺省 container_module=ig_text_feed_timeline，其余置空则不发送。 |
| `nav_chain` | `string` | 3 | 可选 | 埋点参数：可留空；需要贴合真机时用读取该帖那次调用的导航链。 | - |
| `feed_position` | `int32` | 4 | 可选 | 埋点参数：该帖在列表中的下标，缺省 0。 | - |
| `logging_info_token` | `string` | 5 | 可选 | 前置响应：时间线响应里该帖的 logging token；留空则不发送。 | 时间线响应里该帖的 logging token。 |
| `account_state` | `threads.auth.v1.AccountState` | 6 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 响应 `threads.posts.v1.LikeMediaResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `account_state` | `threads.auth.v1.AccountState` | 1 | 单值 | - |

#### Python 调用签名

```python
async def like_media(media_id: str, container_module: str | None=None, nav_chain: str | None=None, feed_position: int | None=None, logging_info_token: str | None=None, *, timeout: float | None=None) -> None
```

### 取消点赞

- RPC：`threads.posts.v1.PostsService/UnlikeMedia`
- gRPC 方法：`/threads.posts.v1.PostsService/UnlikeMedia`
- Python 门面：`PostsClient.unlike_media`
- 所属模块：帖子发布与删除（`posts`）
- 何时调用：撤销此前对某帖的点赞时。
- 前置条件：完整 AccountState、目标帖子的复合 media_id、写操作授权。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`delete`
- 状态：`available`
- 幂等：是
- 副作用：有
- 业务成功：调用成功后重新读取该帖，has_liked 必须回到 false。
- 响应用途：无业务返回值；成功判据是重新读取该帖，has_liked 回到 false。
- 后续接口：threads.profile.v1.ProfileService/ListProfileThreads
- 所属工作流：无
- 说明：TODO(抓包核对): 路径与字段集由 like 对称推导，尚未真机核对；失败时不要自动重试。

#### 请求 `threads.posts.v1.UnlikeMediaRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `media_id` | `string` | 1 | 单值 | 前置响应：与 LikeMedia 使用同一个复合 media_id。 | - |
| `container_module` | `string` | 2 | 可选 | 固定参数：缺省 ig_text_feed_timeline。 | - |
| `nav_chain` | `string` | 3 | 可选 | 埋点参数：可留空。 | - |
| `feed_position` | `int32` | 4 | 可选 | 埋点参数：缺省 0。 | - |
| `logging_info_token` | `string` | 5 | 可选 | 前置响应：时间线响应里该帖的 logging token；留空则不发送。 | - |
| `account_state` | `threads.auth.v1.AccountState` | 6 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 响应 `threads.posts.v1.UnlikeMediaResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `account_state` | `threads.auth.v1.AccountState` | 1 | 单值 | - |

#### Python 调用签名

```python
async def unlike_media(media_id: str, container_module: str | None=None, nav_chain: str | None=None, feed_position: int | None=None, logging_info_token: str | None=None, *, timeout: float | None=None) -> None
```

### 读取指定用户资料

- RPC：`threads.profile.v1.ProfileService/GetUserInfo`
- gRPC 方法：`/threads.profile.v1.ProfileService/GetUserInfo`
- Python 门面：`ProfileClient.get_user_info`
- 所属模块：资料与主页帖子（`profile`）
- 何时调用：已知用户 ID，需要读取公开资料和账号统计时。
- 前置条件：AccountClient 已绑定完整 AccountState。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`read`
- 状态：`available`
- 幂等：是
- 副作用：无
- 业务成功：响应 pk 与请求 user_id 一致。
- 响应用途：核对 pk 等于请求 user_id，再使用 username、计数和隐私状态。
- 后续接口：threads.profile.v1.ProfileService/ListProfileThreads；threads.friendships.v1.FriendshipsService/GetFriendshipStatus
- 所属工作流：无
- 说明：读取指定用户资料。

#### 请求 `threads.profile.v1.GetUserInfoRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `user_id` | `string` | 1 | 单值 | 目标对象：来自搜索结果、帖子作者 pk 或业务数据库。 | Threads/IG 用户数字 id（pk）。 |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 响应 `threads.profile.v1.GetUserInfoResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `user` | `User` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

#### Python 返回 `threads.profile.v1.User`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `pk` | `string` | 1 | 单值 | 用户数字 id |
| `username` | `string` | 2 | 单值 | - |
| `full_name` | `string` | 3 | 可选 | - |
| `biography` | `string` | 4 | 可选 | - |
| `profile_pic_url` | `string` | 5 | 可选 | - |
| `follower_count` | `int64` | 6 | 可选 | - |
| `following_count` | `int64` | 7 | 可选 | - |
| `media_count` | `int64` | 8 | 可选 | - |
| `is_private` | `bool` | 9 | 可选 | - |
| `is_verified` | `bool` | 10 | 可选 | - |

#### Python 调用签名

```python
async def get_user_info(user_id: str, *, timeout: float | None=None) -> 'profile_pb2.User'
```

### 分页读取主页帖子

- RPC：`threads.profile.v1.ProfileService/ListProfileThreads`
- gRPC 方法：`/threads.profile.v1.ProfileService/ListProfileThreads`
- Python 门面：`ProfileClient.list_profile_threads`
- 所属模块：资料与主页帖子（`profile`）
- 何时调用：采集指定账号主页帖子、验证发帖或验证删除结果时。
- 前置条件：AccountClient 已绑定完整 AccountState，并已准备目标 user_id。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`read`
- 状态：`available`
- 幂等：是
- 副作用：无
- 业务成功：status=ok，threads[].items[].post 可直接读取，帖子作者与请求 user_id 的业务语义一致。
- 响应用途：优先消费 threads[].items[].post；next_cursor 传给下一页 max_id；raw_json 仅用于兼容排错。
- 后续接口：threads.profile.v1.ProfileService/ListProfileThreads
- 所属工作流：create_text_post；create_image_post；collect_profile_threads
- 说明：主页帖子已强类型化；raw_json 仅为旧客户端线级兼容和未建模字段排错保留。

#### 请求 `threads.profile.v1.ListProfileThreadsRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `user_id` | `string` | 1 | 单值 | 目标对象：当前账号用 GetCurrentUser.pk，其他账号来自搜索或资料接口。 | - |
| `max_id` | `string` | 2 | 可选 | 上一页响应：使用 ProfileThreadsPage.next_cursor；首次调用省略。 | 分页 |
| `exclude_reposts` | `bool` | 3 | 可选 | 采集策略：是否排除转发，可省略使用上游默认。 | - |
| `account_state` | `threads.auth.v1.AccountState` | 4 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 响应 `threads.profile.v1.ListProfileThreadsResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `page` | `ProfileThreadsPage` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

#### Python 返回 `threads.profile.v1.ProfileThreadsPage`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `raw_json` | `string` | 1 | 单值 | - |
| `next_cursor` | `string` | 2 | 可选 | - |
| `threads` | `ProfileThread` | 3 | 数组 | - |
| `status` | `string` | 4 | 单值 | - |

#### Python 调用签名

```python
async def list_profile_threads(user_id: str, max_id: str | None=None, exclude_reposts: bool | None=None, *, timeout: float | None=None) -> 'profile_pb2.ProfileThreadsPage'
```

### 保存当前账号资料

- RPC：`threads.profile.v1.ProfileService/EditProfile`
- gRPC 方法：`/threads.profile.v1.ProfileService/EditProfile`
- Python 门面：`ProfileClient.edit_profile`
- 所属模块：资料与主页帖子（`profile`）
- 何时调用：修改用户名、显示名、简介、隐私状态或外部链接时。
- 前置条件：先调用 GetCurrentUser(edit=true) 读取完整原值。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`update`
- 状态：`available`
- 幂等：否
- 副作用：有
- 业务成功：保存后重新读取当前账号，资料字段与请求值一致。
- 响应用途：核对返回资料，并再次 GetCurrentUser(edit=true) 验证真实保存结果。
- 后续接口：threads.auth.v1.AuthService/GetCurrentUser
- 所属工作流：edit_profile
- 说明：整表回传接口；公开参数支持 username/first_name/biography/is_private/external_url/url_title，设备 UUID 只从 AccountState 内部取得。调用前应读取当前值；头像和封面属于尚未抓包的独立上传端点，location 不是该端点字段。

#### 请求 `threads.profile.v1.EditProfileRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `username` | `string` | 1 | 单值 | 前置响应或用户修改：未修改时回填 CurrentUser.username。 | 必填 |
| `first_name` | `string` | 2 | 单值 | 前置响应或用户修改：未修改时回填 CurrentUser.full_name。 | 必填，显示名（= full_name） |
| `biography` | `string` | 3 | 单值 | 前置响应或用户修改：未修改时回填 CurrentUser.biography。 | 必填，个性签名（无签名传空串） |
| `is_private` | `bool` | 4 | 单值 | 前置响应或用户修改：未修改时回填 CurrentUser.is_private。 | 必填 |
| `external_url` | `string` | 6 | 可选 | 前置响应或用户修改：未修改时回填 CurrentUser.external_url。 | 可选，链接 URL；服务端写入 bio_links[] |
| `url_title` | `string` | 7 | 可选 | 前置响应或用户修改：从 CurrentUser.bio_links 对应链接标题回填。 | 可选，链接标题，配合 external_url |
| `account_state` | `threads.auth.v1.AccountState` | 8 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 响应 `threads.profile.v1.EditProfileResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `user` | `threads.auth.v1.CurrentUser` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

#### Python 返回 `threads.auth.v1.CurrentUser`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `pk` | `string` | 1 | 单值 | - |
| `username` | `string` | 2 | 单值 | - |
| `full_name` | `string` | 3 | 可选 | - |
| `biography` | `string` | 4 | 可选 | - |
| `profile_pic_url` | `string` | 5 | 可选 | - |
| `email` | `string` | 6 | 可选 | - |
| `text_app_biography` | `string` | 7 | 可选 | - |
| `external_url` | `string` | 8 | 可选 | - |
| `bio_links` | `BioLink` | 9 | 数组 | - |
| `text_app_cover_photo_url` | `string` | 10 | 可选 | - |
| `is_private` | `bool` | 11 | 可选 | - |
| `is_verified` | `bool` | 12 | 可选 | - |

#### Python 调用签名

```python
async def edit_profile(username: str, first_name: str, biography: str, is_private: bool, external_url: str | None=None, url_title: str | None=None, *, timeout: float | None=None) -> auth_pb2.CurrentUser
```

### 分页读取账号回复

- RPC：`threads.replies.v1.RepliesService/ListProfileReplies`
- gRPC 方法：`/threads.replies.v1.RepliesService/ListProfileReplies`
- Python 门面：`RepliesClient.list_profile_replies`
- 所属模块：回复采集（`replies`）
- 何时调用：采集指定账号发布的回复时。
- 前置条件：AccountClient 已绑定完整 AccountState，并已准备目标 user_id。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`read`
- 状态：`available`
- 幂等：是
- 副作用：无
- 业务成功：ListPage.items 属于请求 user_id 的回复列表。
- 响应用途：直接遍历 items；paging.next_cursor 用于下一页 max_id。
- 后续接口：threads.replies.v1.RepliesService/ListProfileReplies
- 所属工作流：无
- 说明：返回结构化 items/paging/metadata，不暴露原始 JSON 字符串。

#### 请求 `threads.replies.v1.ListProfileRepliesRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `user_id` | `string` | 1 | 单值 | 目标对象：来自当前账号 pk、搜索结果或资料接口。 | - |
| `max_id` | `string` | 2 | 可选 | 上一页响应：使用 ListPage.paging.next_cursor；首次调用省略。 | 分页 |
| `account_state` | `threads.auth.v1.AccountState` | 3 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 响应 `threads.common.v1.ListPageResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `page` | `ListPage` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

#### Python 返回 `threads.common.v1.ListPage`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `items` | `google.protobuf.Struct` | 1 | 数组 | - |
| `paging` | `Paging` | 2 | 单值 | - |
| `metadata` | `google.protobuf.Struct` | 3 | 单值 | 除列表和分页字段外的上游顶层字段，供兼容未知增量字段。 |

#### Python 调用签名

```python
async def list_profile_replies(user_id: str, max_id: str | None=None, *, timeout: float | None=None) -> common_pb2.ListPage
```

### 关键词搜索

- RPC：`threads.search.v1.SearchService/KeywordSearch`
- gRPC 方法：`/threads.search.v1.SearchService/KeywordSearch`
- Python 门面：`SearchClient.keyword_search`
- 所属模块：搜索与主题校验（`search`）
- 何时调用：按关键词查找 Threads 账号或内容时。
- 前置条件：AccountClient 已绑定完整 AccountState。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`read`
- 状态：`available`
- 幂等：是
- 副作用：无
- 业务成功：ListPage.items 与请求 query 相关。
- 响应用途：直接遍历 items；metadata 保留未知字段；分页参数必须来自同一轮搜索响应和会话。
- 后续接口：threads.search.v1.SearchService/KeywordSearch
- 所属工作流：无
- 说明：返回结构化 items/paging/metadata，不暴露原始 JSON 字符串。

#### 请求 `threads.search.v1.KeywordSearchRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `query` | `string` | 1 | 单值 | 用户输入：搜索关键词。 | - |
| `page_token` | `string` | 2 | 可选 | 上一页 ListPage.paging.next_cursor；首次调用省略。 | - |
| `rank_token` | `string` | 3 | 可选 | 首次响应或搜索会话上下文：后续页保持同一值。 | - |
| `search_session_id` | `string` | 4 | 可选 | 运行时生成：同一轮搜索分页复用同一个会话 ID。 | - |
| `account_state` | `threads.auth.v1.AccountState` | 5 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 响应 `threads.common.v1.ListPageResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `page` | `ListPage` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

#### Python 返回 `threads.common.v1.ListPage`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `items` | `google.protobuf.Struct` | 1 | 数组 | - |
| `paging` | `Paging` | 2 | 单值 | - |
| `metadata` | `google.protobuf.Struct` | 3 | 单值 | 除列表和分页字段外的上游顶层字段，供兼容未知增量字段。 |

#### Python 调用签名

```python
async def keyword_search(query: str, page_token: str | None=None, rank_token: str | None=None, search_session_id: str | None=None, *, timeout: float | None=None) -> common_pb2.ListPage
```

### 校验发帖主题

- RPC：`threads.search.v1.SearchService/ValidateTag`
- gRPC 方法：`/threads.search.v1.SearchService/ValidateTag`
- Python 门面：`SearchClient.validate_tag`
- 所属模块：搜索与主题校验（`search`）
- 何时调用：发帖前需要确认主题标签是否有效或敏感时。
- 前置条件：AccountClient 已绑定完整 AccountState。
- Metadata：账号认证与可选代理均来自请求内 `account_state`；可选服务访问密钥由 `ThreadsClient(api_key=...)` 注入
- 类型：`read`
- 状态：`available`
- 幂等：是
- 副作用：无
- 业务成功：返回 is_valid/is_sensitive 对应请求 tag_name。
- 响应用途：is_valid=true 且业务允许时，才构造发帖 tag_header；is_sensitive 需触发产品确认。
- 后续接口：threads.posts.v1.PostsService/CreateTextPost；threads.posts.v1.PostsService/CreateImagePost
- 所属工作流：无
- 说明：发帖主题的前置校验。

#### 请求 `threads.search.v1.ValidateTagRequest`

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `tag_name` | `string` | 1 | 单值 | 用户输入：不含 # 的主题名称。 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 响应 `threads.search.v1.ValidateTagResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `result` | `TagValidation` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

#### Python 返回 `threads.search.v1.TagValidation`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `is_valid` | `bool` | 1 | 单值 | - |
| `is_sensitive` | `bool` | 2 | 单值 | - |

#### Python 调用签名

```python
async def validate_tag(tag_name: str, *, timeout: float | None=None) -> search_pb2.TagValidation
```

## gRPC Status Model

- `UNAUTHENTICATED`：`account_state.session.token` 缺失、无效，上游判定会话失效
  （`login_required`、风控注销 `logout_reason=33`、密码错误），或未分类 HTTP 401；须重新登录。
- `PERMISSION_DENIED`：账号已停用/封禁或未分类 HTTP 403；重登无效，须人工处理。
- `FAILED_PRECONDITION`：需完成 challenge/checkpoint/2FA，或登录缺少硬件 attestation signer。
- `RESOURCE_EXHAUSTED`：上游限流、`feedback_required` 限制或未分类 HTTP 429；退避后重试。
- `DEADLINE_EXCEEDED`：上游超时、未分类 HTTP 408/504 或调用超过 deadline。
- `ABORTED`：未分类 HTTP 409；重新读取状态后再决定是否重试。
- `UNIMPLEMENTED`：未分类 HTTP 501；停止重试并检查服务端/上游能力。
- `UNAVAILABLE`：上游连接失败或未分类 HTTP 5xx（501/504 除外）。
- `INVALID_ARGUMENT`：必填字段为空或参数非法。
- `INTERNAL`：未分类 HTTP 400/404、其他未映射失败或内部转换失败。
- `OK`：只代表 RPC 成功；仍须按每个 RPC 的业务成功条件核对目标实体和副作用。
- 失败响应的 trailing metadata `x-threads-account-state-bin` 携带刷新后的
  `AccountState`（protobuf 二进制）；上游失败同样会轮换 RUR、claim 和 MID。
- `details()` 只含结构化摘要，不含上游原始响应正文。

## Proto Sources

### `proto/threads/auth.proto`

```proto
syntax = "proto3";

package threads.auth.v1;

option go_package = "github.com/robin528919/go-threads-api/gen/go/auth/v1;authv1";

// Auth 模块：支持完整账号状态自检，也暴露受安全门禁保护的 CAA/Bloks 登录链路。
service AuthService {
  // CAA/Bloks + 可选 TOTP 登录并换取 IGT:2。
  // 默认要求服务端硬件 attestation signer；显式允许无证明登录时仍不能视为稳定会话。
  rpc Login(LoginRequest) returns (LoginResponse);

  // GET accounts/current_user/ —— 置信度：已确认（edit=true 场景抓包命中）。
  // 使用完整账号状态拉当前账号；edit=true 为编辑资料页读取场景，响应字段更全。
  rpc GetCurrentUser(GetCurrentUserRequest) returns (GetCurrentUserResponse);
}

message LoginRequest {
  string username = 1;
  string password = 2;
  optional string two_factor_seed = 3;
  reserved 4, 5, 6;
  reserved "device", "app_version", "allow_unattested_login";
  optional Proxy proxy = 7;
}

// Go server 在首次登录时生成、并通过 AccountState 返回的完整持久设备。
// 后续功能 RPC 必须复用该设备，禁止重新生成或跨账号共享。
message LoginDevice {
  string android_id = 1;
  string device_uuid = 2;
  string family_device_id = 3;
  string manufacturer = 4;
  string brand = 5;
  string model = 6;
  string codename = 7;
  string cpu = 8;
  string dpi = 9;
  string resolution = 10;
  int32 android_api = 11;
  string android_rel = 12;
  string locale = 13;
  int32 timezone_offset = 14;
  string connection_type = 15;
  bool is_foldable = 16;
  optional string machine_id = 17;
}

message LoginSession {
  string token = 1;
  optional string www_claim = 2;
  optional string rur = 3;
  optional string ds_user_id = 4;
  optional string mid = 5;
}

enum ProxyProtocol {
  PROXY_PROTOCOL_UNSPECIFIED = 0;
  PROXY_PROTOCOL_HTTP = 1;
  PROXY_PROTOCOL_HTTPS = 2;
  PROXY_PROTOCOL_SOCKS5 = 3;
  PROXY_PROTOCOL_SOCKS5H = 4;
}

// 账号绑定的强类型代理。username/password 必须同时提供或同时省略。
message Proxy {
  ProxyProtocol protocol = 1;
  string host = 2;
  uint32 port = 3;
  optional string username = 4;
  optional string password = 5;
}

// SDK 使用者持久化并在独立功能任务之间显式传递的完整账号运行状态。
// Go server 不保存该对象；每次需要身份的功能 RPC 都由调用方重新传入。
message AccountState {
  string username = 1;
  string uid = 2;
  LoginDevice device = 3;
  LoginSession session = 4;
  reserved 5;
  reserved "proxy_url";
  optional string app_version = 6; // 当前只支持 421；空值等同 421。
  // 可选账号代理。认证信息须成对提供，支持 HTTP(S)/SOCKS5/SOCKS5H。
  optional Proxy proxy = 7;
}

message LoginStep {
  string name = 1;
  int32 http_status = 2; // 0 表示本地步骤。
  string detail = 3;
  optional string error = 4;
  optional string failure_kind = 5;
  optional string failure_summary = 6;
  optional int32 logout_reason = 7;
}

enum LoginAssurance {
  LOGIN_ASSURANCE_UNSPECIFIED = 0;
  LOGIN_ASSURANCE_UNATTESTED_SOFTWARE = 1;
  LOGIN_ASSURANCE_EXTERNAL_SIGNER_SUCCEEDED = 2;
  LOGIN_ASSURANCE_EXTERNAL_SIGNER_FAILED = 3;
}

message LoginResponse {
  // success 只有在取到 IGT:2 且用同一代理、设备完成 whoami 身份校验后才为 true。
  bool success = 1;
  reserved 2, 4;
  reserved "session", "device";
  repeated LoginStep steps = 3;
  optional CurrentUser verified_user = 5;
  LoginAssurance assurance = 6;

  // 当前实现永不把即时登录成功等价为长期稳定；必须由真实硬件证明和存活观测另行确认。
  bool stable_for_automation = 7;
  string stability_warning = 8;

  // 登录成功后的完整状态。使用者负责保存，并在发帖等后续独立任务中重新传入。
  optional AccountState account_state = 9;
}

message GetCurrentUserRequest {
  // edit=true：编辑资料页的读取场景（字段更全）。
  optional bool edit = 1;
  AccountState account_state = 2;
}

message GetCurrentUserResponse {
  CurrentUser user = 1;
  AccountState account_state = 2;
}

// 简介外链。Threads 的链接实际落在 bio_links[]，external_url 回显为空。
message BioLink {
  string link_id = 1;
  string url = 2;
  optional string lynx_url = 3;   // 平台跳转包装
  optional string title = 4;
}

message CurrentUser {
  string pk = 1;
  string username = 2;
  optional string full_name = 3;
  optional string biography = 4;
  optional string profile_pic_url = 5;
  optional string email = 6;
  optional string text_app_biography = 7;
  optional string external_url = 8;
  repeated BioLink bio_links = 9;
  optional string text_app_cover_photo_url = 10;
  optional bool is_private = 11;
  optional bool is_verified = 12;
  reserved 13;
  reserved "account_state";
}
```
### `proto/threads/common.proto`

```proto
syntax = "proto3";

package threads.common.v1;

import "google/protobuf/struct.proto";
import "threads/auth.proto";

option go_package = "github.com/robin528919/go-threads-api/gen/go/common/v1;commonv1";

// 分页游标：Threads 私有 API 多用 max_id / paging_token 翻页。
// 读方法统一收敛到此结构，避免各模块各写一套。
message Cursor {
  // 单页条数（客户端建议值；服务端可能有上限）。
  optional int32 limit = 1;
  // 向后翻页游标（对应 Threads 的 max_id / paging_token，随端点而定）。
  optional string after = 2;
}

// 分页返回信息。
message Paging {
  // 下一页游标；为空表示到底。
  optional string next_cursor = 1;
  // 是否还有更多。
  optional bool has_more = 2;
}

// ListPage 把尚在逐字段建模中的列表响应转换成结构化 protobuf。
// 调用方无需解析 JSON；已确认的分页语义固定在 paging，列表项保留字段名和值类型。
message ListPage {
  repeated google.protobuf.Struct items = 1;
  Paging paging = 2;
  // 除列表和分页字段外的上游顶层字段，供兼容未知增量字段。
  google.protobuf.Struct metadata = 3;
  reserved 4;
  reserved "account_state";
}

// 账号绑定列表 RPC 的内部响应信封；Python 门面只向用户返回 page。
message ListPageResponse {
  ListPage page = 1;
  threads.auth.v1.AccountState account_state = 2;
}
```
### `proto/threads/feed.proto`

```proto
syntax = "proto3";

package threads.feed.v1;

import "threads/common.proto";
import "threads/auth.proto";

option go_package = "github.com/robin528919/go-threads-api/gen/go/feed/v1;feedv1";

// Feed 模块：时间线 / 推荐流。
// 报告对应域：feed/text_post_app_timeline/、text_feed/recommended_users/。
service FeedService {
  // GET text_feed/recommended_users/ —— 置信度：路径已确认，响应 users[]/paging_token/has_more。
  // users[] 转为结构化 ListPage，调用方无需解析原始 JSON。
  rpc ListRecommendedUsers(ListRecommendedUsersRequest) returns (threads.common.v1.ListPageResponse);
}

message ListRecommendedUsersRequest {
  // 分页游标（对应 paging_token）。
  optional string paging_token = 1;
  // 推荐类型（recommended_users / great_accounts / ...，见报告 §5.1）。
  optional string recommendation_type = 2;
  threads.auth.v1.AccountState account_state = 3;
}
```
### `proto/threads/friendships.proto`

```proto
syntax = "proto3";

package threads.friendships.v1;

import "threads/auth.proto";

option go_package = "github.com/robin528919/go-threads-api/gen/go/friendships/v1;friendshipsv1";

// Friendships 模块：关系 / 关注域。
// 报告对应域：friendships/show|following|show_many|create_many|mute_*、text_feed/follow_many/。
service FriendshipsService {
  // GET friendships/show/{user_id}/ —— 路径置信度：已确认（抓包命中）。
  // 纯读、不依赖 signed_body 签名。响应字段来自 JADX parser（C31991Ov），见报告 §6.2。
  rpc GetFriendshipStatus(GetFriendshipStatusRequest) returns (GetFriendshipStatusResponse);

  // POST friendships/create/{user_id}/ —— 置信度：已确认（抓包 item173）。
  rpc FollowUser(FollowUserRequest) returns (FollowUserResponse);

  // POST friendships/destroy/{user_id}/ —— 置信度：已确认（抓包 item188）。
  rpc UnfollowUser(UnfollowUserRequest) returns (UnfollowUserResponse);
}

// 关注/取关共用入参形状；两者的线格式差异（destroy 不发 delivery_class 与
// device_id）由 Go SDK 处理，调用方无需关心。
message FollowUserRequest {
  // 目标用户数字 id。
  string user_id = 1;
  // 埋点归因；缺省 container_module=ig_text_feed_profile。
  optional string container_module = 2;
  optional string nav_chain = 3;
  // 非空表示这次关注来自某个帖子，会同时写入 media_id 与 media_id_attribution；
  // 直接按 user_id 关注时留空。
  optional string attribution_media_id = 4;
  // 该帖的排序归因 token，留空则不发送。
  optional string ranking_info_token = 5;
  threads.auth.v1.AccountState account_state = 6;
}

message UnfollowUserRequest {
  string user_id = 1;
  optional string container_module = 2;
  optional string nav_chain = 3;
  optional string attribution_media_id = 4;
  optional string ranking_info_token = 5;
  threads.auth.v1.AccountState account_state = 6;
}

message FollowResult {
  // 操作后的关系状态；判定成功以 status.following 为准。
  FriendshipStatus status = 1;
  // 操作前是否已关注。create 返回布尔值，destroy 真机返回 null（此处不设值）。
  optional bool previous_following = 2;
  // 上游业务级错误文案；两份真机样本均为 null。
  // TODO(抓包核对): 置信度=推测; 非空取值形态未观察到。
  optional string error = 3;
}

message FollowUserResponse {
  FollowResult result = 1;
  threads.auth.v1.AccountState account_state = 2;
}

message UnfollowUserResponse {
  FollowResult result = 1;
  threads.auth.v1.AccountState account_state = 2;
}

message GetFriendshipStatusRequest {
  // 目标用户数字 id。
  string user_id = 1;
  // 对应请求参数 is_external_deeplink_profile_view（默认 false）。
  optional bool is_external_deeplink_profile_view = 2;
  threads.auth.v1.AccountState account_state = 3;
}

// 关系状态读模型。同一个 friendship_status 对象由 friendships/show、create 与
// destroy 共用，各端点只返回其中一个子集。
//
// 字段 1-12、14、20-23 已由真机三份样本确认（新增关注取关.xml item183/173/188）。
message FriendshipStatus {
  optional bool following = 1;
  optional bool followed_by = 2;
  optional bool outgoing_request = 3;
  optional bool incoming_request = 4;
  optional bool blocking = 5;
  optional bool is_blocking_reel = 6;
  optional bool muting = 7;
  optional bool is_muting_reel = 8;
  optional bool is_muting_notes = 9;
  optional bool is_muting_media_notes = 10;
  optional bool is_muting_media_reposts = 11;
  optional bool is_private = 12;
  optional bool is_eligible_to_subscribe = 14;
  // 以下字段真机响应未出现，仅 JADX 静态可见。
  // TODO(抓包核对): 置信度=高概率; 来源=JADX parser C31991Ov; 三份真机样本均未返回
  optional bool subscribed = 13;
  optional bool is_viewer_unconnected = 15;
  optional bool should_show_profile_upsell = 16;
  optional bool is_banner_profile_upsell = 17;
  optional int64 reachability_status = 18;   // int 枚举（apis/friendships/关注关系.md）
  reserved 19;
  reserved "account_state";
  // 真机 show 与 create/destroy 均返回，此前模型缺失。
  optional bool is_bestie = 20;
  optional bool is_restricted = 21;
  optional bool is_feed_favorite = 22;
  optional bool text_post_app_pre_following = 23;
}

message GetFriendshipStatusResponse {
  FriendshipStatus result = 1;
  threads.auth.v1.AccountState account_state = 2;
}
```
### `proto/threads/posts.proto`

```proto
syntax = "proto3";

package threads.posts.v1;

import "threads/auth.proto";

option go_package = "github.com/robin528919/go-threads-api/gen/go/posts/v1;postsv1";

// Posts 模块：发帖 / 媒体上传 / 发帖前风控。
// 报告对应域：rupload_igphoto/、media/configure_text_*、warning/check_offensive_multi_text/。
service PostsService {
  // POST warning/check_offensive_multi_text/ —— 置信度：高概率（抓包亦见）。
  // 发帖前敏感/冒犯文本检测；响应字段见报告 §3。
  rpc CheckOffensiveText(CheckOffensiveTextRequest) returns (CheckOffensiveTextResponse);

  // POST media/configure_text_only_post/ —— 置信度：已确认（完整请求/响应，post-text-api-421.md）。
  rpc CreateTextPost(CreateTextPostRequest) returns (CreateTextPostResponse);

  // POST /rupload_igphoto/{name} —— 置信度：已确认（apis/post-media/上传图片.md）。
  // 原始图片二进制上传；返回的 upload_id 供 CreateImagePost 使用。
  rpc UploadImage(UploadImageRequest) returns (UploadImageResponse);

  // POST media/configure_text_post_app_feed/ —— 置信度：已确认（apis/post-media/发帖.md）。
  // 只负责关联已上传图片并创建单图帖；图片必须先经 UploadImage 上传。
  rpc CreateImagePost(CreateImagePostRequest) returns (CreateImagePostResponse);

  // POST media/{media_id}/delete/?media_type=TEXT_POST —— 置信度：已确认（apis/post-media/删除.md）。
  rpc DeletePost(DeletePostRequest) returns (DeletePostResponse);

  // POST media/{media_id}/like/ —— 置信度：已确认（抓包 item140，响应仅 status）。
  rpc LikeMedia(LikeMediaRequest) returns (LikeMediaResponse);

  // POST media/{media_id}/unlike/ —— 置信度：推测。
  // TODO(抓包核对): 与 like 对称推导，本轮抓包只覆盖点赞，取消点赞尚未真机核对。
  rpc UnlikeMedia(UnlikeMediaRequest) returns (UnlikeMediaResponse);
}

message DeletePostRequest {
  string media_id = 1;   // = URL 中的 media_id（pk_uid）
  reserved 2, 3;
  reserved "uid", "uuid";
  threads.auth.v1.AccountState account_state = 4;
}

message DeletePostResult {
  bool did_delete = 1;
  optional string cxp_deep_deletion_waterfall_id = 2;
  reserved 3;
  reserved "account_state";
}

message DeletePostResponse {
  DeletePostResult result = 1;
  threads.auth.v1.AccountState account_state = 2;
}

message CheckOffensiveTextRequest {
  // 待检测文本（对应 form text_list，JSON 数组）。
  repeated string text_list = 1;
  // 可选关联 media_id。
  optional string media_id = 2;
  threads.auth.v1.AccountState account_state = 3;
}

message OffensiveCheck {
  bool is_offensive = 1;
  optional int64 minimum_next_timestamp = 2;
  optional string text_language = 3;
  reserved 4;
  reserved "account_state";
}

message CheckOffensiveTextResponse {
  OffensiveCheck result = 1;
  threads.auth.v1.AccountState account_state = 2;
}

message CreateTextPostRequest {
  // 正文（对应 caption）。
  string caption = 1;
  reserved 2, 3, 4, 7;
  reserved "uid", "device_id", "uuid", "device";
  // 幂等批次 id（缺省由 SDK 生成 upload_id、publish_id 固定 "1"）。
  optional string upload_id = 5;
  // 回复权限：0=everyone（对应 text_post_app_info.reply_control）。
  optional int32 reply_control = 6;
  // 埋点/会话（必填，缺省由 SDK 生成/留空）。
  optional string camera_session_id = 8;
  optional string nav_chain = 9;
  optional string timezone_offset = 10;
  // 可选功能：主题 / 位置 / 投票（勾选才传）。
  optional string tag_header = 11;   // 主题 display_text
  optional Location location = 12;
  optional Poll poll = 13;

  // 登录返回或由使用者存储恢复的完整状态。
  threads.auth.v1.AccountState account_state = 14;

  // 非空表示这是对某帖的回复（评论），值为被回复帖的 pk（纯数字，不带 _uid 后缀）。
  // 回复与新建帖是同一端点的两种形态，仅 text_post_app_info 内的入口字段不同。
  optional string reply_id = 15;
  // 时间线/回复流响应里该帖的排序归因 token；回复时真机会带上，置空则不发送。
  optional string ranking_info_token = 16;
}

message LikeMediaRequest {
  // URL 与请求体共用的复合 id：{pk}_{author_uid}。
  string media_id = 1;
  // 埋点归因；缺省 container_module=ig_text_feed_timeline，其余置空则不发送。
  optional string container_module = 2;
  optional string nav_chain = 3;
  optional int32 feed_position = 4;
  // 时间线响应里该帖的 logging token。
  optional string logging_info_token = 5;
  threads.auth.v1.AccountState account_state = 6;
}

message LikeMediaResponse {
  threads.auth.v1.AccountState account_state = 1;
}

message UnlikeMediaRequest {
  string media_id = 1;
  optional string container_module = 2;
  optional string nav_chain = 3;
  optional int32 feed_position = 4;
  optional string logging_info_token = 5;
  threads.auth.v1.AccountState account_state = 6;
}

message UnlikeMediaResponse {
  threads.auth.v1.AccountState account_state = 1;
}

message UploadImageRequest {
  // 原始图片字节；当前已确认样本为 image/webp。
  bytes image_data = 1;
  int32 original_width = 2;
  int32 original_height = 3;
  // 缺省由 SDK 生成；后续 CreateImagePost 必须使用响应中的 upload_id。
  optional string upload_id = 4;
  // 缺省 image/webp；当前仅确认 image/webp。
  optional string mime_type = 5;
  // App 抓包存在乐观预上传与正式上传两种模式；缺省为正式上传。
  optional bool is_optimistic_upload = 6;
  // 图片压缩质量指标；调用方掌握真实编码结果时再传。
  optional double msssim = 7;
  optional double ssim = 8;
  // 缺省由 SDK 生成。
  optional string waterfall_id = 9;
  threads.auth.v1.AccountState account_state = 10;
}

message UploadImageResult {
  string upload_id = 1;
  string status = 2;
  reserved 3;
  reserved "account_state";
}

message UploadImageResponse {
  UploadImageResult result = 1;
  threads.auth.v1.AccountState account_state = 2;
}

message CreateImagePostRequest {
  // 图片帖允许空正文。
  string caption = 1;
  reserved 2, 3, 4, 7;
  reserved "uid", "device_id", "uuid", "device";
  // 必须使用 UploadImage 返回的 upload_id。
  string upload_id = 5;
  optional int32 reply_control = 6;
  optional string camera_session_id = 8;
  optional string nav_chain = 9;
  optional string timezone_offset = 10;
  optional string tag_header = 11;
  optional Location location = 12;
  optional Poll poll = 13;
  int32 original_width = 14;
  int32 original_height = 15;
  optional string custom_accessibility_caption = 16;
  threads.auth.v1.AccountState account_state = 17;
}

message Location {
  string name = 1;
  string external_source = 2;   // meta_place
  string meta_place_id = 3;
}

message Poll {
  string question = 1;
  repeated string options = 2;
}

// Media 发帖响应读模型。字段照搬 post-text-api-421.md §3（已确认抓包）。
message Media {
  string id = 1;
  int64 pk = 2;
  int64 fbid = 3;
  int64 taken_at = 4;
  int32 media_type = 5;             // 19 = text_post
  string code = 6;                  // 短代码
  string product_type = 7;          // text_post
  string permalink = 8;
  string integrity_review_decision = 9;  // pending 表示异步审核中
  Caption caption = 10;
  TextPostAppInfo text_post_app_info = 11;
  bool has_liked = 12;
  int64 like_count = 13;
  optional MetaPlace meta_place = 14;   // 位置回显（带位置发帖时）
  optional ImageVersions2 image_versions2 = 15;
  int32 original_width = 16;
  int32 original_height = 17;
  optional MediaUser user = 18;         // 列表端点的顶层作者

  reserved 19;
  reserved "account_state";
}

message CreateTextPostResponse {
  Media media = 1;
  threads.auth.v1.AccountState account_state = 2;
}

message CreateImagePostResponse {
  Media media = 1;
  threads.auth.v1.AccountState account_state = 2;
}

message ImageVersions2 {
  repeated ImageCandidate candidates = 1;
}

message ImageCandidate {
  string url = 1;
  int32 width = 2;
  int32 height = 3;
}

message MediaUser {
  string pk = 1;
  string username = 2;
  string full_name = 3;
  optional string profile_pic_url = 4;
  bool is_private = 5;
  bool is_verified = 6;
}

message MetaPlace {
  int64 id = 1;
  string name = 2;
}

message Caption {
  string pk = 1;
  string text = 2;
  int64 user_id = 3;
  string status = 4;
  CaptionUser user = 5;
}

message CaptionUser {
  string username = 1;
  string full_name = 2;
  bool is_private = 3;
  bool is_verified = 4;
  bool has_onboarded_to_text_post_app = 5;
}

message TextPostAppInfo {
  string id = 1;
  bool is_reply = 2;
  string reply_control = 3;         // everyone / ...
  int32 reply_level = 4;
  int32 self_thread_count = 5;
}
```
### `proto/threads/profile.proto`

```proto
syntax = "proto3";

package threads.profile.v1;

import "threads/auth.proto";
import "threads/posts.proto";

option go_package = "github.com/robin528919/go-threads-api/gen/go/profile/v1;profilev1";

// Profile 模块：用户资料相关端点。
// 报告对应域：users/{uid}/info/、text_feed/{uid}/profile/*、accounts/edit_profile、
//            GQL xdt_text_app_edit_display_name / bio_link。
service ProfileService {
  // GET users/{user_id}/info/  —— 置信度：已确认（抓包命中，Friendly-Name: user_info_by_id）
  // 纯读、不依赖 signed_body 签名，作为首条打通链路的端点。
  rpc GetUserInfo(GetUserInfoRequest) returns (GetUserInfoResponse);

  // GET text_feed/{user_id}/profile/ —— 置信度：已确认（路径）。主页文字贴流。
  // 返回抓包已确认的 threads[].thread_items[].post 强类型结构，同时保留 raw_json 兼容旧客户端。
  rpc ListProfileThreads(ListProfileThreadsRequest) returns (ListProfileThreadsResponse);

  // POST accounts/edit_profile/ —— 置信度：已确认（见 apis/accounts/编辑主页.md）。
  // body 为纯 form（不走 signed_body）；整表回传，未带字段会被清空 ——
  // 调用方应先 AuthService.GetCurrentUser(edit=true) 读当前值。
  // 响应复用 auth 的 CurrentUser（服务端回显同一 user 结构）。
  rpc EditProfile(EditProfileRequest) returns (EditProfileResponse);
}

message EditProfileRequest {
  string username = 1;              // 必填
  string first_name = 2;            // 必填，显示名（= full_name）
  string biography = 3;             // 必填，个性签名（无签名传空串）
  bool is_private = 4;              // 必填
  reserved 5;
  reserved "uuid";
  optional string external_url = 6; // 可选，链接 URL；服务端写入 bio_links[]
  optional string url_title = 7;    // 可选，链接标题，配合 external_url
  threads.auth.v1.AccountState account_state = 8;
}

message GetUserInfoRequest {
  // Threads/IG 用户数字 id（pk）。
  string user_id = 1;
  threads.auth.v1.AccountState account_state = 2;
}

message ListProfileThreadsRequest {
  string user_id = 1;
  optional string max_id = 2;   // 分页
  optional bool exclude_reposts = 3;
  threads.auth.v1.AccountState account_state = 4;
}

// ProfileThreadsPage 与旧 RawList 保持字段 1/2 的线级兼容；新调用方直接读取 threads。
message ProfileThreadsPage {
  string raw_json = 1 [deprecated = true];
  optional string next_cursor = 2;
  repeated ProfileThread threads = 3;
  string status = 4;
  reserved 5;
  reserved "account_state";
}

message ListProfileThreadsResponse {
  ProfileThreadsPage page = 1;
  threads.auth.v1.AccountState account_state = 2;
}

message ProfileThread {
  string id = 1;
  string thread_type = 2;
  bool show_create_reply_cta = 3;
  repeated ProfileThreadItem items = 4;
}

message ProfileThreadItem {
  threads.posts.v1.Media post = 1;
  string thread_item_type = 2;
  string line_type = 3;
  bool is_contextual = 4;
  bool can_inline_expand_below = 5;
}

// 用户资料读模型。
// 注意：字段集为标准 IG user_info 常见字段，报告未逐字段枚举响应体，
// 故字段名/可选性需抓包核对 —— 见 docs/api/verification-status.md。
// TODO(抓包核对): 置信度=高概率; 来源=IG user_info 通用字段; 未逐字段抓包确认
message User {
  string pk = 1;              // 用户数字 id
  string username = 2;
  optional string full_name = 3;
  optional string biography = 4;
  optional string profile_pic_url = 5;
  optional int64 follower_count = 6;
  optional int64 following_count = 7;
  optional int64 media_count = 8;
  optional bool is_private = 9;
  optional bool is_verified = 10;
  reserved 11;
  reserved "account_state";
}

message GetUserInfoResponse {
  User user = 1;
  threads.auth.v1.AccountState account_state = 2;
}

message EditProfileResponse {
  threads.auth.v1.CurrentUser user = 1;
  threads.auth.v1.AccountState account_state = 2;
}
```
### `proto/threads/replies.proto`

```proto
syntax = "proto3";

package threads.replies.v1;

import "threads/common.proto";
import "threads/auth.proto";

option go_package = "github.com/robin528919/go-threads-api/gen/go/replies/v1;repliesv1";

// Replies 模块：回复 / 对话。
// 报告对应域：text_feed/{uid}/profile/replies/、GQL xdt_text_app_approve_reply/ignore_reply。
service RepliesService {
  // GET text_feed/{user_id}/profile/replies/ —— 置信度：已确认（路径）。
  // 响应为结构化分页列表，调用方无需解析原始 JSON。
  rpc ListProfileReplies(ListProfileRepliesRequest) returns (threads.common.v1.ListPageResponse);
}

message ListProfileRepliesRequest {
  string user_id = 1;
  optional string max_id = 2;   // 分页
  threads.auth.v1.AccountState account_state = 3;
}
```
### `proto/threads/search.proto`

```proto
syntax = "proto3";

package threads.search.v1;

import "threads/common.proto";
import "threads/auth.proto";

option go_package = "github.com/robin528919/go-threads-api/gen/go/search/v1;searchv1";

// Search 模块：关键词 / 话题搜索。
// 报告对应域：fbsearch/text_app/keyword/search/、text_feed/tags_search/。
service SearchService {
  // GET fbsearch/text_app/keyword/search/ —— 置信度：高概率（参数在调用方，未文档化）。
  // TODO(抓包核对): 请求参数与逐字段业务模型尚未完全确认。
  rpc KeywordSearch(KeywordSearchRequest) returns (threads.common.v1.ListPageResponse);

  // GET text_feed/tags_validate/ —— 置信度：已确认（发帖选主题的前置校验）。
  rpc ValidateTag(ValidateTagRequest) returns (ValidateTagResponse);
}

message ValidateTagRequest {
  string tag_name = 1;
  threads.auth.v1.AccountState account_state = 2;
}

message TagValidation {
  bool is_valid = 1;
  bool is_sensitive = 2;
  reserved 3;
  reserved "account_state";
}

message ValidateTagResponse {
  TagValidation result = 1;
  threads.auth.v1.AccountState account_state = 2;
}

message KeywordSearchRequest {
  string query = 1;
  optional string page_token = 2;
  optional string rank_token = 3;
  optional string search_session_id = 4;
  threads.auth.v1.AccountState account_state = 5;
}
```


## Embedded Python Full Reference

本文件由 `scripts/llms_docs.py` 根据 Proto、`python/threads_sdk` 公共异步方法和
`docs/llms-metadata.json` 生成。禁止手工修改。

AI 在生成调用代码前必须先选择工作流，再按每一步的参数来源取值。不得只根据方法签名猜测
`uid`、`uuid`、`upload_id`、分页游标或设备字段。

## Install

每个 `test-vX.Y.Z` / `vX.Y.Z` 都同时发布 Go server 与 `X.Y.Z` Python SDK。
从对应版本的公开文档下载页获取以下任一产物：

- 生产 Latest：`https://threads-api.es007.com/latest/docs/sdk-download/`
- 测试 Latest：`https://threads-api.es007.com/test/latest/docs/sdk-download/`

- `threads_sdk-X.Y.Z-py3-none-any.whl`：推荐使用 `uv add ./threads_sdk-X.Y.Z-py3-none-any.whl`。
- `threads_sdk-X.Y.Z.tar.gz`：源码发行包。
- `threads_sdk-X.Y.Z-copy.zip`：解压后把完整 `threads_sdk/` 目录复制进调用方项目。

复制包内部已经包含当前 Proto 生成的 protobuf/gRPC stub。运行环境只需安装 `grpcio` 和
`protobuf`，不需要 `grpcio-tools` 或仓库源码。仓库开发环境才使用：

```bash
uv sync --project python
```

运行时要求 Python 3.10+。SDK 与 Go server 必须来自同一 `X.Y.Z`；升级时二者一起升级，
禁止把测试 SDK 连接到不同版本的生产 server。

## Client

```python
import asyncio
import os
from pathlib import Path

import grpc
from threads_sdk import ThreadsClient, dump_account_state, load_account_state


async def main() -> None:
    state = load_account_state(Path("account-state.bin").read_bytes())
    async with ThreadsClient(
        target=os.getenv("THREADS_GRPC_TARGET", "127.0.0.1:50051"),
        tls=os.getenv("THREADS_GRPC_TLS") == "1",
        root_certificates=(
            Path(os.environ["THREADS_GRPC_CA_FILE"]).read_bytes()
            if os.getenv("THREADS_GRPC_CA_FILE")
            else None
        ),
        default_timeout=60,
    ) as client:
        account = client.account(state)
        current_user = await account.auth.get_current_user(
            edit=False,
            timeout=15,
        )
        print(current_user.pk, current_user.username)
        Path("account-state.bin").write_bytes(dump_account_state(account.state))


asyncio.run(main())
```

客户端启动时只需要通过 `target` 设置 Go gRPC server 地址；默认是
`127.0.0.1:50051`，也可以使用 `THREADS_GRPC_TARGET` 注入。
`ThreadsClient` 不持有账号信息；登录后或从存储恢复 `AccountState`，再用
`client.account(state)` 创建账号客户端。账号认证、设备、可选 `Proxy` 和 App 版本都封装在完整状态中，
每次成功调用后通过 `account.state` 取得刷新结果。跨主机设置 `tls=True`，私有 CA 通过
`root_certificates` 提供。字符串代理配置必须先由 `Proxy.from_url()` 转成强类型对象；账号密码成对可选，
省略时适用于 IP 白名单代理。
`ThreadsClient(default_timeout=...)` 设置全部 RPC 的默认 deadline，默认 60 秒；每个公开异步方法的
`timeout=` 可以逐调用覆盖。`default_timeout=None` 表示不设置 SDK 默认 deadline。
`dump_account_state()` 返回未加密二进制，包含 Session token、设备身份和可能存在的代理凭据；调用方必须
在自己的存储层加密并限制访问。
公开 `AccountState`、`LoginSession` 和 `LoginResult` 是隐藏内部 protobuf 的安全值对象，其文本表示不会
输出 Session、设备标识或代理凭据；持久化只使用 `dump_account_state()`。
它们是只读的：字段只有 getter，`CopyFrom`、`HasField` 和字段赋值等 protobuf 写入方法不再提供，
需要改变状态时重新构造对象。`AccountState.SerializeToString()` 作为只读兼容保留，等价于
`dump_account_state()`。
Token、代理认证信息、密码和 2FA seed 必须来自受保护配置，禁止写入源码、日志、
异常、测试快照或提交记录。

## Async And Error Model

- 所有业务网络方法都是 `async def`，必须使用 `await`。
- 捕获 `grpc.aio.AioRpcError`，检查 `code()`、`details()` 和 metadata。
- 按 `code()` 分流：`UNAUTHENTICATED` 重新登录，`PERMISSION_DENIED` 与
  `FAILED_PRECONDITION` 停止重试，`RESOURCE_EXHAUSTED` 退避，
  `DEADLINE_EXCEEDED` / `UNAVAILABLE` 有限重试，`ABORTED` 先重新读取状态，
  `UNIMPLEMENTED` 停止重试，`INVALID_ARGUMENT` 修正参数。
- RPC deadline 到期会产生 `DEADLINE_EXCEEDED`；ARQ 任务超时、取消、重试和幂等由调用方管理。
- 只读且幂等的 RPC 仅可对明确的临时错误做有限退避重试。
- 创建、更新、删除接口不得盲目重试；超时后先查询真实副作用。
- 失败同样会轮换 Session 路由态；SDK 已从失败响应中采纳刷新后的状态，
  因此异常处理里也必须持久化 `account.state`，不能沿用调用前的旧状态。
- `details()` 只含结构化摘要，不含上游原始响应正文；完整正文只在服务端日志。
- `OK`、HTTP 2xx、对象非空或单个字段存在都不能单独证明业务成功。

```python
try:
    user = await account.profile.get_user_info("17841400000000000")
except grpc.aio.AioRpcError as exc:
    if exc.code() is grpc.StatusCode.UNAUTHENTICATED:
        save_state(dump_account_state(account.state))  # 失败也要保存刷新后的状态
        raise NeedsRelogin from exc
    print(exc.code().name, exc.details())
```

## Task Workflows

<a id="workflow-verify_session"></a>

### 校验已保存的完整账号状态 `verify_session`

- 状态：`available`
- 目标：确认 AccountState 对应的真实账号，并取得后续调用使用的资料字段。
- 前置条件：已从使用者存储恢复完整 AccountState。；每个账号状态携带自己的可选 Proxy；不要让多账号共享可变状态对象。

| 步骤 | 调用接口 | 目的 | 输入与参数来源 | 响应与下一步 |
| ---: | --- | --- | --- | --- |
| 1 | `AccountAuthClient.get_current_user`<br>`threads.auth.v1.AuthService/GetCurrentUser` | 调用 get_current_user(edit=False) 校验会话。 | 先通过 client.account(state) 创建账号客户端，再传 edit=False。 | 核对 pk、username；任务结束后保存 account.state。 |

<a id="workflow-login"></a>

### 账密登录并取得 IGT:2 `login`

- 状态：`experimental`
- 目标：通过 CAA/Bloks 登录生成会话并即时核对账号身份。
- 前置条件：准备账号、密码和可选 TOTP seed；Proxy 可省略。；生产容器已经固定加载仓库版本化 keybox.xml；SDK 用户不传 keybox。；登录成功后由使用者持久化返回的 AccountState。

| 步骤 | 调用接口 | 目的 | 输入与参数来源 | 响应与下一步 |
| ---: | --- | --- | --- | --- |
| 1 | `AuthClient.login`<br>`threads.auth.v1.AuthService/Login` | 提交账号、密码、可选 2FA seed 和可选账号代理；Go server 生成首次登录设备。 | 代理可省略；传递 Proxy(host, port, protocol, username, password)。username/password 成对可选，省略时用于 IP 白名单代理；协议支持 HTTP、HTTPS、SOCKS5、SOCKS5H。配置字符串只能先通过 Proxy.from_url() 转成强类型对象。不要把密码、2FA seed 或代理凭据写入日志。 | 核对 success、account_state、verified_user.username 和 assurance；把 account_state 保存到使用者自己的存储。 |

#### 步骤间参数传递

- `LoginResponse.account_state` → `ThreadsClient.account(state)`：后续独立任务从使用者存储加载完整状态；SDK 不负责持久化。

#### 当前缺失能力

- 软件环境尚不能证明 session 可长期稳定；stable_for_automation 必须保持 false，直到硬件证明和存活观测通过。

<a id="workflow-edit_profile"></a>

### 安全编辑当前账号资料 `edit_profile`

- 状态：`available`
- 目标：修改指定资料字段，同时避免整表回传接口清空未携带的原值。
- 前置条件：已从使用者存储恢复完整 AccountState。

| 步骤 | 调用接口 | 目的 | 输入与参数来源 | 响应与下一步 |
| ---: | --- | --- | --- | --- |
| 1 | `AccountAuthClient.get_current_user`<br>`threads.auth.v1.AuthService/GetCurrentUser` | 先调用 get_current_user(edit=True) 读取完整当前资料。 | edit=True。 | 保存 username、full_name、biography、is_private、external_url 和 bio_links。 |
| 2 | `ProfileClient.edit_profile`<br>`threads.profile.v1.ProfileService/EditProfile` | 只替换目标字段，其余字段使用上一步原值并整表提交。 | username/first_name/biography/is_private 必须完整回传；设备身份由 AccountState 内部提供。 | 核对返回资料，再调用 get_current_user(edit=True) 验证保存结果。 |

#### 步骤间参数传递

- `CurrentUser.username/full_name/biography/is_private/external_url` → `EditProfileRequest 对应字段`：未修改字段必须回填原值，不能省略。

#### 当前缺失能力

- 头像和封面需要独立上传端点，当前契约未实现。
- location 不是 EditProfile 端点字段。

<a id="workflow-create_text_post"></a>

### 发布文本帖 `create_text_post`

- 状态：`available`
- 目标：使用当前账号和持久设备身份创建纯文本帖子。
- 前置条件：已从登录响应或使用者存储取得完整 AccountState。；写操作已获得明确授权。

| 步骤 | 调用接口 | 目的 | 输入与参数来源 | 响应与下一步 |
| ---: | --- | --- | --- | --- |
| 1 | `PostsClient.create_text_post`<br>`threads.posts.v1.PostsService/CreateTextPost` | 用 ThreadsClient.account(state).posts 提交 caption 和可选发帖功能字段。 | AccountState 自动提供 uid、完整设备、Session、代理与 App 版本；纯文本帖不需要先上传媒体。 | 核对 Media 的作者、caption、code 和 permalink，并把 account.state 的刷新结果写回使用者存储。 |
| 2 | `ProfileClient.list_profile_threads`<br>`threads.profile.v1.ProfileService/ListProfileThreads` | 重新读取账号主页确认帖子真实出现。 | user_id 使用 AccountState.uid。 | 在 threads[].items[].post 中找到新帖子。 |

#### 步骤间参数传递

- `LoginResponse.account_state 或使用者存储` → `CreateTextPostRequest.account_state`：每个独立任务显式传入完整状态；响应状态覆盖保存后供下一任务使用。

<a id="workflow-create_image_post"></a>

### 发布单图帖 `create_image_post`

- 状态：`available`
- 目标：先上传 WebP 图片，再用上传结果创建单图帖子。
- 前置条件：已从登录响应或使用者存储取得完整 AccountState。；准备 WebP 图片字节及真实宽高。；写操作已获得明确授权。

| 步骤 | 调用接口 | 目的 | 输入与参数来源 | 响应与下一步 |
| ---: | --- | --- | --- | --- |
| 1 | `PostsClient.upload_image`<br>`threads.posts.v1.PostsService/UploadImage` | 上传图片二进制及真实宽高。 | image_data 为 WebP bytes；original_width/original_height 来自图片元数据。 | 要求 status=ok，并保存响应 upload_id。 |
| 2 | `PostsClient.create_image_post`<br>`threads.posts.v1.PostsService/CreateImagePost` | 使用上传响应创建单图帖。 | upload_id 必须使用 UploadImageResult.upload_id；宽高与上传步骤保持一致。 | 核对 media_type=1、caption、image_versions2 和帖子作者。 |
| 3 | `ProfileClient.list_profile_threads`<br>`threads.profile.v1.ProfileService/ListProfileThreads` | 重新读取账号主页确认图片帖真实出现。 | user_id 使用发帖 uid。 | 在 threads[].items[].post 中找到新帖子和图片字段。 |

#### 步骤间参数传递

- `UploadImageResult.upload_id` → `CreateImagePostRequest.upload_id`：图片上传返回值必须原样传给创建接口。
- `UploadImageRequest.original_width/original_height` → `CreateImagePostRequest.original_width/original_height`：创建接口使用与上传图片一致的真实尺寸。

<a id="workflow-create_video_post"></a>

### 发布视频帖 `create_video_post`

- 状态：`unavailable`
- 目标：上传视频并创建视频帖子。
- 前置条件：无

当前没有可执行调用步骤。

#### 当前缺失能力

- 当前 Proto、Go SDK 和 Python 门面均没有视频上传 RPC。
- 当前没有视频 configure RPC、视频转码状态查询或封面上传工作流。
- 在完成真实抓包、契约和黑盒测试前，禁止复用 UploadImage/CreateImagePost 伪装视频发布。

<a id="workflow-collect_profile_threads"></a>

### 分页采集主页帖子 `collect_profile_threads`

- 状态：`available`
- 目标：读取指定账号主页帖子，并使用游标持续翻页。
- 前置条件：已从使用者存储恢复完整 AccountState。；准备目标 user_id。

| 步骤 | 调用接口 | 目的 | 输入与参数来源 | 响应与下一步 |
| ---: | --- | --- | --- | --- |
| 1 | `ProfileClient.list_profile_threads`<br>`threads.profile.v1.ProfileService/ListProfileThreads` | 首次调用不传 max_id，读取 threads 强类型结果。 | user_id 为目标账号；exclude_reposts 按采集需求设置。 | 消费 threads[].items[].post，并读取 next_cursor。 |
| 2 | `ProfileClient.list_profile_threads`<br>`threads.profile.v1.ProfileService/ListProfileThreads` | next_cursor 非空时继续请求下一页。 | 将上一页 next_cursor 传入下一次 max_id。 | next_cursor 为空时结束；raw_json 只用于未建模字段排错。 |

#### 步骤间参数传递

- `ProfileThreadsPage.next_cursor` → `ListProfileThreadsRequest.max_id`：分页游标来自上一次真实响应。

## Module And Method Reference

## 登录与当前账号 — `client.auth（仅登录）/ account.auth（登录后）`

登录、校验会话并读取当前账号身份。

### 账号登录 — `AuthClient.login`

```python
async def login(*, username: str, password: str, two_factor_seed: str | None=None, proxy: Proxy | None=None, timeout: float | None=None) -> LoginResult
```

- RPC：`threads.auth.v1.AuthService/Login`
- 状态：`experimental`
- 何时调用：没有可用 IGT:2，并且需要通过账密建立新会话时。
- 前置条件：生产容器已固定加载 keybox signer；账号代理可选。
- 响应用途：保存 account_state，核对 verified_user.username 和 assurance；后续任务用 ThreadsClient.account(state) 恢复，stable_for_automation 不能由即时成功推断。
- 业务成功：success=true、account_state.session.token 为 IGT:2，且 verified_user.username 与请求账号一致；stable_for_automation 仍必须为 false，直到硬件证明和长期存活观测另行通过。
- 后续接口：threads.posts.v1.PostsService/CreateTextPost
- 所属工作流：login

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `username` | `string` | 1 | 单值 | 用户输入：Threads/Instagram 登录账号。 | - |
| `password` | `string` | 2 | 单值 | 受保护账号配置：禁止写入源码和日志。 | - |
| `two_factor_seed` | `string` | 3 | 可选 | 受保护账号配置：启用 TOTP 时提供。 | - |
| `proxy` | `Proxy` | 7 | 可选 | 可选账号出口：由 Proxy 强类型对象提供；认证账号密码必须同时提供或同时省略。 | - |

#### 返回 `threads.auth.v1.LoginResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `success` | `bool` | 1 | 单值 | success 只有在取到 IGT:2 且用同一代理、设备完成 whoami 身份校验后才为 true。 |
| `steps` | `LoginStep` | 3 | 数组 | - |
| `verified_user` | `CurrentUser` | 5 | 可选 | - |
| `assurance` | `LoginAssurance` | 6 | 单值 | - |
| `stable_for_automation` | `bool` | 7 | 单值 | 当前实现永不把即时登录成功等价为长期稳定；必须由真实硬件证明和存活观测另行确认。 |
| `stability_warning` | `string` | 8 | 单值 | - |
| `account_state` | `AccountState` | 9 | 可选 | 登录成功后的完整状态。使用者负责保存，并在发帖等后续独立任务中重新传入。 |

### 读取当前账号 — `AccountAuthClient.get_current_user`

```python
async def get_current_user(edit: bool | None=None, *, timeout: float | None=None) -> auth_pb2.CurrentUser
```

- RPC：`threads.auth.v1.AuthService/GetCurrentUser`
- 状态：`available`
- 何时调用：校验 AccountState 会话、取得当前 uid，或在资料编辑前读取完整原值时。
- 前置条件：已通过 client.account(state) 恢复完整账号状态。
- 响应用途：pk 是发帖 uid 和当前账号 user_id；edit=true 时使用完整资料字段回填 EditProfile。
- 业务成功：响应 pk 与 AccountState.uid/session 所属账号一致，且 AccountClient.state 已由内部响应信封刷新。
- 后续接口：threads.profile.v1.ProfileService/EditProfile；threads.posts.v1.PostsService/CreateTextPost；threads.posts.v1.PostsService/CreateImagePost
- 所属工作流：verify_session；login；edit_profile；create_text_post；create_image_post

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `edit` | `bool` | 1 | 可选 | 调用场景：普通身份校验传 false；资料编辑前传 true。 | edit=true：编辑资料页的读取场景（字段更全）。 |
| `account_state` | `AccountState` | 2 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 返回 `threads.auth.v1.GetCurrentUserResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `user` | `CurrentUser` | 1 | 单值 | - |
| `account_state` | `AccountState` | 2 | 单值 | - |
## 资料与主页帖子 — `account.profile`

读取用户资料、保存当前账号资料并分页采集主页帖子。

### 读取指定用户资料 — `ProfileClient.get_user_info`

```python
async def get_user_info(user_id: str, *, timeout: float | None=None) -> 'profile_pb2.User'
```

- RPC：`threads.profile.v1.ProfileService/GetUserInfo`
- 状态：`available`
- 何时调用：已知用户 ID，需要读取公开资料和账号统计时。
- 前置条件：AccountClient 已绑定完整 AccountState。
- 响应用途：核对 pk 等于请求 user_id，再使用 username、计数和隐私状态。
- 业务成功：响应 pk 与请求 user_id 一致。
- 后续接口：threads.profile.v1.ProfileService/ListProfileThreads；threads.friendships.v1.FriendshipsService/GetFriendshipStatus
- 所属工作流：无

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `user_id` | `string` | 1 | 单值 | 目标对象：来自搜索结果、帖子作者 pk 或业务数据库。 | Threads/IG 用户数字 id（pk）。 |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 返回 `threads.profile.v1.GetUserInfoResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `user` | `User` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

### 分页读取主页帖子 — `ProfileClient.list_profile_threads`

```python
async def list_profile_threads(user_id: str, max_id: str | None=None, exclude_reposts: bool | None=None, *, timeout: float | None=None) -> 'profile_pb2.ProfileThreadsPage'
```

- RPC：`threads.profile.v1.ProfileService/ListProfileThreads`
- 状态：`available`
- 何时调用：采集指定账号主页帖子、验证发帖或验证删除结果时。
- 前置条件：AccountClient 已绑定完整 AccountState，并已准备目标 user_id。
- 响应用途：优先消费 threads[].items[].post；next_cursor 传给下一页 max_id；raw_json 仅用于兼容排错。
- 业务成功：status=ok，threads[].items[].post 可直接读取，帖子作者与请求 user_id 的业务语义一致。
- 后续接口：threads.profile.v1.ProfileService/ListProfileThreads
- 所属工作流：create_text_post；create_image_post；collect_profile_threads

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `user_id` | `string` | 1 | 单值 | 目标对象：当前账号用 GetCurrentUser.pk，其他账号来自搜索或资料接口。 | - |
| `max_id` | `string` | 2 | 可选 | 上一页响应：使用 ProfileThreadsPage.next_cursor；首次调用省略。 | 分页 |
| `exclude_reposts` | `bool` | 3 | 可选 | 采集策略：是否排除转发，可省略使用上游默认。 | - |
| `account_state` | `threads.auth.v1.AccountState` | 4 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 返回 `threads.profile.v1.ListProfileThreadsResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `page` | `ProfileThreadsPage` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

### 保存当前账号资料 — `ProfileClient.edit_profile`

```python
async def edit_profile(username: str, first_name: str, biography: str, is_private: bool, external_url: str | None=None, url_title: str | None=None, *, timeout: float | None=None) -> auth_pb2.CurrentUser
```

- RPC：`threads.profile.v1.ProfileService/EditProfile`
- 状态：`available`
- 何时调用：修改用户名、显示名、简介、隐私状态或外部链接时。
- 前置条件：先调用 GetCurrentUser(edit=true) 读取完整原值。
- 响应用途：核对返回资料，并再次 GetCurrentUser(edit=true) 验证真实保存结果。
- 业务成功：保存后重新读取当前账号，资料字段与请求值一致。
- 后续接口：threads.auth.v1.AuthService/GetCurrentUser
- 所属工作流：edit_profile

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `username` | `string` | 1 | 单值 | 前置响应或用户修改：未修改时回填 CurrentUser.username。 | 必填 |
| `first_name` | `string` | 2 | 单值 | 前置响应或用户修改：未修改时回填 CurrentUser.full_name。 | 必填，显示名（= full_name） |
| `biography` | `string` | 3 | 单值 | 前置响应或用户修改：未修改时回填 CurrentUser.biography。 | 必填，个性签名（无签名传空串） |
| `is_private` | `bool` | 4 | 单值 | 前置响应或用户修改：未修改时回填 CurrentUser.is_private。 | 必填 |
| `external_url` | `string` | 6 | 可选 | 前置响应或用户修改：未修改时回填 CurrentUser.external_url。 | 可选，链接 URL；服务端写入 bio_links[] |
| `url_title` | `string` | 7 | 可选 | 前置响应或用户修改：从 CurrentUser.bio_links 对应链接标题回填。 | 可选，链接标题，配合 external_url |
| `account_state` | `threads.auth.v1.AccountState` | 8 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 返回 `threads.profile.v1.EditProfileResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `user` | `threads.auth.v1.CurrentUser` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |
## 帖子发布与删除 — `account.posts`

检查文本、发布文本帖、上传图片、发布单图帖和删除帖子。

### 检查冒犯文本 — `PostsClient.check_offensive_text`

```python
async def check_offensive_text(text_list: Sequence[str], media_id: str | None=None, *, timeout: float | None=None) -> posts_pb2.OffensiveCheck
```

- RPC：`threads.posts.v1.PostsService/CheckOffensiveText`
- 状态：`known-upstream-error`
- 何时调用：发帖前希望执行上游文本风险检查时；当前真实上游返回 404。
- 前置条件：AccountClient 已绑定完整 AccountState。
- 响应用途：当前以 gRPC 错误路径处理，不能把未返回结果视为安全。
- 业务成功：当前真实上游返回 HTTP 404，测试以真实错误映射为验收结果。
- 后续接口：无
- 所属工作流：无

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `text_list` | `string` | 1 | 数组 | 用户内容：待检查的一段或多段文本。 | 待检测文本（对应 form text_list，JSON 数组）。 |
| `media_id` | `string` | 2 | 可选 | 已有媒体上下文：编辑或关联媒体时提供，否则省略。 | 可选关联 media_id。 |
| `account_state` | `threads.auth.v1.AccountState` | 3 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 返回 `threads.posts.v1.CheckOffensiveTextResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `result` | `OffensiveCheck` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

### 创建文本帖 — `PostsClient.create_text_post`

```python
async def create_text_post(caption: str, upload_id: str | None=None, reply_control: int=0, camera_session_id: str | None=None, nav_chain: str | None=None, timezone_offset: str | None=None, tag_header: str | None=None, location: posts_pb2.Location | None=None, poll: posts_pb2.Poll | None=None, reply_id: str | None=None, ranking_info_token: str | None=None, *, timeout: float | None=None) -> posts_pb2.Media
```

- RPC：`threads.posts.v1.PostsService/CreateTextPost`
- 状态：`available`
- 何时调用：发布不带图片或视频的 Threads 文本帖子时；传 reply_id 时同一接口用于发布回复（评论）。
- 前置条件：完整 AccountState、写操作授权。
- 响应用途：保存 id/code/permalink，核对 caption、user 和主页结果；SDK 从内部响应信封刷新状态，调用方将 AccountClient.state 覆盖写回自己的存储。
- 业务成功：返回帖子属于 account_state.uid，caption 与请求一致，并能从账号主页重新读取；AccountClient.state 已刷新。
- 后续接口：threads.profile.v1.ProfileService/ListProfileThreads
- 所属工作流：create_text_post

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `caption` | `string` | 1 | 单值 | 用户输入：帖子正文。 | 正文（对应 caption）。 |
| `upload_id` | `string` | 5 | 可选 | 运行时生成：可省略，由 Go SDK 生成；重试时不得随意复用。 | 幂等批次 id（缺省由 SDK 生成 upload_id、publish_id 固定 "1"）。 |
| `reply_control` | `int32` | 6 | 可选 | 产品设置：回复权限枚举，默认 0。 | 回复权限：0=everyone（对应 text_post_app_info.reply_control）。 |
| `camera_session_id` | `string` | 8 | 可选 | 运行时生成：模拟发布会话时提供，否则省略。 | 埋点/会话（必填，缺省由 SDK 生成/留空）。 |
| `nav_chain` | `string` | 9 | 可选 | 运行时上下文：导航链，普通调用可省略。 | - |
| `timezone_offset` | `string` | 10 | 可选 | 运行环境：账号所在地时区偏移。 | - |
| `tag_header` | `string` | 11 | 可选 | 主题功能：先用 ValidateTag 校验后按上游格式提供。 | 可选功能：主题 / 位置 / 投票（勾选才传）。 主题 display_text |
| `location` | `Location` | 12 | 可选 | 用户选择：posts_pb2.Location，未选择位置时省略。 | - |
| `poll` | `Poll` | 13 | 可选 | 用户输入：posts_pb2.Poll，未创建投票时省略。 | - |
| `account_state` | `threads.auth.v1.AccountState` | 14 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态，包含 uid、设备、Session、可选代理和 App 版本。 | 登录返回或由使用者存储恢复的完整状态。 |
| `reply_id` | `string` | 15 | 可选 | 可选参数：非空即表示发布回复（评论），取被回复帖的纯数字 pk（Media.pk 或 post.pk，不是 {pk}_{author_uid} 复合 id）；留空为新建独立帖。 | 非空表示这是对某帖的回复（评论），值为被回复帖的 pk（纯数字，不带 _uid 后缀）。 回复与新建帖是同一端点的两种形态，仅 text_post_app_info 内的入口字段不同。 |
| `ranking_info_token` | `string` | 16 | 可选 | 前置响应：时间线/回复流响应里该帖的排序归因 token；留空则不发送该字段。 | 时间线/回复流响应里该帖的排序归因 token；回复时真机会带上，置空则不发送。 |

#### 返回 `threads.posts.v1.CreateTextPostResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `media` | `Media` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

### 上传图片 — `PostsClient.upload_image`

```python
async def upload_image(image_data: bytes, original_width: int, original_height: int, upload_id: str | None=None, mime_type: str | None=None, is_optimistic_upload: bool | None=None, msssim: float | None=None, ssim: float | None=None, waterfall_id: str | None=None, *, timeout: float | None=None) -> 'posts_pb2.UploadImageResult'
```

- RPC：`threads.posts.v1.PostsService/UploadImage`
- 状态：`available`
- 何时调用：创建单图帖前上传 WebP 图片二进制时。
- 前置条件：完整 AccountState、WebP 图片字节、真实宽高、写操作授权。
- 响应用途：status 必须为 ok；将 upload_id 原样传给 CreateImagePost。
- 业务成功：响应 status=ok 且 upload_id 非空；该 upload_id 可被 CreateImagePost 接受。
- 后续接口：threads.posts.v1.PostsService/CreateImagePost
- 所属工作流：create_image_post

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `image_data` | `bytes` | 1 | 单值 | 本地文件或内存：WebP 原始 bytes。 | 原始图片字节；当前已确认样本为 image/webp。 |
| `original_width` | `int32` | 2 | 单值 | 图片元数据：解码后的真实像素宽度。 | - |
| `original_height` | `int32` | 3 | 单值 | 图片元数据：解码后的真实像素高度。 | - |
| `upload_id` | `string` | 4 | 可选 | 运行时生成：通常省略并由 Go SDK 生成。 | 缺省由 SDK 生成；后续 CreateImagePost 必须使用响应中的 upload_id。 |
| `mime_type` | `string` | 5 | 可选 | 固定协议值：当前确认 image/webp。 | 缺省 image/webp；当前仅确认 image/webp。 |
| `is_optimistic_upload` | `bool` | 6 | 可选 | 上传策略：通常省略使用 SDK 默认值。 | App 抓包存在乐观预上传与正式上传两种模式；缺省为正式上传。 |
| `msssim` | `double` | 7 | 可选 | 图片质量计算：调用方实际计算时提供，否则省略。 | 图片压缩质量指标；调用方掌握真实编码结果时再传。 |
| `ssim` | `double` | 8 | 可选 | 图片质量计算：调用方实际计算时提供，否则省略。 | - |
| `waterfall_id` | `string` | 9 | 可选 | 运行时生成：上传链路追踪 ID，通常省略。 | 缺省由 SDK 生成。 |
| `account_state` | `threads.auth.v1.AccountState` | 10 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 返回 `threads.posts.v1.UploadImageResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `result` | `UploadImageResult` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

### 创建单图帖 — `PostsClient.create_image_post`

```python
async def create_image_post(caption: str, upload_id: str, original_width: int, original_height: int, reply_control: int=0, camera_session_id: str | None=None, nav_chain: str | None=None, timezone_offset: str | None=None, tag_header: str | None=None, location: 'posts_pb2.Location | None'=None, poll: 'posts_pb2.Poll | None'=None, custom_accessibility_caption: str | None=None, *, timeout: float | None=None) -> 'posts_pb2.Media'
```

- RPC：`threads.posts.v1.PostsService/CreateImagePost`
- 状态：`available`
- 何时调用：UploadImage 成功后，把已上传图片发布为单图帖子时。
- 前置条件：UploadImageResult.status=ok、完整 AccountState、写操作授权。
- 响应用途：核对 media_type=1、image_versions2、caption 和 user，并保存 id/code/permalink。
- 业务成功：返回 media_type=1，正文与请求一致，image_versions2 非空，并能从账号主页重新读取。
- 后续接口：threads.profile.v1.ProfileService/ListProfileThreads
- 所属工作流：create_image_post

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `caption` | `string` | 1 | 单值 | 用户输入：帖子正文。 | 图片帖允许空正文。 |
| `upload_id` | `string` | 5 | 单值 | 前置响应：UploadImageResult.upload_id。 | 必须使用 UploadImage 返回的 upload_id。 |
| `reply_control` | `int32` | 6 | 可选 | 产品设置：回复权限枚举，默认 0。 | - |
| `camera_session_id` | `string` | 8 | 可选 | 运行时生成：模拟发布会话时提供，否则省略。 | - |
| `nav_chain` | `string` | 9 | 可选 | 运行时上下文：导航链，普通调用可省略。 | - |
| `timezone_offset` | `string` | 10 | 可选 | 运行环境：账号所在地时区偏移。 | - |
| `tag_header` | `string` | 11 | 可选 | 主题功能：先用 ValidateTag 校验后按上游格式提供。 | - |
| `location` | `Location` | 12 | 可选 | 用户选择：posts_pb2.Location，未选择位置时省略。 | - |
| `poll` | `Poll` | 13 | 可选 | 用户输入：posts_pb2.Poll，未创建投票时省略。 | - |
| `original_width` | `int32` | 14 | 单值 | 前置请求：与 UploadImage.original_width 保持一致。 | - |
| `original_height` | `int32` | 15 | 单值 | 前置请求：与 UploadImage.original_height 保持一致。 | - |
| `custom_accessibility_caption` | `string` | 16 | 可选 | 用户输入：图片无障碍说明，可省略。 | - |
| `account_state` | `threads.auth.v1.AccountState` | 17 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 返回 `threads.posts.v1.CreateImagePostResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `media` | `Media` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

### 删除帖子 — `PostsClient.delete_post`

```python
async def delete_post(media_id: str, *, timeout: float | None=None) -> posts_pb2.DeletePostResult
```

- RPC：`threads.posts.v1.PostsService/DeletePost`
- 状态：`available`
- 何时调用：删除当前账号已有帖子或清理测试帖子时。
- 前置条件：完整 AccountState、目标 media_id、写操作授权。
- 响应用途：did_delete 必须为 true，并通过 ListProfileThreads 确认目标帖子消失。
- 业务成功：did_delete=true，且重新读取主页确认目标帖子已经消失。
- 后续接口：threads.profile.v1.ProfileService/ListProfileThreads
- 所属工作流：无

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `media_id` | `string` | 1 | 单值 | 前置响应：创建接口返回的 Media.id，或主页帖子中的 post.id。 | = URL 中的 media_id（pk_uid） |
| `account_state` | `threads.auth.v1.AccountState` | 4 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 返回 `threads.posts.v1.DeletePostResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `result` | `DeletePostResult` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

### 点赞帖子 — `PostsClient.like_media`

```python
async def like_media(media_id: str, container_module: str | None=None, nav_chain: str | None=None, feed_position: int | None=None, logging_info_token: str | None=None, *, timeout: float | None=None) -> None
```

- RPC：`threads.posts.v1.PostsService/LikeMedia`
- 状态：`available`
- 何时调用：对指定帖子点赞时。
- 前置条件：完整 AccountState、目标帖子的复合 media_id、写操作授权。
- 响应用途：无业务返回值；成功判据是重新读取该帖，has_liked 为 true。
- 业务成功：调用成功后重新读取该帖，has_liked 必须为 true。
- 后续接口：threads.profile.v1.ProfileService/ListProfileThreads
- 所属工作流：无

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `media_id` | `string` | 1 | 单值 | 前置响应：创建接口返回的 Media.id，或主页帖子/时间线中的 post.id（形如 {pk}_{author_uid}）。 | URL 与请求体共用的复合 id：{pk}_{author_uid}。 |
| `container_module` | `string` | 2 | 可选 | 固定参数：缺省 ig_text_feed_timeline，从其他入口点赞时按实际来源传入。 | 埋点归因；缺省 container_module=ig_text_feed_timeline，其余置空则不发送。 |
| `nav_chain` | `string` | 3 | 可选 | 埋点参数：可留空；需要贴合真机时用读取该帖那次调用的导航链。 | - |
| `feed_position` | `int32` | 4 | 可选 | 埋点参数：该帖在列表中的下标，缺省 0。 | - |
| `logging_info_token` | `string` | 5 | 可选 | 前置响应：时间线响应里该帖的 logging token；留空则不发送。 | 时间线响应里该帖的 logging token。 |
| `account_state` | `threads.auth.v1.AccountState` | 6 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 返回 `threads.posts.v1.LikeMediaResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `account_state` | `threads.auth.v1.AccountState` | 1 | 单值 | - |

### 取消点赞 — `PostsClient.unlike_media`

```python
async def unlike_media(media_id: str, container_module: str | None=None, nav_chain: str | None=None, feed_position: int | None=None, logging_info_token: str | None=None, *, timeout: float | None=None) -> None
```

- RPC：`threads.posts.v1.PostsService/UnlikeMedia`
- 状态：`available`
- 何时调用：撤销此前对某帖的点赞时。
- 前置条件：完整 AccountState、目标帖子的复合 media_id、写操作授权。
- 响应用途：无业务返回值；成功判据是重新读取该帖，has_liked 回到 false。
- 业务成功：调用成功后重新读取该帖，has_liked 必须回到 false。
- 后续接口：threads.profile.v1.ProfileService/ListProfileThreads
- 所属工作流：无

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `media_id` | `string` | 1 | 单值 | 前置响应：与 LikeMedia 使用同一个复合 media_id。 | - |
| `container_module` | `string` | 2 | 可选 | 固定参数：缺省 ig_text_feed_timeline。 | - |
| `nav_chain` | `string` | 3 | 可选 | 埋点参数：可留空。 | - |
| `feed_position` | `int32` | 4 | 可选 | 埋点参数：缺省 0。 | - |
| `logging_info_token` | `string` | 5 | 可选 | 前置响应：时间线响应里该帖的 logging token；留空则不发送。 | - |
| `account_state` | `threads.auth.v1.AccountState` | 6 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 返回 `threads.posts.v1.UnlikeMediaResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `account_state` | `threads.auth.v1.AccountState` | 1 | 单值 | - |
## 回复采集 — `account.replies`

分页读取指定账号的回复列表。

### 分页读取账号回复 — `RepliesClient.list_profile_replies`

```python
async def list_profile_replies(user_id: str, max_id: str | None=None, *, timeout: float | None=None) -> common_pb2.ListPage
```

- RPC：`threads.replies.v1.RepliesService/ListProfileReplies`
- 状态：`available`
- 何时调用：采集指定账号发布的回复时。
- 前置条件：AccountClient 已绑定完整 AccountState，并已准备目标 user_id。
- 响应用途：直接遍历 items；paging.next_cursor 用于下一页 max_id。
- 业务成功：ListPage.items 属于请求 user_id 的回复列表。
- 后续接口：threads.replies.v1.RepliesService/ListProfileReplies
- 所属工作流：无

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `user_id` | `string` | 1 | 单值 | 目标对象：来自当前账号 pk、搜索结果或资料接口。 | - |
| `max_id` | `string` | 2 | 可选 | 上一页响应：使用 ListPage.paging.next_cursor；首次调用省略。 | 分页 |
| `account_state` | `threads.auth.v1.AccountState` | 3 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 返回 `threads.common.v1.ListPageResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `page` | `ListPage` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |
## 搜索与主题校验 — `account.search`

关键词搜索以及发帖主题标签校验。

### 关键词搜索 — `SearchClient.keyword_search`

```python
async def keyword_search(query: str, page_token: str | None=None, rank_token: str | None=None, search_session_id: str | None=None, *, timeout: float | None=None) -> common_pb2.ListPage
```

- RPC：`threads.search.v1.SearchService/KeywordSearch`
- 状态：`available`
- 何时调用：按关键词查找 Threads 账号或内容时。
- 前置条件：AccountClient 已绑定完整 AccountState。
- 响应用途：直接遍历 items；metadata 保留未知字段；分页参数必须来自同一轮搜索响应和会话。
- 业务成功：ListPage.items 与请求 query 相关。
- 后续接口：threads.search.v1.SearchService/KeywordSearch
- 所属工作流：无

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `query` | `string` | 1 | 单值 | 用户输入：搜索关键词。 | - |
| `page_token` | `string` | 2 | 可选 | 上一页 ListPage.paging.next_cursor；首次调用省略。 | - |
| `rank_token` | `string` | 3 | 可选 | 首次响应或搜索会话上下文：后续页保持同一值。 | - |
| `search_session_id` | `string` | 4 | 可选 | 运行时生成：同一轮搜索分页复用同一个会话 ID。 | - |
| `account_state` | `threads.auth.v1.AccountState` | 5 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 返回 `threads.common.v1.ListPageResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `page` | `ListPage` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

### 校验发帖主题 — `SearchClient.validate_tag`

```python
async def validate_tag(tag_name: str, *, timeout: float | None=None) -> search_pb2.TagValidation
```

- RPC：`threads.search.v1.SearchService/ValidateTag`
- 状态：`available`
- 何时调用：发帖前需要确认主题标签是否有效或敏感时。
- 前置条件：AccountClient 已绑定完整 AccountState。
- 响应用途：is_valid=true 且业务允许时，才构造发帖 tag_header；is_sensitive 需触发产品确认。
- 业务成功：返回 is_valid/is_sensitive 对应请求 tag_name。
- 后续接口：threads.posts.v1.PostsService/CreateTextPost；threads.posts.v1.PostsService/CreateImagePost
- 所属工作流：无

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `tag_name` | `string` | 1 | 单值 | 用户输入：不含 # 的主题名称。 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 返回 `threads.search.v1.ValidateTagResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `result` | `TagValidation` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |
## 推荐用户 — `account.feed`

分页读取 Threads 推荐账号。

### 读取推荐用户 — `FeedClient.list_recommended_users`

```python
async def list_recommended_users(paging_token: str | None=None, recommendation_type: str | None=None, *, timeout: float | None=None) -> common_pb2.ListPage
```

- RPC：`threads.feed.v1.FeedService/ListRecommendedUsers`
- 状态：`available`
- 何时调用：需要获取 Threads 推荐账号或继续推荐流分页时。
- 前置条件：AccountClient 已绑定完整 AccountState。
- 响应用途：直接遍历 items；paging.next_cursor 用于下一页 paging_token；metadata 保留未知顶层字段。
- 业务成功：ListPage.items 与请求推荐场景一致，paging 游标来自同一次真实响应。
- 后续接口：threads.feed.v1.FeedService/ListRecommendedUsers
- 所属工作流：无

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `paging_token` | `string` | 1 | 可选 | 上一页响应：使用 ListPage.paging.next_cursor；首次调用省略。 | 分页游标（对应 paging_token）。 |
| `recommendation_type` | `string` | 2 | 可选 | 产品场景配置：按上游支持的推荐类型填写，未知时省略。 | 推荐类型（recommended_users / great_accounts / ...，见报告 §5.1）。 |
| `account_state` | `threads.auth.v1.AccountState` | 3 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 返回 `threads.common.v1.ListPageResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `page` | `ListPage` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |
## 关系状态 — `account.friendships`

读取与目标账号之间的关注、拉黑和静音状态。

### 读取关系状态 — `FriendshipsClient.get_friendship_status`

```python
async def get_friendship_status(user_id: str, is_external_deeplink_profile_view: bool=False, *, timeout: float | None=None) -> 'friendships_pb2.FriendshipStatus'
```

- RPC：`threads.friendships.v1.FriendshipsService/GetFriendshipStatus`
- 状态：`available`
- 何时调用：需要确认当前账号是否关注、被关注、拉黑或静音目标账号时。
- 前置条件：AccountClient 已绑定完整 AccountState。
- 响应用途：直接读取 following、followed_by、blocking、muting 等强类型字段。
- 业务成功：返回关系状态属于请求 user_id 对应的目标账号。
- 后续接口：无
- 所属工作流：无

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `user_id` | `string` | 1 | 单值 | 目标对象：来自 GetUserInfo.pk、搜索结果或业务数据库。 | 目标用户数字 id。 |
| `is_external_deeplink_profile_view` | `bool` | 2 | 可选 | 调用场景：普通 API 调用使用 false。 | 对应请求参数 is_external_deeplink_profile_view（默认 false）。 |
| `account_state` | `threads.auth.v1.AccountState` | 3 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 返回 `threads.friendships.v1.GetFriendshipStatusResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `result` | `FriendshipStatus` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

### 关注用户 — `FriendshipsClient.follow_user`

```python
async def follow_user(user_id: str, container_module: str | None=None, nav_chain: str | None=None, attribution_media_id: str | None=None, ranking_info_token: str | None=None, *, timeout: float | None=None) -> 'friendships_pb2.FollowResult'
```

- RPC：`threads.friendships.v1.FriendshipsService/FollowUser`
- 状态：`available`
- 何时调用：关注目标账号时。
- 前置条件：完整 AccountState、目标 user_id、写操作授权。
- 响应用途：以 result.status.following 判定结果；previous_following 区分「本次新增关注」与「此前已关注」。
- 业务成功：result.status.following=true，并用 GetFriendshipStatus 独立回读确认关注生效。
- 后续接口：threads.friendships.v1.FriendshipsService/GetFriendshipStatus
- 所属工作流：无

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `user_id` | `string` | 1 | 单值 | 用户输入或前置响应：目标用户数字 id，可来自 GetUserInfo、推荐流或搜索结果。 | 目标用户数字 id。 |
| `container_module` | `string` | 2 | 可选 | 固定参数：缺省 ig_text_feed_profile；从帖子详情页操作时传 ig_text_post_permalink。 | 埋点归因；缺省 container_module=ig_text_feed_profile。 |
| `nav_chain` | `string` | 3 | 可选 | 埋点参数：可留空；需要贴合真机时用进入该用户页那次调用的导航链。 | - |
| `attribution_media_id` | `string` | 4 | 可选 | 前置响应：从某个帖子发起关注时传该帖复合 media_id（{pk}_{author_uid}）；直接按 user_id 操作时留空。 | 非空表示这次关注来自某个帖子，会同时写入 media_id 与 media_id_attribution； 直接按 user_id 关注时留空。 |
| `ranking_info_token` | `string` | 5 | 可选 | 前置响应：时间线/回复流里该帖的排序归因 token；留空则不发送。 | 该帖的排序归因 token，留空则不发送。 |
| `account_state` | `threads.auth.v1.AccountState` | 6 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 返回 `threads.friendships.v1.FollowUserResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `result` | `FollowResult` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |

### 取消关注 — `FriendshipsClient.unfollow_user`

```python
async def unfollow_user(user_id: str, container_module: str | None=None, nav_chain: str | None=None, attribution_media_id: str | None=None, ranking_info_token: str | None=None, *, timeout: float | None=None) -> 'friendships_pb2.FollowResult'
```

- RPC：`threads.friendships.v1.FriendshipsService/UnfollowUser`
- 状态：`available`
- 何时调用：取消对目标账号的关注时。
- 前置条件：完整 AccountState、目标 user_id、写操作授权。
- 响应用途：以 result.status.following=false 判定结果；该接口不返回 previous_following。
- 业务成功：result.status.following=false，并用 GetFriendshipStatus 独立回读确认已取关。
- 后续接口：threads.friendships.v1.FriendshipsService/GetFriendshipStatus
- 所属工作流：无

#### 参数与来源

| 字段 | 类型 | 字段号 | 规则 | 参数来源 | 说明 |
| --- | --- | ---: | --- | --- | --- |
| `user_id` | `string` | 1 | 单值 | 用户输入或前置响应：目标用户数字 id，可来自 GetUserInfo、推荐流或搜索结果。 | - |
| `container_module` | `string` | 2 | 可选 | 固定参数：缺省 ig_text_feed_profile；从帖子详情页操作时传 ig_text_post_permalink。 | - |
| `nav_chain` | `string` | 3 | 可选 | 埋点参数：可留空；需要贴合真机时用进入该用户页那次调用的导航链。 | - |
| `attribution_media_id` | `string` | 4 | 可选 | 前置响应：从某个帖子发起关注时传该帖复合 media_id（{pk}_{author_uid}）；直接按 user_id 操作时留空。 | - |
| `ranking_info_token` | `string` | 5 | 可选 | 前置响应：时间线/回复流里该帖的排序归因 token；留空则不发送。 | - |
| `account_state` | `threads.auth.v1.AccountState` | 6 | 单值 | SDK 自动注入：来自 AccountClient 当前完整状态。 | - |

#### 返回 `threads.friendships.v1.UnfollowUserResponse`

| 字段 | 类型 | 字段号 | 规则 | 说明 |
| --- | --- | ---: | --- | --- |
| `result` | `FollowResult` | 1 | 单值 | - |
| `account_state` | `threads.auth.v1.AccountState` | 2 | 单值 | - |
