# X 数据

获取用户资料，搜索用户和帖子，分页读取动态与回复。

按需要的数据选择接口。五个接口均使用 POST，结果直接位于 JSON 根层级，没有统一的 `data` 外层。

示例使用[总览](https://openapi.felo.ai/zh-CN/docs/data-api/guides/felo-overview.md)中配置的 `FELO_API_KEY`。调用使用**平台余额**，不使用 Felo Pro 积分。响应示例仅展示结构，实际内容和可选字段会变化。

## 选择接口

| 需要什么 | 接口 |
|---|---|
| 已知用户名，查询资料 | `POST /api/v1/data/x/user/info` |
| 按关键词寻找用户 | `POST /api/v1/data/x/user/search` |
| 查询一个用户发布的帖子 | `POST /api/v1/data/x/user/tweets` |
| 按关键词搜索帖子 | `POST /api/v1/data/x/tweet/search` |
| 查询指定帖子的回复 | `POST /api/v1/data/x/tweet/replies` |

## 1. 查询用户资料

`POST /api/v1/data/x/user/info`

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `usernames` | string[] | 是 | 1–100 个用户名，建议不带 `@` |

```bash
curl -sS --request POST "https://openapi.felo.ai/api/v1/data/x/user/info" \
  -H "Authorization: Bearer $FELO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"usernames":["OpenAI"]}'
```

返回 `users` 和 `total`。每个用户包含 `user_id`、`username`、`display_name`，还可能有简介、粉丝数、认证状态等元数据。批量查询可能只返回部分用户，请按用户名或 ID 匹配，不要按数组下标对应。

```json
{"users":[{"user_id":"example-user-id","username":"example","display_name":"Example"}],"total":1}
```

## 2. 搜索用户

`POST /api/v1/data/x/user/search`

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `query` | string | 是 | 1–1,000 字符 |
| `cursor` | string | 否 | 分页游标，最长 4,000 字符 |

```bash
curl -sS --request POST "https://openapi.felo.ai/api/v1/data/x/user/search" \
  -H "Authorization: Bearer $FELO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"AI research"}'
```

返回 `users`、`total`、`has_next` 和 `next_cursor`。此接口没有 `limit` 参数。

```json
{"users":[],"total":0,"has_next":false,"next_cursor":null}
```

## 3. 查询用户帖子

`POST /api/v1/data/x/user/tweets`

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `username` | string | 至少提供一个用户标识 | 用户名，最长 100 字符 |
| `x_user_id` | string | 至少提供一个用户标识 | 用户 ID，最长 100 字符；同时提供时优先使用 ID |
| `limit` | integer | 否 | 1–1,000；当前实现至少按 20 条处理，默认 20 |
| `cursor` | string | 否 | 分页游标，最长 4,000 字符 |
| `include_replies` | boolean | 否 | 是否附带嵌套回复；默认 `false` |

```bash
curl -sS --request POST "https://openapi.felo.ai/api/v1/data/x/user/tweets" \
  -H "Authorization: Bearer $FELO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"username":"OpenAI","limit":20,"include_replies":false}'
```

返回 `tweets`、`total`、`has_next` 和 `next_cursor`。把 `limit` 设为小于 20 的值，不代表一定减少返回数量或费用。包含回复可能增加返回数据量及计费条数。

### 数量与嵌套回复边界

`limit` 是本次请求希望收集的主帖数量，不是保证返回的数量。当前实现按至少 20 条处理：目标为 20 条时读取一页；目标大于 20 条时自动向上游翻页，最多读取 50 页。每轮取页前会检查累计时间，超过 60 秒便停止继续取页；该检查不是整个 HTTP 请求必须在 60 秒内结束的承诺。

达到目标、上游没有下一页、返回空页、游标缺失或达到上述限制时，都会停止收集。因此成功响应也可能少于 `limit`。需要继续时，按响应的 `has_next` 和 `next_cursor` 分页。

`include_replies=true` 时，每个返回主帖额外收集最多 5 页、最多 100 条嵌套回复，遇到无下一页、空页或无法继续的游标会提前结束。`limit` 不包含这些嵌套回复，主帖的 `total` 也不包含它们；回复仍可能增加计费条数。

嵌套 `replies` 不是完整回复存档，也不提供这一轮附加收集的独立翻页游标。需要单独分页读取回复时，请调用“查询帖子回复”，使用该接口返回的游标；不要将主帖的 `next_cursor` 用于回复。

## 4. 搜索帖子

`POST /api/v1/data/x/tweet/search`

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `query` | string | 是 | 搜索内容，1–1,000 字符 |
| `query_type` | string | 否 | `Latest`（默认）或 `Top` |
| `limit` | integer | 否 | 1–1,000；当前实现至少按 20 条处理，默认 20 |
| `cursor` | string | 否 | 分页游标，最长 4,000 字符 |
| `since_time` | integer 或 string | 否 | 起始时间；推荐用 Unix 秒数，避免时间格式歧义 |
| `until_time` | integer 或 string | 否 | 截止时间；数值使用秒，不是毫秒 |

```bash
curl -sS --request POST "https://openapi.felo.ai/api/v1/data/x/tweet/search" \
  -H "Authorization: Bearer $FELO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"retrieval augmented generation","query_type":"Latest","limit":20}'
```

返回 `tweets`、`total`、`has_next` 和 `next_cursor`。帖子包含 `id`、`content`、`author`、`created_at` 和 `metrics`，可能嵌套回复、引用或转发的帖子。ID 请按字符串处理。

### 返回数量边界

`limit` 是期望收集数量，不保证取满。当前实现按至少 20 条处理：目标为 20 条时读取一页，目标大于 20 条时自动翻页，最多读取 50 页。每轮取页前检查累计时间，超过 60 秒后不再继续取页；正在进行的上游请求可能继续等待，因此这不是整个 HTTP 请求的超时上限。

达到目标、上游没有下一页、返回空页或游标缺失时也会停止。响应少于 `limit` 不代表请求失败，也不代表已经获取所有匹配帖子；是否继续分页应检查 `has_next` 和 `next_cursor`。

```json
{
  "tweets": [{
    "id":"example-post-id",
    "content":"示例帖子内容",
    "author":{"user_id":"example-user-id","username":"example","display_name":"Example"},
    "created_at":"2026-11-01T00:00:00Z",
    "metrics":{},
    "replies":[]
  }],
  "total":1,
  "has_next":false,
  "next_cursor":null
}
```

## 5. 查询帖子回复

`POST /api/v1/data/x/tweet/replies`

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `tweet_ids` | string 或 string[] | 是 | 单个帖子 ID，或最多 20 个 ID 的数组 |
| `cursor` | string | 否 | 最长 4,000 字符；分页时只提交一个帖子 ID |
| `since_time` | integer | 否 | 起始时间，Unix 秒数 |
| `until_time` | integer | 否 | 截止时间，Unix 秒数 |

把占位符替换为真实帖子 ID：

```bash
curl -sS --request POST "https://openapi.felo.ai/api/v1/data/x/tweet/replies" \
  -H "Authorization: Bearer $FELO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tweet_ids":["POST_ID"]}'
```

与帖子搜索不同，回复按原帖子分组放在 `results` 中，每组有自己的分页状态：

```json
{"results":[{"tweet_id":"POST_ID","replies":[],"total":0,"has_next":false,"next_cursor":null}]}
```

### 单次回复数量边界

每次请求对每个 `tweet_id` 读取一页上游回复，最多保留 100 条，不会自动翻完该帖子的所有回复。批量传入最多 20 个 ID 时，100 条的上限分别作用于每个 `results` 项，实际返回可以少于上限。

每组的 `total` 是该组本次返回的回复条数，不是该帖子的全部回复数。需要后续页时，取这一组的 `next_cursor`，下一次只提交对应的一个 `tweet_id`；接口没有用于提高单次上限的 `limit` 参数。

## 分页与费用控制

`total` 表示本次响应中对应数组的条数；帖子查询的 `total` 只统计主帖，回复查询则逐组统计。它不是全站匹配总数、帖子完整回复总数或最终计费条数。

1. 首次请求不传 `cursor`。
2. 读取 `has_next` 和 `next_cursor`；查询回复时，读取每个 `results` 项中的这两个字段。
3. 有下一页且游标非空时，保持查询条件不变，原样传回游标。批量回复需要逐个帖子继续分页。
4. 没有下一页、游标重复，或达到应用自己的页数和费用上限时停止。

分页状态反映上游最后一次返回的状态；达到数量上限后返回的集合可能经过截取。即使 `has_next=false`，也不能据此承诺已取得 X 上的全部历史或全部回复。应用应展示实际返回条数，避免把有限结果标记为“全部数据”。

每次成功翻页都可能产生费用。五个接口按返回结果条数计费，不是固定的每次请求价格。嵌套回复等记录可能影响计费条数，不要只用最外层数组长度估算费用。请结合[当前价格表](https://openapi.felo.ai/api/v1/data/prices)和控制台用量查看。完整对象字段见 [OpenAPI 定义](https://openapi.felo.ai/api/v1/data/openapi.json)。
