# go-threads-api Python Full AI 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 | 单值 | - |
