如何用 TwexAPI 审计 X 账号状态
账号列表不会自己保持干净。创作者可能被封禁,合作账号可能失效,白名单里的用户可能改名或丢失认证信号。等到付款、投放或合作上线时才发现,通常已经晚了。
TwexAPI 适合把这类检查做成固定流程:先用 POST /twitter/users/status 批量确认账号是否存在、是否 suspended,再用 POST /x/account/verify 补充蓝标和机构认证信息。这样你拿到的不是一个模糊的“可信/不可信”分数,而是一组能放进审批和复核队列的状态证据。
审计目标
这类审计应该放在会产生资金、权限或品牌曝光的动作之前。目标是确认名单里的 X 账号是否仍然可用、是否 suspended 或未找到,以及认证信息是否符合你的业务规则。蓝标可以作为上下文,但不能替代账号状态证据。
常见目标有三类:
- 封禁或不可用账号:付款、合作或白名单放行前,先识别
suspended或未找到的账号。 - 认证信息变化:确认账号是否仍然是 Blue Verified,或是否属于官方机构认证关联账号。
- 复核证据留存:把原始响应、审计时间、审计原因和上一次状态一起保存,方便人工判断是新风险还是旧状态。
接口选择
把“账号是否可用”和“账号是否认证”分成两个问题处理。前者用状态端点,后者用认证端点,避免把认证标识误当作账号风险结论。
| 目的 | 端点 | 请求体 | 适合判断 |
|---|---|---|---|
| 批量账号状态 | POST /twitter/users/status | 用户名数组 | 账号是否 suspended、是否未找到 |
| 认证信息 | POST /x/account/verify | 用户 ID 或用户名数组 | is_blue_verified、is_verified_organization_affiliate、is_verified |
两个端点都使用 Bearer Token:
Authorization: Bearer <your_token>
Content-Type: application/json账号状态请求示例:
POST https://api.twexapi.io/twitter/users/status
Authorization: Bearer <your_token>
Content-Type: application/json
["elonmusk", "sundarpichai", "unknown_handle"]认证信息请求示例:
POST https://api.twexapi.io/x/account/verify
Authorization: Bearer <your_token>
Content-Type: application/json
["elonmusk", "sundarpichai"]Python 示例:批量审计循环
下面是一个保守的服务端审计脚本:先检查账号状态,再补充认证信息,最后输出一份可以写入数据库或人工复核队列的结果。示例对响应结构做了兼容处理,因为状态端点可能按数组或映射返回数据。
1import os
2from datetime import datetime, timezone
3
4import requests
5
6API_BASE = "https://api.twexapi.io"
7TOKEN = os.environ["TWEXAPI_BEARER_TOKEN"]
8
9def post_json(path, body):
10 response = requests.post(
11 API_BASE + path,
12 headers={
13 "Authorization": f"Bearer {TOKEN}",
14 "Content-Type": "application/json",
15 "Accept": "application/json",
16 },
17 json=body,
18 timeout=30,
19 )
20 response.raise_for_status()
21 payload = response.json()
22
23 if isinstance(payload, dict) and payload.get("code", 200) >= 400:
24 raise RuntimeError(payload.get("msg", "TwexAPI returned an error"))
25
26 return payload
27
28def normalize_status_payload(payload, usernames):
29 data = payload.get("data", payload) if isinstance(payload, dict) else payload
30
31 if isinstance(data, dict):
32 return {name.lower(): data.get(name) for name in usernames}
33
34 if isinstance(data, list):
35 return {
36 name.lower(): data[index] if index < len(data) else None
37 for index, name in enumerate(usernames)
38 }
39
40 return {name.lower(): None for name in usernames}
41
42def verification_by_username(payload):
43 rows = payload.get("data", []) if isinstance(payload, dict) else []
44 result = {}
45
46 for row in rows:
47 screen_name = (row.get("screen_name") or "").lower()
48 if screen_name:
49 result[screen_name] = row
50
51 return result
52
53def classify_account(status_value, verify_row):
54 status_text = "" if status_value is None else str(status_value).lower()
55
56 if status_value is None:
57 return "REVIEW_NOT_FOUND"
58
59 if "suspended" in status_text:
60 return "BLOCK_SUSPENDED"
61
62 if any(flag in status_text for flag in ("not_found", "not found", "missing", "unavailable")):
63 return "REVIEW_NOT_FOUND"
64
65 if verify_row.get("is_verified"):
66 return "PASS_STATUS_VERIFIED"
67
68 return "PASS_STANDARD"
69
70def audit_accounts(usernames, audit_reason):
71 status_payload = post_json("/twitter/users/status", usernames)
72 verify_payload = post_json("/x/account/verify", usernames)
73
74 statuses = normalize_status_payload(status_payload, usernames)
75 verifications = verification_by_username(verify_payload)
76 audited_at = datetime.now(timezone.utc).isoformat()
77
78 results = []
79 for username in usernames:
80 key = username.lower()
81 verify_row = verifications.get(key, {})
82 status_value = statuses.get(key)
83
84 results.append({
85 "username": username,
86 "status": status_value,
87 "is_blue_verified": verify_row.get("is_blue_verified"),
88 "is_org_affiliate": verify_row.get("is_verified_organization_affiliate"),
89 "is_verified": verify_row.get("is_verified"),
90 "decision": classify_account(status_value, verify_row),
91 "audit_reason": audit_reason,
92 "audited_at": audited_at,
93 })
94
95 return results
96
97accounts = ["elonmusk", "sundarpichai", "unknown_handle"]
98for row in audit_accounts(accounts, "partner_payout_precheck"):
99 print(row)生产环境里,把 TWEXAPI_BEARER_TOKEN 放进服务端环境变量,不要写进仓库。每次审计完成后,把规范化结果和原始响应都保存下来;人工复核需要原始证据,而看板和规则引擎更适合读规范化字段。
响应字段
保存字段时,目标是让复核人员之后能还原当时为什么放行、阻断或要求复核。/twitter/users/status 用于账号可用性;/x/account/verify 返回认证元数据。
认证响应的 data 数组中常见字段包括:
| 字段 | 含义 |
|---|---|
user_id | X 用户 ID |
screen_name | 用户名 |
is_blue_verified | 是否为 Blue Verified |
is_verified_organization_affiliate | 是否为官方机构认证关联账号 |
is_verified | 是否满足蓝标或机构认证任一条件 |
状态端点的关键点更简单:如果账号 suspended,应进入阻断或人工复核;如果返回 null 或无法匹配目标账号,应按“未找到/需复核”处理,而不是直接放行。
放入哪些流程
把检查放在会产生资金、权限或品牌风险的动作之前。最常见的是下面几类:
- 付款前检查:给创作者、KOL 或合作账号打款前,先确认账号没有 suspended 或失效。
- 白名单审批:社区、活动或产品内权限放行前,记录账号状态和认证证据。
- 合作方准入:代理商、达人库或 affiliate 名单入库前,保存状态快照。
- 品牌账号监控:高管、品牌和客服账号定期复查,一旦状态变化就通知安全或 PR 团队。
- Sybil 风险过滤:把 suspended、未找到、认证变化等信号交给更完整的风控模型,而不是只靠单个字段做结论。
风险分层
把 API 返回值转成业务动作时,不要让脚本直接执行不可逆操作。先生成明确的决策建议,再交给工作流执行。
| 信号 | 建议动作 |
|---|---|
suspended | 阻断付款或准入,并创建复核任务 |
null 或未找到 | 暂停自动放行,要求确认用户名是否变更 |
| 未认证但状态正常 | 可继续流程,但不要给高信任权限 |
| 认证状态变化 | 触发二次确认,特别是品牌、机构和高管账号 |
| 连续多次 API 错误 | 标记为系统问题,重试或延后审计,不要误判为账号风险 |
落地注意事项
审计结果要能复现、能解释、能恢复。建议保存原始状态响应、认证响应、用户名、账号 ID、审计时间、审计原因、名单版本和规则版本。
周期性审计时,要区分临时 API 错误和账号风险。网络超时应该重试;suspended、未找到或认证状态变化应该生成带证据的人工复核任务。对付款和合作场景,最好把“通过”“阻断”“需复核”三种状态分开,避免所有异常都变成同一种错误提示。
小结
把账号状态审计当成一条证据链,而不是一个蓝标判断:读取账号列表,调用 /twitter/users/status 检查可用性,用 /x/account/verify 补充认证信息,保存原始响应和规范化字段,再把可疑变化送进人工复核队列。这样付款、合作、社区白名单和品牌监控列表才不会在后台悄悄失效。